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

  1. 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.
  2. Pre-validate the URL with url.Parse on baseURL+searchPath and fail fast with a clear config error.
  3. 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

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)