mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-10 02:20:28 +00:00
fix: improve retry handling for expired HTTP redirect url (#1263)
This commit is contained in:
5 files changed
+410
-8
No files matched your search
@@ -778,6 +778,168 @@ func TestFetcher_AsyncPrefetch(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestFetcher_DownloadExpiringRedirectURL tests that the fetcher correctly handles
|
||||
// expiring redirect URLs by falling back to the original URL and getting a new redirect.
|
||||
func TestFetcher_DownloadExpiringRedirectURL(t *testing.T) {
|
||||
// Test with redirect expiring after 2 requests
|
||||
// This means:
|
||||
// - Request 1: Resolve (original URL redirects to temp URL v1)
|
||||
// - Request 2: First download request to temp URL v1 succeeds
|
||||
// - Request 3: Second download request to temp URL v1 returns 403
|
||||
// - Fetcher should then retry with original URL, get temp URL v2
|
||||
// - Continue downloading from temp URL v2
|
||||
t.Run("RedirectExpiresAfter2Requests", func(t *testing.T) {
|
||||
os.Remove(test.DownloadFile)
|
||||
|
||||
// Create server with redirect expiring after 2 requests, with slow transfer to ensure
|
||||
// multiple connection attempts are needed
|
||||
listener := test.StartTestExpiringRedirectServer(2, 100*time.Nanosecond)
|
||||
defer listener.Close()
|
||||
|
||||
fetcher := buildFetcher()
|
||||
err := fetcher.Resolve(&base.Request{
|
||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||
}, &base.Options{
|
||||
Name: test.DownloadName,
|
||||
Path: test.Dir,
|
||||
Extra: &http.OptsExtra{
|
||||
Connections: 4, // Use multiple connections to trigger redirect expiration
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = fetcher.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = fetcher.Wait()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify file content is correct despite redirect expiration
|
||||
want := test.FileMd5(test.BuildFile)
|
||||
got := test.FileMd5(test.DownloadFile)
|
||||
if want != got {
|
||||
t.Errorf("Download() got = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
os.Remove(test.DownloadFile)
|
||||
})
|
||||
|
||||
// Test with redirect expiring after 5 requests (more room for initial connections)
|
||||
t.Run("RedirectExpiresAfter5Requests", func(t *testing.T) {
|
||||
os.Remove(test.DownloadFile)
|
||||
|
||||
listener := test.StartTestExpiringRedirectServer(5, 100*time.Nanosecond)
|
||||
defer listener.Close()
|
||||
|
||||
fetcher := buildFetcher()
|
||||
err := fetcher.Resolve(&base.Request{
|
||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||
}, &base.Options{
|
||||
Name: test.DownloadName,
|
||||
Path: test.Dir,
|
||||
Extra: &http.OptsExtra{
|
||||
Connections: 8,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = fetcher.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = fetcher.Wait()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify file content
|
||||
want := test.FileMd5(test.BuildFile)
|
||||
got := test.FileMd5(test.DownloadFile)
|
||||
if want != got {
|
||||
t.Errorf("Download() got = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
os.Remove(test.DownloadFile)
|
||||
})
|
||||
}
|
||||
|
||||
// TestFetcher_RetryAfterError tests that the fetcher can retry downloading
|
||||
// after a previous download attempt failed by calling Start() again.
|
||||
func TestFetcher_RetryAfterError(t *testing.T) {
|
||||
os.Remove(test.DownloadFile)
|
||||
|
||||
// Server fails first 3 requests (after resolve), then recovers
|
||||
// With 1 connection and 3 retries:
|
||||
// - First Start(): requests 2, 3, 4 → all fail (3 retries exhausted) → returns error
|
||||
// - Second Start(): request 5 → succeeds (server recovered after 3 failures)
|
||||
listener := test.StartTestFailThenRecoverServer(3)
|
||||
defer listener.Close()
|
||||
|
||||
fetcher := buildFetcher()
|
||||
err := fetcher.Resolve(&base.Request{
|
||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||
}, &base.Options{
|
||||
Name: test.DownloadName,
|
||||
Path: test.Dir,
|
||||
Extra: &http.OptsExtra{
|
||||
Connections: 1, // Use single connection to simplify test
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// First download attempt - should fail because server returns 416 after resolve
|
||||
err = fetcher.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = fetcher.Wait()
|
||||
// First attempt should fail with 416 error
|
||||
if err == nil {
|
||||
t.Fatal("Expected first download attempt to fail, but it succeeded")
|
||||
}
|
||||
t.Logf("First attempt failed as expected: %v", err)
|
||||
|
||||
// Check that fetcher is in error state
|
||||
state := fetcher.getState()
|
||||
if state != stateError {
|
||||
t.Errorf("Expected fetcher to be in stateError, got %v", state)
|
||||
}
|
||||
|
||||
// Verify that we can call Start() again after error
|
||||
// This tests the stateError handling in Start()
|
||||
err = fetcher.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start() after error failed: %v", err)
|
||||
}
|
||||
|
||||
// Wait for second attempt - should succeed now that server has recovered
|
||||
err = fetcher.Wait()
|
||||
if err != nil {
|
||||
t.Fatalf("Retry failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify file content
|
||||
want := test.FileMd5(test.BuildFile)
|
||||
got := test.FileMd5(test.DownloadFile)
|
||||
if want != got {
|
||||
t.Errorf("Download() got = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
os.Remove(test.DownloadFile)
|
||||
}
|
||||
|
||||
func TestFetcherManager_ParseName(t *testing.T) {
|
||||
type args struct {
|
||||
u string
|
||||
|
||||
Reference in new issue
Block a user