fish2018/pansou · error

[ ] 创建请求失败

Error message

[%s] 创建请求失败: %w

What it means

ClmaoPlugin.searchPage fails while constructing the outbound http.Request via http.NewRequestWithContext before any network I/O happens. This wraps a Go standard-library error such as a malformed URL (net/url.Parse error), invalid method, or a bad body passed to NewRequest. The request context with TimeoutSeconds is created just before, so context creation itself is not the cause.

Solutions

  1. Print/inspect the wrapped error — it names the exact URL parse problem.
  2. Ensure the keyword is escaped before interpolation: url.QueryEscape(keyword) or use url.Values{"q": {keyword}}.Encode().
  3. Validate searchURL with url.Parse and log it (sanitized) before creating the request.
  4. Fix the base URL constant if the site changed domains or paths.

Example fix

// before
searchURL := fmt.Sprintf("https://example.com/search?q=%s", keyword)
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
// after
searchURL := fmt.Sprintf("https://example.com/search?q=%s", url.QueryEscape(keyword))
if _, perr := url.Parse(searchURL); perr != nil {
    return nil, fmt.Errorf("invalid search url: %w", perr)
}
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
Defensive patterns

Strategy: validation

Validate before calling

func validSearchURL(base string, keyword string) (string, error) {
    if strings.TrimSpace(keyword) == "" { return "", errors.New("empty keyword") }
    u := fmt.Sprintf("%s?q=%s", base, url.QueryEscape(keyword))
    if _, err := url.Parse(u); err != nil { return "", fmt.Errorf("invalid url: %w", err) }
    return u, nil
}

Try / catch

results, err := p.searchPage(client, keyword, page)
if err != nil {
    if strings.Contains(err.Error(), "创建请求失败") {
        // URL construction problem: fix keyword escaping/base URL, not retryable
        return nil, fmt.Errorf("bad request construction: %w", err)
    }
    return nil, err
}

Prevention

When it happens

Trigger: searchPage builds searchURL (derived from the keyword) and calls http.NewRequestWithContext(ctx, "GET", searchURL, nil). If searchURL is empty, contains characters invalid for a URL, or the keyword was not escaped, url.Parse fails and the error is wrapped as "[clmao] 创建请求失败: %w".

Common situations: Keyword containing spaces, CJK characters, or special characters (&, #, %) that were not url.QueryEscape'd into searchURL; a configuration change broke the base URL constant; programmatic construction of the URL from an empty/unvalidated keyword.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/c72e5c6a050dceaf. Report an issue: GitHub.

Appendix: source

Thrown at plugin/clmao/clmao.go:179

}

// searchPage 搜索指定页面
func (p *ClmaoPlugin) searchPage(client *http.Client, keyword string, page int) ([]model.SearchResult, error) {
	// clm64 encodes the keyword as base64 and uses query-style pagination.
	encodedKeyword := base64.StdEncoding.EncodeToString([]byte(keyword))
	searchURL := fmt.Sprintf("%s/search?word=%s&sort=time", BaseURL, url.QueryEscape(encodedKeyword))
	if page > 1 {
		searchURL += fmt.Sprintf("&p=%d", page)
	}

	// 创建带超时的上下文
	ctx, cancel := context.WithTimeout(context.Background(), TimeoutSeconds*time.Second)
	defer cancel()

	// 创建请求
	req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建请求失败: %w", p.Name(), err)
	}

	// 设置请求头
	p.setRequestHeaders(req)

	// 发送HTTP请求
	resp, err := p.doRequestWithRetry(req, client)
	if err != nil {
		return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
	}
	defer resp.Body.Close()

	// 检查状态码
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[%s] 请求返回状态码: %d", p.Name(), resp.StatusCode)
	}

	// 读取响应体内容

View on GitHub (pinned to beaa561337)