http.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. package surge
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "io"
  6. "net/http"
  7. "net/http/cookiejar"
  8. "time"
  9. "github.com/sagernet/sing-box/script/jsc"
  10. "github.com/sagernet/sing-box/script/modules/boxctx"
  11. "github.com/sagernet/sing/common"
  12. F "github.com/sagernet/sing/common/format"
  13. "github.com/dop251/goja"
  14. "golang.org/x/net/publicsuffix"
  15. )
  16. type HTTP struct {
  17. class jsc.Class[*Module, *HTTP]
  18. cookieJar *cookiejar.Jar
  19. httpTransport *http.Transport
  20. }
  21. func createHTTP(module *Module) jsc.Class[*Module, *HTTP] {
  22. class := jsc.NewClass[*Module, *HTTP](module)
  23. class.DefineConstructor(newHTTP)
  24. class.DefineMethod("get", httpRequest(http.MethodGet))
  25. class.DefineMethod("post", httpRequest(http.MethodPost))
  26. class.DefineMethod("put", httpRequest(http.MethodPut))
  27. class.DefineMethod("delete", httpRequest(http.MethodDelete))
  28. class.DefineMethod("head", httpRequest(http.MethodHead))
  29. class.DefineMethod("options", httpRequest(http.MethodOptions))
  30. class.DefineMethod("patch", httpRequest(http.MethodPatch))
  31. class.DefineMethod("trace", httpRequest(http.MethodTrace))
  32. class.DefineMethod("toString", (*HTTP).toString)
  33. return class
  34. }
  35. func newHTTP(class jsc.Class[*Module, *HTTP], call goja.ConstructorCall) *HTTP {
  36. return &HTTP{
  37. class: class,
  38. cookieJar: common.Must1(cookiejar.New(&cookiejar.Options{
  39. PublicSuffixList: publicsuffix.List,
  40. })),
  41. httpTransport: &http.Transport{
  42. ForceAttemptHTTP2: true,
  43. TLSClientConfig: &tls.Config{},
  44. },
  45. }
  46. }
  47. func httpRequest(method string) func(s *HTTP, call goja.FunctionCall) any {
  48. return func(s *HTTP, call goja.FunctionCall) any {
  49. if len(call.Arguments) != 2 {
  50. panic(s.class.Runtime().NewTypeError("invalid arguments"))
  51. }
  52. context := boxctx.MustFromRuntime(s.class.Runtime())
  53. var (
  54. url string
  55. headers http.Header
  56. body []byte
  57. timeout = 5 * time.Second
  58. insecure bool
  59. autoCookie bool = true
  60. autoRedirect bool
  61. // policy string
  62. binaryMode bool
  63. )
  64. switch optionsValue := call.Argument(0).(type) {
  65. case goja.String:
  66. url = optionsValue.String()
  67. case *goja.Object:
  68. url = jsc.AssertString(s.class.Runtime(), optionsValue.Get("url"), "options.url", false)
  69. headers = jsc.AssertHTTPHeader(s.class.Runtime(), optionsValue.Get("headers"), "option.headers")
  70. body = jsc.AssertStringBinary(s.class.Runtime(), optionsValue.Get("body"), "options.body", true)
  71. timeoutInt := jsc.AssertInt(s.class.Runtime(), optionsValue.Get("timeout"), "options.timeout", true)
  72. if timeoutInt > 0 {
  73. timeout = time.Duration(timeoutInt) * time.Second
  74. }
  75. insecure = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("insecure"), "options.insecure", true)
  76. autoCookie = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("auto-cookie"), "options.auto-cookie", true)
  77. autoRedirect = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("auto-redirect"), "options.auto-redirect", true)
  78. // policy = jsc.AssertString(s.class.Runtime(), optionsValue.Get("policy"), "options.policy", true)
  79. binaryMode = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("binary-mode"), "options.binary-mode", true)
  80. default:
  81. panic(s.class.Runtime().NewTypeError(F.ToString("invalid argument: options: expected string or object, but got ", optionsValue)))
  82. }
  83. callback := jsc.AssertFunction(s.class.Runtime(), call.Argument(1), "callback")
  84. s.httpTransport.TLSClientConfig.InsecureSkipVerify = insecure
  85. httpClient := &http.Client{
  86. Timeout: timeout,
  87. Transport: s.httpTransport,
  88. CheckRedirect: func(req *http.Request, via []*http.Request) error {
  89. if autoRedirect {
  90. return nil
  91. }
  92. return http.ErrUseLastResponse
  93. },
  94. }
  95. if autoCookie {
  96. httpClient.Jar = s.cookieJar
  97. }
  98. request, err := http.NewRequestWithContext(context.Context, method, url, bytes.NewReader(body))
  99. if host := headers.Get("Host"); host != "" {
  100. request.Host = host
  101. headers.Del("Host")
  102. }
  103. request.Header = headers
  104. if err != nil {
  105. panic(s.class.Runtime().NewGoError(err))
  106. }
  107. go func() {
  108. defer s.httpTransport.CloseIdleConnections()
  109. response, executeErr := httpClient.Do(request)
  110. if err != nil {
  111. _, err = callback(nil, s.class.Runtime().NewGoError(executeErr), nil, nil)
  112. if err != nil {
  113. context.ErrorHandler(err)
  114. }
  115. return
  116. }
  117. defer response.Body.Close()
  118. var content []byte
  119. content, err = io.ReadAll(response.Body)
  120. if err != nil {
  121. _, err = callback(nil, s.class.Runtime().NewGoError(err), nil, nil)
  122. if err != nil {
  123. context.ErrorHandler(err)
  124. }
  125. }
  126. responseObject := s.class.Runtime().NewObject()
  127. responseObject.Set("status", response.StatusCode)
  128. responseObject.Set("headers", jsc.HeadersToValue(s.class.Runtime(), response.Header))
  129. var bodyValue goja.Value
  130. if binaryMode {
  131. bodyValue = jsc.NewUint8Array(s.class.Runtime(), content)
  132. } else {
  133. bodyValue = s.class.Runtime().ToValue(string(content))
  134. }
  135. _, err = callback(nil, nil, responseObject, bodyValue)
  136. }()
  137. return nil
  138. }
  139. }
  140. func (h *HTTP) toString(call goja.FunctionCall) any {
  141. return "[sing-box Surge HTTP]"
  142. }