fish2018/pansou · error

[ ] 请求失败

Error message

[%s] 请求失败: %w

What it means

searchImpl wraps errors from doJuPansouRequestWithRetry, which performs the actual HTTP call with retries. It fires when the request cannot be delivered at all (connection refused, DNS failure, TLS error, context timeout) after all retry attempts. The underlying cause is preserved via %w.

Solutions

  1. Unwrap the error to see the root cause (timeout vs connection refused vs TLS).
  2. Verify the upstream domain is still alive with curl from the host running the plugin.
  3. Increase jupansouStreamTimeout if the SSE endpoint is slow to respond.
  4. Configure a proxy if the host is unreachable from your network.
  5. Check for rate limiting/IP bans — slow down request frequency or rotate IPs.

Example fix

// before: opaque failure
results, err := p.searchImpl(client, keyword)
// after: diagnose the cause
if err != nil {
    var nerr net.Error
    if errors.As(err, &nerr) && nerr.Timeout() {
        log.Println("jupansou timed out; consider raising jupansouStreamTimeout")
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

u, _ := url.Parse(jupansouBaseURL)
conn, err := net.DialTimeout("tcp", u.Host, 3*time.Second)
if err != nil { return fmt.Errorf("jupansou host unreachable: %w", err) }
conn.Close()

Try / catch

results, err := p.searchImpl(client, keyword)
var nerr net.Error
if err != nil && errors.As(err, &nerr) {
    if nerr.Timeout() {
        log.Println("jupansou request timed out; raising timeout or retrying")
    } else {
        log.Printf("jupansou transport error: %v", nerr)
    }
    return fallbackResults
}

Prevention

When it happens

Trigger: client.Do inside doJuPansouRequestWithRetry fails on every attempt: unreachable host, refused connection, TLS handshake failure, or the jupansouStreamTimeout context expiring.

Common situations: JuPansou API is down or its domain changed; corporate firewall/DNS blocks the host; the streaming endpoint is slower than the configured timeout; missing outbound internet in containers.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at plugin/jupansou/jupansou.go:117

	}
	ctx, cancel := context.WithTimeout(context.Background(), jupansouStreamTimeout)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建请求失败: %w", p.Name(), err)
	}
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36")
	req.Header.Set("Accept", "text/event-stream")
	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", jupansouBaseURL+"/")
	req.Header.Set("Origin", jupansouBaseURL)
	req.Header.Set("X-Requested-With", "XMLHttpRequest")

	resp, err := doJuPansouRequestWithRetry(req, client)
	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] 接口返回状态码: %d", p.Name(), resp.StatusCode)
	}

	items := make([]juPansouStreamItem, 0)
	scanner := bufio.NewScanner(resp.Body)
	scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if !strings.HasPrefix(line, "data:") {
			continue
		}

		payload := strings.TrimSpace(strings.TrimPrefix(line, "data:"))
		if payload == "" || payload == "[DONE]" {

View on GitHub (pinned to beaa561337)