fish2018/pansou · error
[ ] 创建搜索请求失败
Error message
[%s] 创建搜索请求失败: %w
What it means
fetchSearch wraps any error returned by http.NewRequestWithContext with this message, prefixed by the plugin name. The request construction itself failed before any network I/O occurred — most commonly because baseURL+searchPath does not parse as a valid URL. This is a local construction error, not a network problem.
Solutions
- Verify the plugin's configured baseURL starts with a valid scheme and host (e.g. https://example.com) and has no stray spaces; trim it before use.
- Pre-validate the URL with url.Parse on baseURL+searchPath and fail fast with a clear config error.
- Check that searchPath is a well-formed constant path and no config value is being concatenated into it.
Example fix
// before
baseURL := cfg["baseURL"] // may be " example.com"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+searchPath, strings.NewReader(form))
// after
baseURL := strings.TrimSpace(cfg["baseURL"])
if u, perr := url.Parse(baseURL); perr != nil || u.Scheme == "" || u.Host == "" {
return nil, fmt.Errorf("invalid baseURL %q", baseURL)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+searchPath, strings.NewReader(form)) Defensive patterns
Strategy: validation
Validate before calling
u, err := url.Parse(baseURL + searchPath)
if err != nil || u.Scheme == "" || u.Host == "" {
return fmt.Errorf("invalid search URL: %q", baseURL+searchPath)
} Try / catch
if err != nil {
return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
} Prevention
- Always configure baseURL with scheme and host, trimmed of whitespace
- Validate config URLs at plugin startup, not per request
- Keep searchPath as a tested constant
When it happens
Trigger: http.NewRequestWithContext(ctx, http.MethodPost, baseURL+searchPath, strings.NewReader(form)) returns an error — i.e. the combined URL fails url.Parse (malformed scheme/host, control characters or spaces in baseURL, empty baseURL producing a non-absolute URL).
Common situations: A misconfigured baseURL in the plugin config (missing scheme like 'http://', trailing garbage, or an empty value), or a baseURL containing whitespace/newlines read from an env var or config file without trimming.
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/04e6019e9db0cfc2.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/dygang/dygang.go:175
mu.Unlock()
}
}()
}
wg.Wait()
return plugin.FilterResultsByKeyword(results, keyword), nil
}
func (p *Plugin) fetchSearch(client *http.Client, keyword string) (*goquery.Document, error) {
encoded, err := encodeGB18030(keyword)
if err != nil {
return nil, fmt.Errorf("[%s] 编码搜索关键词失败: %w", p.Name(), err)
}
form := "tempid=1&tbname=article&keyboard=" + url.QueryEscape(string(encoded)) + "&show=title%2Csmalltext&Submit="
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+searchPath, strings.NewReader(form))
if err != nil {
return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
}
setHeaders(req, baseURL+"/")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
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, 4<<20))
if err != nil {
return nil, fmt.Errorf("[%s] 读取搜索结果失败: %w", p.Name(), err)
}
decoded, err := decodeGB18030(body)
if err != nil {
return nil, fmt.Errorf("[%s] 解码搜索结果失败: %w", p.Name(), err)View on GitHub (pinned to beaa561337)