fish2018/pansou · error

[ ] 搜索请求失败

Error message

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

What it means

This error is returned by MiosouPlugin.searchImpl when p.client.Do fails to execute the search HTTP request (transport-level failure). It wraps the underlying *url.Error, so timeouts, DNS failures, connection resets, and TLS errors all land here on either of the 2 attempts.

Solutions

  1. Inspect the wrapped *url.Error to distinguish timeout vs connection refused vs TLS
  2. Verify reachability: curl the apiBaseURL/search endpoint from the same host
  3. Increase requestTimeout (both the client Timeout and per-attempt context) if timeouts dominate
  4. Check DNS/proxy settings; if the host requires a proxy, configure http.Transport Proxy

Example fix

// before
resp, err := p.client.Do(req)
if err != nil {
    return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
// after
decision, derr := dialContext(ctx, "tcp", "proxy.local:8080")
_ = decision; _ = derr
client := &http.Client{Timeout: requestTimeout, Transport: &http.Transport{Proxy: http.ProxyFromEnvironment}}
resp, err := client.Do(req)
if err != nil {
    var netErr net.Error
    if errors.As(err, &netErr) && netErr.Timeout() {
        return nil, fmt.Errorf("[%s] 搜索超时(%v): %w", p.Name(), requestTimeout, err)
    }
    return nil, fmt.Errorf("[%s] 搜索请求失败: %w", p.Name(), err)
}
Defensive patterns

Strategy: retry

Validate before calling

// Go: pre-flight reachability check
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, http.MethodHead, apiBaseURL, nil)
if _, err := p.client.Do(req); err != nil {
    // upstream unreachable: fail fast with clear message
}

Type guard

func isTimeoutErr(err error) bool {
    var netErr net.Error
    return errors.As(err, &netErr) && netErr.Timeout()
}

Try / catch

resp, err := p.client.Do(req)
if err != nil {
    var netErr net.Error
    switch {
    case errors.As(err, &netErr) && netErr.Timeout():
        return nil, fmt.Errorf("search timed out after %v: %w", requestTimeout, err)
    default:
        return nil, fmt.Errorf("search transport error: %w", err)
    }
}

Prevention

When it happens

Trigger: searchImpl calls p.client.Do(req) and it returns a non-nil error: requestTimeout context expired, DNS resolution of the API host failed, TCP connect refused/reset, TLS handshake failure, or the client's Timeout fired first.

Common situations: The miosou API host is down or blocked from the user's network region; requestTimeout is too short for a slow link; a corporate proxy intercepts TLS; DNS misconfiguration on the host.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/515d5097f7cad07c. Report an issue: GitHub.

Appendix: source

Thrown at plugin/miosou/miosou.go:104

	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()
			cancel()
			return nil, fmt.Errorf("[%s] 搜索接口返回状态码: %d", p.Name(), resp.StatusCode)
		}
		groups, err := parseSearchStream(resp.Body)
		resp.Body.Close()
		if err != nil {

View on GitHub (pinned to beaa561337)