// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. //go:build script package js import ( "bytes" "errors" teaconst "github.com/TeaOSLab/EdgeNode/internal/const" "github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/iwind/TeaGo/maps" "io" "net/http" "net/url" "strings" "time" ) var sharedHTTPClientPool = utils.NewHTTPClient(30 * time.Second) func init() { if !teaconst.IsMain { return } SharedLibraryManager.Register(&JSNetHTTPClientLibrary{}) } type JSNetHTTPClientLibrary struct { JSBaseLibrary } func (this *JSNetHTTPClientLibrary) JSNamespace() string { return "gojs.net.http.client.JSNetHTTPClientLibrary" } func (this *JSNetHTTPClientLibrary) JSPrototype() string { return ` gojs.net.http.client.Request = class { #url #headers = {} #method #body constructor(url) { this.#url = url } get url() { return this.#url } addHeader(name, value) { if (typeof(name) == "string" && typeof(value) == "string") { if (typeof(this.#headers[name]) == "undefined") { this.#headers[name] = [value] } else { this.#headers[name].push(value) } } } get headers() { return this.#headers } setContentType(contentType) { this.#headers["Content-Type"] = [contentType] } setUserAgent(userAgent) { this.#headers["User-Agent"] = [userAgent] } setMethod(method) { this.#method = method } get method() { return this.#method } setBody(body) { this.#body = body } get body() { return this.#body } get() { this.setMethod("GET") return gojs.net.http.client.DefaultClient.do(this) } post() { this.setMethod("POST") return gojs.net.http.client.DefaultClient.do(this) } } gojs.net.http.client.Response = class { #goObjectId constructor(goObjectId) { this.#goObjectId = goObjectId } get goObjectId() { return this.#goObjectId } get error() { return $this.ResponseError(this.#goObjectId) } get contentLength() { return $this.ResponseContentLength(this.#goObjectId) } get status() { return $this.ResponseStatus(this.#goObjectId) } get headers() { return $this.ResponseHeaders(this.#goObjectId) } get body() { return $this.ResponseBodyString(this.#goObjectId) } get bodyObject() { return JSON.parse(this.body) } } gojs.net.http.client.Client = class { do(req) { let respObjectId = $this.Do({ "url": req.url, "method": req.method, "headers": req.headers, "headerKeys": Object.keys(req.headers), "body": req.body, }) return new gojs.net.http.client.Response(respObjectId) } } gojs.net.http.client.DefaultClient = new gojs.net.http.client.Client() ` } func (this *JSNetHTTPClientLibrary) JSDone(ctx *Context) { // TODO 因为每个请求会执行,所以这里需要性能,可能需要有调用的时候才执行 var objMap = ctx.GoObjectMap() for _, obj := range objMap { if obj != nil { resp, ok := obj.(*JSNetHTTPClientLibraryResponse) if ok { resp.Close() } } } } func (this *JSNetHTTPClientLibrary) Do(arguments *FunctionArguments) (uint32, error) { obj, ok := arguments.ObjectAt(0) if !ok { return 0, ErrGoObjectNotFound } if obj.IsNullOrUndefined() { return 0, ErrGoObjectNotFound } urlString, _ := obj.GetString("url") _, err := url.Parse(urlString) if err != nil { return 0, errors.New("invalid url: '" + urlString + "'") } method, _ := obj.GetString("method") if len(method) == 0 { method = http.MethodGet } method = strings.ToUpper(method) var headers = http.Header{} headerObj, ok := obj.GetObject("headers") if ok && !headerObj.IsNullOrUndefined() { headerKeysArray, _ := obj.GetStringsArray("headerKeys") for _, headerKey := range headerKeysArray { headerValues, _ := headerObj.GetStringsArray(headerKey) if len(headerValues) > 0 { headers[headerKey] = headerValues } } } var bodyReader io.Reader bodyString, _ := obj.GetString("body") if len(bodyString) > 0 { bodyReader = bytes.NewReader([]byte(bodyString)) } req, err := http.NewRequest(method, urlString, bodyReader) if err != nil { var goObjectId = arguments.Context().AddGoObject(NewJSNetHTTPClientLibraryResponse(nil, err)) return goObjectId, nil } var hasContentType = false var hasUserAgent = false req.Header = headers for k := range headers { if strings.ToLower(k) == "content-type" { hasContentType = true } if strings.ToLower(k) == "user-agent" { hasUserAgent = true } } if !hasContentType && method == http.MethodPost { req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } if !hasUserAgent { req.Header.Set("User-Agent", teaconst.GlobalProductName+"-Node-Script/"+teaconst.Version) } resp, err := sharedHTTPClientPool.Do(req) if err != nil { var goObjectId = arguments.Context().AddGoObject(NewJSNetHTTPClientLibraryResponse(nil, err)) return goObjectId, nil } var goObjectId = arguments.Context().AddGoObject(NewJSNetHTTPClientLibraryResponse(resp, err)) return goObjectId, nil } func (this *JSNetHTTPClientLibrary) ResponseError(arguments *FunctionArguments) (any, error) { obj, ok := arguments.GoObjectAt(0) if !ok { return "", ErrGoObjectNotFound } var err = obj.(*JSNetHTTPClientLibraryResponse).Error() if err != nil { return maps.Map{"message": err.Error()}, nil } return nil, nil } func (this *JSNetHTTPClientLibrary) ResponseStatus(arguments *FunctionArguments) (int, error) { obj, ok := arguments.GoObjectAt(0) if !ok { return 0, ErrGoObjectNotFound } return obj.(*JSNetHTTPClientLibraryResponse).Status(), nil } func (this *JSNetHTTPClientLibrary) ResponseContentLength(arguments *FunctionArguments) (int64, error) { obj, ok := arguments.GoObjectAt(0) if !ok { return 0, ErrGoObjectNotFound } return obj.(*JSNetHTTPClientLibraryResponse).ContentLength(), nil } func (this *JSNetHTTPClientLibrary) ResponseHeaders(arguments *FunctionArguments) (http.Header, error) { obj, ok := arguments.GoObjectAt(0) if !ok { return nil, ErrGoObjectNotFound } return obj.(*JSNetHTTPClientLibraryResponse).Headers(), nil } func (this *JSNetHTTPClientLibrary) ResponseBodyString(arguments *FunctionArguments) (string, error) { obj, ok := arguments.GoObjectAt(0) if !ok { return "", ErrGoObjectNotFound } s, _ := obj.(*JSNetHTTPClientLibraryResponse).BodyString() return s, nil } type JSNetHTTPClientLibraryResponse struct { resp *http.Response err error body []byte bodyRead bool } func NewJSNetHTTPClientLibraryResponse(resp *http.Response, err error) *JSNetHTTPClientLibraryResponse { return &JSNetHTTPClientLibraryResponse{resp: resp, err: err} } func (this *JSNetHTTPClientLibraryResponse) Close() { if this.resp != nil && this.resp.Body != nil { _ = this.resp.Body.Close() } } func (this *JSNetHTTPClientLibraryResponse) Error() error { return this.err } func (this *JSNetHTTPClientLibraryResponse) Status() int { if this.resp != nil { return this.resp.StatusCode } return 0 } func (this *JSNetHTTPClientLibraryResponse) ContentLength() int64 { if this.resp != nil { return this.resp.ContentLength } return 0 } func (this *JSNetHTTPClientLibraryResponse) Headers() http.Header { if this.resp != nil { return this.resp.Header } return http.Header{} } func (this *JSNetHTTPClientLibraryResponse) BodyString() (string, error) { if this.resp != nil { data, err := this.readBody() if err != nil { return "", err } return string(data), nil } return "", nil } func (this *JSNetHTTPClientLibraryResponse) readBody() ([]byte, error) { if this.bodyRead { return this.body, nil } data, err := io.ReadAll(this.resp.Body) this.body = data this.bodyRead = true return data, err }