From 529420ee5a69829009310f1993c05745d0b2843c Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Sun, 20 Sep 2026 23:18:21 +0400 Subject: [PATCH] feat(script): replace followRedirects with maxRedirects http option --- internal/sarin/scripthttp.go | 9 +++---- internal/script/http.go | 49 +++++++++++++++++++++++------------- internal/script/js_http.go | 28 +++++++++++++++++---- internal/script/lua_http.go | 13 ++++++---- 4 files changed, 66 insertions(+), 33 deletions(-) diff --git a/internal/sarin/scripthttp.go b/internal/sarin/scripthttp.go index 2b88cdc..9cdcb4f 100644 --- a/internal/sarin/scripthttp.go +++ b/internal/sarin/scripthttp.go @@ -11,9 +11,6 @@ import ( "go.aykhans.me/sarin/internal/types" ) -// scriptHTTPMaxRedirects is the redirect limit when followRedirects is set. -const scriptHTTPMaxRedirects = 10 - // scriptHTTPDefaultTimeout is the default timeout for script requests, independent of -T. const scriptHTTPDefaultTimeout = 30 * time.Second @@ -65,7 +62,7 @@ func newScriptHTTPClient(dialFunc fasthttp.DialFunc, defaultTimeout time.Duratio } } -// Do sends the request. With followRedirects the timeout applies to each hop. +// Do sends the request. With maxRedirects the timeout applies to each hop. // It can return the following errors: // - types.ScriptHTTPRequestError func (c *scriptHTTPClient) Do(r *script.HTTPRequest) (*script.HTTPResponse, error) { @@ -116,8 +113,8 @@ func (c *scriptHTTPClient) Do(r *script.HTTPRequest) (*script.HTTPResponse, erro } req.SetTimeout(timeout) - if r.FollowRedirects { - err = client.DoRedirects(req, resp, scriptHTTPMaxRedirects) + if r.MaxRedirects > 0 { + err = client.DoRedirects(req, resp, r.MaxRedirects) } else { err = client.Do(req, resp) } diff --git a/internal/script/http.go b/internal/script/http.go index a82a221..7dc1b80 100644 --- a/internal/script/http.go +++ b/internal/script/http.go @@ -1,7 +1,9 @@ package script import ( + "math" "net/http" + "strconv" "strings" "time" @@ -10,15 +12,15 @@ import ( // HTTPRequest is a request made by a script through the http.* functions. type HTTPRequest struct { - Method string - URL string - Headers map[string][]string - Params map[string][]string - Cookies map[string][]string - Body string - Timeout time.Duration // zero means the doer's default timeout - Insecure bool - FollowRedirects bool + Method string + URL string + Headers map[string][]string + Params map[string][]string + Cookies map[string][]string + Body string + Timeout time.Duration // zero means the doer's default timeout + Insecure bool + MaxRedirects int } // HTTPResponse is the response returned to a script by the http.* functions. @@ -63,14 +65,14 @@ const httpDefaultMethod = http.MethodGet // Option keys accepted by the http function. const ( - httpOptionMethod = "method" - httpOptionHeaders = "headers" - httpOptionParams = "params" - httpOptionCookies = "cookies" - httpOptionBody = "body" - httpOptionTimeout = "timeout" - httpOptionInsecure = "insecure" - httpOptionFollowRedirects = "followRedirects" + httpOptionMethod = "method" + httpOptionHeaders = "headers" + httpOptionParams = "params" + httpOptionCookies = "cookies" + httpOptionBody = "body" + httpOptionTimeout = "timeout" + httpOptionInsecure = "insecure" + httpOptionMaxRedirects = "maxRedirects" ) // httpBridge connects http to the doer. Calls only work while transform runs. @@ -97,6 +99,19 @@ func (b *httpBridge) do(req *HTTPRequest) (*HTTPResponse, error) { return b.doer.Do(req) } +// parseHTTPMaxRedirects checks that the redirect limit is a non-negative whole number. +// It can return the following errors: +// - types.ScriptHTTPOptionError +func parseHTTPMaxRedirects(count float64) (int, error) { + if count < 0 || count != math.Trunc(count) { + return 0, types.NewScriptHTTPOptionError( + httpOptionMaxRedirects, + types.NewScriptTypeError("a whole number of 0 or more", strconv.FormatFloat(count, 'f', -1, 64)), + ) + } + return int(count), nil +} + // parseHTTPTimeout parses a Go duration string such as "500ms" or "2s". // It can return the following errors: // - types.ScriptHTTPOptionError diff --git a/internal/script/js_http.go b/internal/script/js_http.go index d519102..7cdce59 100644 --- a/internal/script/js_http.go +++ b/internal/script/js_http.go @@ -100,16 +100,22 @@ func (e *JsEngine) parseHTTPOptions(opts *goja.Object, req *HTTPRequest) error { if req.Timeout, err = parseHTTPTimeout(timeout); err != nil { return err } - case httpOptionInsecure, httpOptionFollowRedirects: + case httpOptionInsecure: flag, ok := value.Export().(bool) if !ok { return types.NewScriptHTTPOptionError(name, types.NewScriptTypeError("boolean", jsTypeName(value))) } - if name == httpOptionInsecure { - req.Insecure = flag - } else { - req.FollowRedirects = flag + req.Insecure = flag + case httpOptionMaxRedirects: + count, ok := numberValue(value) + if !ok { + return types.NewScriptHTTPOptionError(name, types.NewScriptTypeError("number", jsTypeName(value))) + } + + var err error + if req.MaxRedirects, err = parseHTTPMaxRedirects(count); err != nil { + return err } default: return types.NewScriptHTTPOptionError(name, types.ErrScriptHTTPUnknownOption) @@ -190,3 +196,15 @@ func (e *JsEngine) throw(err error) { } panic(errorValue) } + +// numberValue reports whether value is a JavaScript number, and returns it. +func numberValue(value goja.Value) (float64, bool) { + switch number := value.Export().(type) { + case int64: + return float64(number), true + case float64: + return number, true + default: + return 0, false + } +} diff --git a/internal/script/lua_http.go b/internal/script/lua_http.go index efd7fc2..a1e3a14 100644 --- a/internal/script/lua_http.go +++ b/internal/script/lua_http.go @@ -92,17 +92,20 @@ func (e *LuaEngine) parseHTTPOptions(opts *lua.LTable, req *HTTPRequest) error { return } req.Timeout, err = parseHTTPTimeout(string(timeout)) - case httpOptionInsecure, httpOptionFollowRedirects: + case httpOptionInsecure: flag, ok := v.(lua.LBool) if !ok { err = types.NewScriptHTTPOptionError(name, types.NewScriptTypeError("boolean", v.Type().String())) return } - if name == httpOptionInsecure { - req.Insecure = bool(flag) - } else { - req.FollowRedirects = bool(flag) + req.Insecure = bool(flag) + case httpOptionMaxRedirects: + count, ok := v.(lua.LNumber) + if !ok { + err = types.NewScriptHTTPOptionError(name, types.NewScriptTypeError("number", v.Type().String())) + return } + req.MaxRedirects, err = parseHTTPMaxRedirects(float64(count)) default: err = types.NewScriptHTTPOptionError(name, types.ErrScriptHTTPUnknownOption) }