fish2018/pansou · error

[ ] 创建搜索请求失败

Error message

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

What it means

This error is returned by HaitunsouPlugin.searchImpl when http.NewRequestWithContext fails to construct the GET request against the plugin's configured base URL plus the URL-escaped search keyword. In practice http.NewRequestWithContext only fails when the target URL fails url.Parse (invalid control characters, malformed URL), so it almost always indicates a bad baseURL configuration or a keyword containing characters that survived PathEscape and produced an unparseable URL. The original parse error is wrapped with %w for errors.Is/As inspection.

Solutions

  1. Inspect the wrapped error with errors.As(*url.Error) to see the exact URL parse failure and fix the baseURL configuration value
  2. Ensure baseURL comes from a validated source; validate with url.Parse(p.baseURL) at plugin construction time
  3. Sanitize the keyword of control characters before searchImpl builds the URL
  4. Fall back to the defaultBaseURL if the configured one fails validation

Example fix

// before
searchURL := fmt.Sprintf("%s/s/%s.html", strings.TrimRight(p.baseURL, "/"), url.PathEscape(keyword))
// after
if _, err := url.Parse(p.baseURL); err != nil {
    return nil, fmt.Errorf("[%s] invalid baseURL: %w", p.Name(), err)
}
searchURL := fmt.Sprintf("%s/s/%s.html", strings.TrimRight(p.baseURL, "/"), url.PathEscape(keyword))
Defensive patterns

Strategy: validation

Validate before calling

if _, err := url.Parse(strings.TrimSpace(p.baseURL)); err != nil {
    return nil, fmt.Errorf("baseURL invalid: %w", err)
}
if strings.ContainsFunc(keyword, func(r rune) bool { return r < 0x20 }) {
    return nil, errors.New("keyword contains control characters")
}

Try / catch

if _, err := plugin.Search(kw, nil); err != nil {
    var urlErr *url.Error
    if errors.As(err, &urlErr) { /* bad URL: fix config, don't retry */ }
}

Prevention

When it happens

Trigger: Calling Search/SearchWithResult when p.baseURL is malformed (e.g. configured with a space, control character, or invalid scheme such as 'htp:/') so that fmt.Sprintf produces an unparseable URL, or when the cleaned keyword contains raw control bytes that PathEscape leaves invalid for a URL.

Common situations: Deployments overriding the default https://www.haitunsou.com base URL via config with a typo, trailing garbage, or an env var containing whitespace; unusual keywords with embedded newline/control characters after cleanText.

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/399a4cdb95799ab5. Report an issue: GitHub.

Appendix: source

Thrown at plugin/haitunsou/haitunsou.go:91

func (p *HaitunsouPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (model.PluginSearchResult, error) {
	return p.AsyncSearchWithResult(keyword, p.searchImpl, p.MainCacheKey, ext)
}

func (p *HaitunsouPlugin) searchImpl(client *http.Client, keyword string, _ map[string]interface{}) ([]model.SearchResult, error) {
	keyword = cleanText(keyword)
	if keyword == "" {
		return []model.SearchResult{}, nil
	}
	if client == nil {
		client = http.DefaultClient
	}

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	defer cancel()
	searchURL := fmt.Sprintf("%s/s/%s.html", strings.TrimRight(p.baseURL, "/"), url.PathEscape(keyword))
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
	}
	setRequestHeaders(req, p.baseURL)

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("[%s] 搜索请求返回 HTTP %d", p.Name(), resp.StatusCode)
	}
	body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1))
	if err != nil {
		return nil, fmt.Errorf("[%s] 读取搜索响应失败: %w", p.Name(), err)
	}
	if len(body) > maxResponseSize {
		return nil, fmt.Errorf("[%s] 搜索响应超过 %d 字节", p.Name(), maxResponseSize)
	}

View on GitHub (pinned to beaa561337)