GopeedLab/gopeed · error

too many redirects

Error message

too many redirects

What it means

In the same redirect policy (pkg/download/engine/inject/xhr/module.go:246-248), once more than 20 redirects have been followed for a single request (len(via) > 20), the http client stops and returns 'too many redirects'. This mirrors the browser/fetch safety limit and protects against redirect loops between two or more URLs.

Source

Thrown at pkg/download/engine/inject/xhr/module.go:247

	if contentType != "" && (!isStringBody || xhr.requestHeaders.Get("Content-Type") == "") {
		reqBuilder.SetHeader("Content-Type", contentType)
	}

	// Set timeout
	if xhr.Timeout > 0 {
		xhr.client.SetTimeout(time.Duration(xhr.Timeout) * time.Millisecond)
	}

	// Configure redirect behavior
	xhr.client.SetRedirectPolicy(func(req *http.Request, via []*http.Request) error {
		if xhr.Redirect == redirectManual {
			return http.ErrUseLastResponse
		}
		if xhr.Redirect == redirectError {
			return errors.New("redirect failed")
		}
		if len(via) > 20 {
			return errors.New("too many redirects")
		}
		return nil
	})

	// Execute request
	resp, err := reqBuilder.Send(xhr.method, xhr.url)
	if err != nil {
		// handle timeout error
		var ne net.Error
		if errors.As(err, &ne) && ne.Timeout() {
			if xhr.Timeout > 0 {
				xhr.Upload.callOntimeout()
				xhr.callOntimeout()
			}
			return
		}
		xhr.Upload.callOnerror()
		xhr.callOnerror()

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Trace the chain: run with redirect:'manual' in a loop (max ~5) and log each Location to find the loop pair
  2. Fix the loop server-side (canonicalize host/scheme, correct rewrite rules)
  3. If auth-bounce, attach the needed Cookie/Authorization header in the script's fetch options so the target stops redirecting
  4. Request the final destination URL directly once identified

Example fix

// before (script)
const r = await fetch('http://a.example/file') // a <-> b redirect loop -> "too many redirects"

// after (script)
let u = 'http://a.example/file'
for (let i = 0; i < 5; i++) {
  const m = await fetch(u, { redirect: 'manual' })
  if (m.status < 300 || m.status >= 400) break
  console.log('hop:', u, '->', m.headers.get('location'))
  u = new URL(m.headers.get('location'), u).href
}
const r = await fetch(u)
Defensive patterns

Strategy: try-catch

Validate before calling

// Script-side: bounded manual follow to detect loops before the 20-hop limit
async function fetchBounded(url, maxHops = 5) {
    let u = url
    for (let i = 0; i <= maxHops; i++) {
        const m = await fetch(u, { redirect: 'manual' })
        if (m.status < 300 || m.status >= 400) return m
        const loc = m.headers.get('location')
        if (!loc) throw new Error('redirect without location')
        u = new URL(loc, u).href
    }
    throw new Error('redirect loop suspected at ' + u)
}

Try / catch

try {
    const r = await fetch(url) // follow
} catch (e) {
    if (String(e).includes('too many redirects')) {
        // hop-trace manually (see validationCode) to find the loop pair, then request the final URL directly
    }
}

Prevention

When it happens

Trigger: Two URLs redirecting to each other (A -> B -> A ...) with redirect:'follow'; a redirect chain longer than 20 hops (common with ad/tracking chains or auth gates); a server that rewrites a URL and redirects to the rewritten form which again fails the rewrite; cookies/auth not being forwarded so the target keeps bouncing back to login -> original URL.

Common situations: Login-protected download URLs where the injected client lacks the session cookie, producing an infinite auth bounce; misconfigured vhosts alternating between www/non-www or http/https; CDN chains; relative Location headers that resolve back onto the redirecting endpoint.

Related errors


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/b9267f2ea93cf7ec. Report an issue: GitHub.