Files
waf-platform/EdgeNode/internal/js/lib_net_http_client.go
2026-02-04 20:27:13 +08:00

349 lines
7.5 KiB
Go

// 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
}