fish2018/pansou · error

[ ] 创建搜索请求失败

Error message

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

What it means

This error is returned by MiosouPlugin.searchImpl when http.NewRequestWithContext fails to construct the GET request to apiBaseURL + "/search?keyword=...". With a constant apiBaseURL this almost always means the URL failed to parse (url.Parse error inside NewRequest) or the context is invalid — a programming/config-level problem rather than a network issue.

Solutions

  1. Print the constructed URL and validate it (http.NewRequest manually or url.Parse) to find the malformed part
  2. Check apiBaseURL configuration for stray whitespace, missing scheme, or illegal characters
  3. Ensure the context passed to WithTimeout is fresh, not already canceled
  4. Since this is deterministic, fix the config/code; retrying will not help

Example fix

// before (misconfigured base)
apiBaseURL = "miosou.example.com/api" // missing scheme
// after
apiBaseURL = "https://miosou.example.com/api"
u, err := url.Parse(apiBaseURL)
if err != nil || u.Scheme == "" {
    log.Fatalf("invalid apiBaseURL: %v", err)
}
Defensive patterns

Strategy: validation

Validate before calling

// Go: validate the API URL at startup
u, err := url.Parse(apiBaseURL)
if err != nil || u.Scheme == "" || u.Host == "" {
    return fmt.Errorf("invalid apiBaseURL %q: %v", apiBaseURL, err)
}

Type guard

func validHTTPURL(raw string) bool {
    u, err := url.Parse(raw)
    return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != ""
}

Try / catch

if req, err = http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil); err != nil {
    return nil, fmt.Errorf("constructing search request for %q: %w", reqURL, err)
}

Prevention

When it happens

Trigger: searchImpl builds the request on each of its 2 attempts; NewRequestWithContext returns err when the method or URL is invalid — e.g. apiBaseURL was misconfigured to contain spaces or control characters, or the URL is malformed so url.Parse fails.

Common situations: A build-time misconfiguration of apiBaseURL (bad constant, env override with whitespace or an invalid scheme); a keyword so unusual it breaks URL construction (rare, since url.QueryEscape is applied); passing a nil/already-canceled context from refactored code.

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

Appendix: source

Thrown at plugin/miosou/miosou.go:98

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

func (p *MiosouPlugin) searchImpl(_ *http.Client, keyword string, _ map[string]interface{}) ([]model.SearchResult, error) {
	keyword = strings.TrimSpace(keyword)
	if keyword == "" {
		return nil, nil
	}
	if err := p.ensureGate(); err != nil {
		return nil, err
	}

	for attempt := 0; attempt < 2; attempt++ {
		ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiBaseURL+"/search?keyword="+url.QueryEscape(keyword), nil)
		if err != nil {
			cancel()
			return nil, fmt.Errorf("[%s] 创建搜索请求失败: %w", p.Name(), err)
		}
		setHeaders(req, "text/event-stream")
		resp, err := p.client.Do(req)
		if err != nil {
			cancel()
			return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
		}
		if isAnubisGateResponse(resp) {
			resp.Body.Close()
			cancel()
			p.invalidateGate()
			if err := p.ensureGate(); err != nil {
				return nil, err
			}
			continue
		}
		if resp.StatusCode != http.StatusOK {
			resp.Body.Close()

View on GitHub (pinned to beaa561337)