Improve checker/fixer cohesion and harden workflows
This commit is contained in:
@@ -25,6 +25,15 @@ type LinkResult struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
func shouldFallbackToGET(statusCode int) bool {
|
||||
switch statusCode {
|
||||
case http.StatusBadRequest, http.StatusForbidden, http.StatusMethodNotAllowed, http.StatusNotImplemented:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// CheckLink checks a single URL. Uses HEAD first, falls back to GET.
|
||||
func CheckLink(url string, client *http.Client) LinkResult {
|
||||
result := LinkResult{URL: url}
|
||||
@@ -32,14 +41,6 @@ func CheckLink(url string, client *http.Client) LinkResult {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Try HEAD first
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
return result
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
// Track redirects
|
||||
var finalURL string
|
||||
origCheckRedirect := client.CheckRedirect
|
||||
@@ -52,16 +53,25 @@ func CheckLink(url string, client *http.Client) LinkResult {
|
||||
}
|
||||
defer func() { client.CheckRedirect = origCheckRedirect }()
|
||||
|
||||
resp, err := client.Do(req)
|
||||
doRequest := func(method string) (*http.Response, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, method, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
resp, err := doRequest(http.MethodHead)
|
||||
if err != nil {
|
||||
// Fallback to GET
|
||||
req, err2 := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err2 != nil {
|
||||
resp, err = doRequest(http.MethodGet)
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
return result
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
resp, err = client.Do(req)
|
||||
} else if shouldFallbackToGET(resp.StatusCode) {
|
||||
resp.Body.Close()
|
||||
resp, err = doRequest(http.MethodGet)
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
return result
|
||||
|
||||
@@ -78,3 +78,41 @@ func TestCheckLinks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLinkFallbackToGETOnMethodNotAllowed(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodHead {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
result := CheckLink(server.URL, &http.Client{})
|
||||
if !result.OK {
|
||||
t.Errorf("expected OK after GET fallback, got status %d, error: %s", result.StatusCode, result.Error)
|
||||
}
|
||||
if result.StatusCode != http.StatusOK {
|
||||
t.Errorf("status = %d, want 200", result.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLinkFallbackToGETOnForbiddenHead(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodHead {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
result := CheckLink(server.URL, &http.Client{})
|
||||
if !result.OK {
|
||||
t.Errorf("expected OK after GET fallback, got status %d, error: %s", result.StatusCode, result.Error)
|
||||
}
|
||||
if result.StatusCode != http.StatusOK {
|
||||
t.Errorf("status = %d, want 200", result.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user