fish2018/pansou · error

[ ] 创建请求失败

Error message

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

What it means

The xinjuc plugin's searchImpl failed to construct the outbound GET request via http.NewRequestWithContext. Despite the name, this API rarely fails: it only returns an error for an unparsable/invalid URL (missing or bad scheme, invalid characters) or an invalid context. The plugin wraps that error with its plugin name for identification. Since the URL is built from SiteURL plus a query-escaped keyword, a bad SiteURL constant is the realistic cause.

Solutions

  1. Print/log the final searchURL and validate it with url.Parse — check for a missing scheme or host.
  2. Fix the SiteURL constant to a full absolute URL including https:// scheme.
  3. Test url.QueryEscape(keyword) output for the offending keyword; strip control characters from user input.
  4. Never pass a nil context; use context.Background() or a timeout context as the code already does.
  5. Add a startup-time sanity check that validates SiteURL parses as an absolute URL.

Example fix

// before
searchURL := fmt.Sprintf("%s/?s=%s", SiteURL, url.QueryEscape(keyword))
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
if err != nil {
    return nil, fmt.Errorf("[%s] 创建请求失败: %w", p.Name(), err)
}
// after
searchURL := fmt.Sprintf("%s/?s=%s", SiteURL, url.QueryEscape(keyword))
if u, perr := url.Parse(searchURL); perr != nil || u.Scheme == "" || u.Host == "" {
    return nil, fmt.Errorf("[%s] 无效的搜索URL: %q", p.Name(), searchURL)
}
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
if err != nil {
    return nil, fmt.Errorf("[%s] 创建请求失败: %w", p.Name(), err)
}
Defensive patterns

Strategy: validation

Validate before calling

// Go: validate the request target before building the request
u, err := url.Parse(fmt.Sprintf("%s/?s=%s", SiteURL, url.QueryEscape(keyword)))
if err != nil {
    return fmt.Errorf("bad search url: %w", err)
}
if u.Scheme != "http" && u.Scheme != "https" {
    return fmt.Errorf("search url missing scheme: %q", u.String())
}
if u.Host == "" {
    return fmt.Errorf("search url missing host: %q", u.String())
}

Type guard

// Go: guard helper for request construction
func newValidGetRequest(ctx context.Context, rawURL string) (*http.Request, error) {
    if ctx == nil {
        return nil, fmt.Errorf("nil context")
    }
    u, err := url.Parse(rawURL)
    if err != nil || u.Host == "" || (u.Scheme != "http" && u.Scheme != "https") {
        return nil, fmt.Errorf("invalid absolute URL: %q", rawURL)
    }
    return http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
}

Try / catch

req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
if err != nil {
    // request construction errors are deterministic — do not retry, log and skip plugin
    log.Printf("[%s] skipping search, bad request: %v", "xinjuc", err)
    return nil, fmt.Errorf("[%s] 创建请求失败: %w", "xinjuc", err)
}

Prevention

When it happens

Trigger: Calling Search/SearchWithResult on the xinjuc plugin when the constructed searchURL (SiteURL + "/?s=" + escaped keyword) is not a valid absolute HTTP URL — e.g. SiteURL is empty, missing the scheme ("example.com" instead of "https://example.com"), contains spaces or control characters — or ctx passed to NewRequestWithContext is nil.

Common situations: SiteURL constant misconfigured after a site move; config value substituted without scheme; keyword escaping leaving control characters in the URL (rare since url.QueryEscape is used); programmatic refactoring that passes a nil context.

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/19c6da4caa459284. Report an issue: GitHub.

Appendix: source

Thrown at plugin/xinjuc/xinjuc.go:129

// SearchWithResult 执行搜索并返回包含IsFinal标记的结果
func (p *XinjucPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (model.PluginSearchResult, error) {
	return p.AsyncSearchWithResult(keyword, p.searchImpl, p.MainCacheKey, ext)
}

// searchImpl 实现具体的搜索逻辑
func (p *XinjucPlugin) searchImpl(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
	// 1. 构建搜索URL
	searchURL := fmt.Sprintf("%s/?s=%s", SiteURL, url.QueryEscape(keyword))
	
	// 2. 创建带超时的上下文
	ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
	defer cancel()
	
	// 3. 创建请求
	req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建请求失败: %w", p.Name(), err)
	}
	
	// 4. 设置完整的请求头(避免反爬虫)
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Referer", SiteURL)
	
	// 5. 发送请求(带重试机制)
	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)