/
t3
/
cli
Обзор
Документация
Войти
/
t3
/
cli
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
internal/engine/bridge/http.go
288 строк
9 KB
Ivan
http default client
09 июл 2026, 14:47
09 июл 2026, 14:47
2103ad3
Код
Авторство
О чём код?
package bridge import ( "fmt" "net/http" "sync" "github.com/dop251/goja" ) // NewHTTP инжектирует t3.http в goja runtime. // Если transport == nil, t3.http.newClient будет кидать исключение // "http client is not available". func NewHTTP(vm *goja.Runtime, transport *http.Transport) { t3Val := vm.Get("t3") var t3Obj *goja.Object if t3Val == nil || goja.IsUndefined(t3Val) { t3Obj = vm.NewObject() vm.Set("t3", t3Obj) } else { t3Obj = t3Val.ToObject(vm) } httpObj := vm.NewObject() // newClient(config) — создаёт HTTP-клиент с заданной конфигурацией httpObj.Set("newClient", newHTTPClientFunc(vm, transport)) t3Obj.Set("http", httpObj) } // NewGlobalHTTP инжектирует глобальный объект http (без префикса t3.) // в goja runtime. Предоставляет convenience-методы get/post/put/del // с автосозданием дефолтного клиента, а также newClient(config) для // создания кастомного клиента. func NewGlobalHTTP(vm *goja.Runtime, transport *http.Transport) { httpObj := vm.NewObject() var defaultClient *httpClient var once sync.Once // getClient создаёт дефолтный HTTP-клиент при первом вызове getClient := func() *httpClient { once.Do(func() { if transport == nil { // Если transport не передан, создаём дефолтный defaultClient, _ = newHTTPClient(&HTTPClientConfig{}, nil) } else { defaultClient, _ = newHTTPClient(&HTTPClientConfig{}, transport) } }) return defaultClient } // get(url) — GET-запрос с дефолтным клиентом httpObj.Set("get", func(call goja.FunctionCall) goja.Value { urlArg := call.Argument(0) if urlArg == nil || goja.IsUndefined(urlArg) || urlArg.String() == "" { panic(vm.ToValue("http.get(): url argument is required")) } resp, err := getClient().fetch(urlArg.String(), &FetchOptions{Method: "GET"}) if err != nil { panic(vm.ToValue("http.get(): " + err.Error())) } return newResponseObject(vm, resp) }) // post(url, body) — POST-запрос с дефолтным клиентом httpObj.Set("post", func(call goja.FunctionCall) goja.Value { urlArg := call.Argument(0) if urlArg == nil || goja.IsUndefined(urlArg) || urlArg.String() == "" { panic(vm.ToValue("http.post(): url argument is required")) } opts := &FetchOptions{Method: "POST"} if bodyArg := call.Argument(1); bodyArg != nil && !goja.IsUndefined(bodyArg) { opts.Body = bodyArg.String() } resp, err := getClient().fetch(urlArg.String(), opts) if err != nil { panic(vm.ToValue("http.post(): " + err.Error())) } return newResponseObject(vm, resp) }) // put(url, body) — PUT-запрос с дефолтным клиентом httpObj.Set("put", func(call goja.FunctionCall) goja.Value { urlArg := call.Argument(0) if urlArg == nil || goja.IsUndefined(urlArg) || urlArg.String() == "" { panic(vm.ToValue("http.put(): url argument is required")) } opts := &FetchOptions{Method: "PUT"} if bodyArg := call.Argument(1); bodyArg != nil && !goja.IsUndefined(bodyArg) { opts.Body = bodyArg.String() } resp, err := getClient().fetch(urlArg.String(), opts) if err != nil { panic(vm.ToValue("http.put(): " + err.Error())) } return newResponseObject(vm, resp) }) // del(url) — DELETE-запрос с дефолтным клиентом httpObj.Set("del", func(call goja.FunctionCall) goja.Value { urlArg := call.Argument(0) if urlArg == nil || goja.IsUndefined(urlArg) || urlArg.String() == "" { panic(vm.ToValue("http.del(): url argument is required")) } resp, err := getClient().fetch(urlArg.String(), &FetchOptions{Method: "DELETE"}) if err != nil { panic(vm.ToValue("http.del(): " + err.Error())) } return newResponseObject(vm, resp) }) // patch(url, body) — PATCH-запрос с дефолтным клиентом httpObj.Set("patch", func(call goja.FunctionCall) goja.Value { urlArg := call.Argument(0) if urlArg == nil || goja.IsUndefined(urlArg) || urlArg.String() == "" { panic(vm.ToValue("http.patch(): url argument is required")) } opts := &FetchOptions{Method: "PATCH"} if bodyArg := call.Argument(1); bodyArg != nil && !goja.IsUndefined(bodyArg) { opts.Body = bodyArg.String() } resp, err := getClient().fetch(urlArg.String(), opts) if err != nil { panic(vm.ToValue("http.patch(): " + err.Error())) } return newResponseObject(vm, resp) }) // head(url) — HEAD-запрос с дефолтным клиентом httpObj.Set("head", func(call goja.FunctionCall) goja.Value { urlArg := call.Argument(0) if urlArg == nil || goja.IsUndefined(urlArg) || urlArg.String() == "" { panic(vm.ToValue("http.head(): url argument is required")) } resp, err := getClient().fetch(urlArg.String(), &FetchOptions{Method: "HEAD"}) if err != nil { panic(vm.ToValue("http.head(): " + err.Error())) } return newResponseObject(vm, resp) }) // options(url) — OPTIONS-запрос с дефолтным клиентом httpObj.Set("options", func(call goja.FunctionCall) goja.Value { urlArg := call.Argument(0) if urlArg == nil || goja.IsUndefined(urlArg) || urlArg.String() == "" { panic(vm.ToValue("http.options(): url argument is required")) } resp, err := getClient().fetch(urlArg.String(), &FetchOptions{Method: "OPTIONS"}) if err != nil { panic(vm.ToValue("http.options(): " + err.Error())) } return newResponseObject(vm, resp) }) // newClient(config) — создаёт кастомный HTTP-клиент (алиас t3.http.newClient) httpObj.Set("newClient", newHTTPClientFunc(vm, transport)) // fetch(url, opts) — универсальный метод с дефолтным клиентом httpObj.Set("fetch", func(call goja.FunctionCall) goja.Value { urlArg := call.Argument(0) if urlArg == nil || goja.IsUndefined(urlArg) || urlArg.String() == "" { panic(vm.ToValue("http.fetch(): url argument is required")) } urlStr := urlArg.String() opts := parseFetchOptions(vm, call.Argument(1)) resp, err := getClient().fetch(urlStr, opts) if err != nil { panic(vm.ToValue("http.fetch(): " + err.Error())) } return newResponseObject(vm, resp) }) vm.Set("http", httpObj) } // newHTTPClientFunc создаёт замыкание для newClient(config), // общее для t3.http.newClient и http.newClient. func newHTTPClientFunc(vm *goja.Runtime, transport *http.Transport) func(goja.FunctionCall) goja.Value { return func(call goja.FunctionCall) goja.Value { if transport == nil { panic(vm.ToValue("http client is not available in this context")) } cfgVal := call.Argument(0) if cfgVal == nil || goja.IsUndefined(cfgVal) || goja.IsNull(cfgVal) { panic(vm.ToValue("http.newClient(): config argument is required")) } cfg, err := parseHTTPClientConfig(vm, cfgVal) if err != nil { panic(vm.ToValue("http.newClient(): " + err.Error())) } client, err := newHTTPClient(cfg, transport) if err != nil { panic(vm.ToValue("http.newClient(): " + err.Error())) } return newClientObject(vm, client) } } // parseHTTPClientConfig разбирает JS-объект конфигурации в HTTPClientConfig. // Поддерживает передачу tlsCert и tlsKey как строки или как File (из S3). func parseHTTPClientConfig(vm *goja.Runtime, cfgVal goja.Value) (*HTTPClientConfig, error) { cfgObj := cfgVal.ToObject(vm) cfg := &HTTPClientConfig{ Headers: make(map[string]string), } if v := cfgObj.Get("baseURL"); v != nil && !goja.IsUndefined(v) { cfg.BaseURL = v.String() } if v := cfgObj.Get("insecureSkipVerify"); v != nil && !goja.IsUndefined(v) { cfg.InsecureSkipVerify = v.ToBoolean() } if v := cfgObj.Get("timeoutMs"); v != nil && !goja.IsUndefined(v) { cfg.TimeoutMs = v.ToInteger() } // tlsCert и tlsKey могут быть строкой или File (из S3) if v := cfgObj.Get("tlsCert"); v != nil && !goja.IsUndefined(v) && !goja.IsNull(v) { certStr, ok := resolveStringArg(vm, v) if !ok { return nil, fmt.Errorf("tlsCert must be a string or a File object") } cfg.TLSCert = certStr } if v := cfgObj.Get("tlsKey"); v != nil && !goja.IsUndefined(v) && !goja.IsNull(v) { keyStr, ok := resolveStringArg(vm, v) if !ok { return nil, fmt.Errorf("tlsKey must be a string or a File object") } cfg.TLSKey = keyStr } // headers — map общих заголовков if v := cfgObj.Get("headers"); v != nil && !goja.IsUndefined(v) && !goja.IsNull(v) { headersObj := v.ToObject(vm) for _, key := range headersObj.Keys() { if hv := headersObj.Get(key); hv != nil && !goja.IsUndefined(hv) { cfg.Headers[key] = hv.String() } } } return cfg, nil } // newClientObject создаёт JS-объект HTTPClient с методом fetch(url, options) func newClientObject(vm *goja.Runtime, client *httpClient) *goja.Object { obj := vm.NewObject() obj.Set("fetch", func(call goja.FunctionCall) goja.Value { urlArg := call.Argument(0) if urlArg == nil || goja.IsUndefined(urlArg) || urlArg.String() == "" { panic(vm.ToValue("client.fetch(): url argument is required")) } urlStr := urlArg.String() opts := parseFetchOptions(vm, call.Argument(1)) resp, err := client.fetch(urlStr, opts) if err != nil { panic(vm.ToValue("client.fetch(): " + err.Error())) } return newResponseObject(vm, resp) }) return obj }