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

  1. Log the wrapped %w error and the exact detailURL value to see the url.Parse failure reason
  2. Validate the detail URL before calling fetchDetail: require an absolute http(s) URL (url.ParseRequestURI + scheme check)
  3. 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

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)