123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- package surge
- import (
- "bytes"
- "crypto/tls"
- "io"
- "net/http"
- "net/http/cookiejar"
- "time"
- "github.com/sagernet/sing-box/script/jsc"
- "github.com/sagernet/sing-box/script/modules/boxctx"
- "github.com/sagernet/sing/common"
- F "github.com/sagernet/sing/common/format"
- "github.com/dop251/goja"
- "golang.org/x/net/publicsuffix"
- )
- type HTTP struct {
- class jsc.Class[*Module, *HTTP]
- cookieJar *cookiejar.Jar
- httpTransport *http.Transport
- }
- func createHTTP(module *Module) jsc.Class[*Module, *HTTP] {
- class := jsc.NewClass[*Module, *HTTP](module)
- class.DefineConstructor(newHTTP)
- class.DefineMethod("get", httpRequest(http.MethodGet))
- class.DefineMethod("post", httpRequest(http.MethodPost))
- class.DefineMethod("put", httpRequest(http.MethodPut))
- class.DefineMethod("delete", httpRequest(http.MethodDelete))
- class.DefineMethod("head", httpRequest(http.MethodHead))
- class.DefineMethod("options", httpRequest(http.MethodOptions))
- class.DefineMethod("patch", httpRequest(http.MethodPatch))
- class.DefineMethod("trace", httpRequest(http.MethodTrace))
- class.DefineMethod("toString", (*HTTP).toString)
- return class
- }
- func newHTTP(class jsc.Class[*Module, *HTTP], call goja.ConstructorCall) *HTTP {
- return &HTTP{
- class: class,
- cookieJar: common.Must1(cookiejar.New(&cookiejar.Options{
- PublicSuffixList: publicsuffix.List,
- })),
- httpTransport: &http.Transport{
- ForceAttemptHTTP2: true,
- TLSClientConfig: &tls.Config{},
- },
- }
- }
- func httpRequest(method string) func(s *HTTP, call goja.FunctionCall) any {
- return func(s *HTTP, call goja.FunctionCall) any {
- if len(call.Arguments) != 2 {
- panic(s.class.Runtime().NewTypeError("invalid arguments"))
- }
- context := boxctx.MustFromRuntime(s.class.Runtime())
- var (
- url string
- headers http.Header
- body []byte
- timeout = 5 * time.Second
- insecure bool
- autoCookie bool = true
- autoRedirect bool
- // policy string
- binaryMode bool
- )
- switch optionsValue := call.Argument(0).(type) {
- case goja.String:
- url = optionsValue.String()
- case *goja.Object:
- url = jsc.AssertString(s.class.Runtime(), optionsValue.Get("url"), "options.url", false)
- headers = jsc.AssertHTTPHeader(s.class.Runtime(), optionsValue.Get("headers"), "option.headers")
- body = jsc.AssertStringBinary(s.class.Runtime(), optionsValue.Get("body"), "options.body", true)
- timeoutInt := jsc.AssertInt(s.class.Runtime(), optionsValue.Get("timeout"), "options.timeout", true)
- if timeoutInt > 0 {
- timeout = time.Duration(timeoutInt) * time.Second
- }
- insecure = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("insecure"), "options.insecure", true)
- autoCookie = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("auto-cookie"), "options.auto-cookie", true)
- autoRedirect = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("auto-redirect"), "options.auto-redirect", true)
- // policy = jsc.AssertString(s.class.Runtime(), optionsValue.Get("policy"), "options.policy", true)
- binaryMode = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("binary-mode"), "options.binary-mode", true)
- default:
- panic(s.class.Runtime().NewTypeError(F.ToString("invalid argument: options: expected string or object, but got ", optionsValue)))
- }
- callback := jsc.AssertFunction(s.class.Runtime(), call.Argument(1), "callback")
- s.httpTransport.TLSClientConfig.InsecureSkipVerify = insecure
- httpClient := &http.Client{
- Timeout: timeout,
- Transport: s.httpTransport,
- CheckRedirect: func(req *http.Request, via []*http.Request) error {
- if autoRedirect {
- return nil
- }
- return http.ErrUseLastResponse
- },
- }
- if autoCookie {
- httpClient.Jar = s.cookieJar
- }
- request, err := http.NewRequestWithContext(context.Context, method, url, bytes.NewReader(body))
- if host := headers.Get("Host"); host != "" {
- request.Host = host
- headers.Del("Host")
- }
- request.Header = headers
- if err != nil {
- panic(s.class.Runtime().NewGoError(err))
- }
- go func() {
- defer s.httpTransport.CloseIdleConnections()
- response, executeErr := httpClient.Do(request)
- if err != nil {
- _, err = callback(nil, s.class.Runtime().NewGoError(executeErr), nil, nil)
- if err != nil {
- context.ErrorHandler(err)
- }
- return
- }
- defer response.Body.Close()
- var content []byte
- content, err = io.ReadAll(response.Body)
- if err != nil {
- _, err = callback(nil, s.class.Runtime().NewGoError(err), nil, nil)
- if err != nil {
- context.ErrorHandler(err)
- }
- }
- responseObject := s.class.Runtime().NewObject()
- responseObject.Set("status", response.StatusCode)
- responseObject.Set("headers", jsc.HeadersToValue(s.class.Runtime(), response.Header))
- var bodyValue goja.Value
- if binaryMode {
- bodyValue = jsc.NewUint8Array(s.class.Runtime(), content)
- } else {
- bodyValue = s.class.Runtime().ToValue(string(content))
- }
- _, err = callback(nil, nil, responseObject, bodyValue)
- }()
- return nil
- }
- }
- func (h *HTTP) toString(call goja.FunctionCall) any {
- return "[sing-box Surge HTTP]"
- }
|