fish2018/pansou · error
[ ] 创建详情页请求失败
Error message
[%s] 创建详情页请求失败: %w
What it means
Lou1Plugin.fetchDetail builds a GET http.Request via http.NewRequestWithContext for a detail page URL; this error wraps any failure from that constructor. NewRequestWithContext only fails when the HTTP method is invalid, the URL cannot be parsed, or the URL scheme is unsupported (http/https only). In this plugin the method is a constant GET, so in practice the URL string itself is malformed.
Solutions
- Log the wrapped %w error and the exact detailURL value to see the url.Parse failure reason
- Validate the detail URL before calling fetchDetail: require an absolute http(s) URL (url.ParseRequestURI + scheme check)
- Fix URL construction so thread_url is resolved against baseURL with net/url ResolveReference, and skip hits with empty/invalid URLs
Example fix
// before
req, err := http.NewRequestWithContext(ctx, http.MethodGet, detailURL, nil)
// after
u, perr := url.Parse(detailURL)
if perr != nil || (u.Scheme != "http" && u.Scheme != "https") {
return detailResult{}, fmt.Errorf("invalid detail url %q", detailURL)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) Defensive patterns
Strategy: validation
Validate before calling
u, err := url.Parse(detailURL)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
// skip this result instead of calling fetchDetail
return skip
} Type guard
func isValidHTTPURL(raw string) bool {
u, err := url.Parse(raw)
return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != ""
} Try / catch
if err != nil {
var uerr *url.Error
if errors.As(err, &uerr) {
log.Printf("bad detail url %q: %v", detailURL, uerr)
}
return detailResult{}, fmt.Errorf("...")
} Prevention
- Resolve relative thread_url values against baseURL with url.Parse and ResolveReference before fetching
- Skip search hits whose URL is empty or fails validation rather than passing them downstream
- Add a unit test covering malformed thread_url inputs
When it happens
Trigger: detailURL is not a parseable absolute URL (empty string, missing scheme, contains spaces or control characters), or has a scheme other than http/https. The method is always http.MethodGet here so method validation never fails.
Common situations: A search hit returns a relative or empty thread_url that is joined into detailURL incorrectly; upstream changed its thread_url format; a redirect target with an unusual scheme is followed into the detail fetch.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/d65a0cb30e5ba662.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/lou1/lou1.go:263
type lou1SearchHit struct {
Subject string `json:"subject"`
ThreadURL string `json:"thread_url"`
}
type detailResult struct {
links []model.Link
datetime time.Time
tags []string
description string
}
func (p *Lou1Plugin) fetchDetail(client *http.Client, detailURL string) (detailResult, error) {
ctx, cancel := context.WithTimeout(context.Background(), detailTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, detailURL, nil)
if err != nil {
return detailResult{}, fmt.Errorf("[%s] 创建详情页请求失败: %w", p.Name(), err)
}
setHTMLHeaders(req, baseURL)
resp, err := p.doRequestWithRetry(req, client, maxRequestRetries)
if err != nil {
return detailResult{}, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return detailResult{}, fmt.Errorf("[%s] 详情页返回状态码: %d", p.Name(), resp.StatusCode)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return detailResult{}, fmt.Errorf("[%s] 解析详情页失败: %w", p.Name(), err)
}
View on GitHub (pinned to beaa561337)