fish2018/pansou · error

发送搜索请求失败

Error message

发送搜索请求失败: %w

What it means

This error wraps any failure from the HTTP client when the xiaozhang plugin's searchImpl sends its GET request to the site's search URL via doRequest. It is a transport-level failure: DNS resolution, TCP connect, TLS handshake, timeout, or redirect-policy errors are all wrapped here with %w so the root cause is preserved. The plugin throws it because without an HTTP response there is nothing to parse for search results.

Solutions

  1. Inspect the wrapped cause with errors.Unwrap/%v of the returned error to see whether it is DNS, timeout, or TLS.
  2. Verify network connectivity to the search host (curl the searchURL from the same host).
  3. Increase the http.Client Timeout if failures are deadline-exceeded on a slow site.
  4. Check the BaseURL/SearchPath constants against the current live site — the domain may have moved.
  5. If the site requires a proxy, configure HTTP_PROXY/HTTPS_PROXY or a custom http.Transport Proxy.
  6. Add retry with backoff around doRequest for transient network errors.

Example fix

// before
resp, err := p.doRequest(client, searchURL, BaseURL, true)
if err != nil {
    return nil, fmt.Errorf("发送搜索请求失败: %w", err)
}
// after
resp, err := p.doRequestWithRetry(client, searchURL, BaseURL, true, 3) // retry wrapper
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        return nil, fmt.Errorf("发送搜索请求超时: %w", err)
    }
    return nil, fmt.Errorf("发送搜索请求失败: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: pre-check reachability before invoking the plugin search
u, err := url.Parse(searchURL)
if err != nil || u.Scheme == "" || u.Host == "" {
    return fmt.Errorf("invalid search URL: %w", err)
}
conn, err := net.DialTimeout("tcp", net.JoinHostPort(u.Hostname(), portOf(u, "443")), 3*time.Second)
if err != nil {
    return fmt.Errorf("site unreachable: %w", err)
}
conn.Close()

Type guard

// Go: errors-based narrowing of the wrapped cause
func isTimeoutErr(err error) bool {
    var ne net.Error
    return errors.As(err, &ne) && ne.Timeout() || errors.Is(err, context.DeadlineExceeded)
}

Try / catch

results, err := plugin.Search(keyword, ext)
if err != nil {
    if isTimeoutErr(err) {
        // transient network issue: retry once with a longer timeout
        results, err = plugin.Search(keyword, ext)
    }
    if err != nil {
        log.Printf("xiaozhang search failed: %v", err)
        return nil, results // degrade gracefully, other plugins may still answer
    }
}

Prevention

When it happens

Trigger: Calling the xiaozhang plugin Search/SearchWithResult when doRequest's tempClient.Do(req) returns a non-nil error — e.g. the site is unreachable, the configured BaseURL no longer resolves, the client timeout expires, TLS fails, or a redirect loop hits CheckRedirect limits.

Common situations: The target site is down or blocked (firewall/GFW), no network connectivity in the container, DNS record changed or domain expired, client Timeout too short for a slow upstream, or a proxy is required but not configured in the environment.

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

Appendix: source

Thrown at plugin/xiaozhang/xiaozhang.go:146

		log.Printf("[Xiaozhang] 响应状态: %d", resp.StatusCode)
	}
	
	return resp, nil
}

// searchImpl 实际的搜索实现
func (p *XiaozhangPlugin) searchImpl(client *http.Client, keyword string, ext map[string]interface{}) ([]model.SearchResult, error) {
	searchURL := fmt.Sprintf("%s%s?keyword=%s", BaseURL, SearchPath, url.QueryEscape(keyword))
	
	if p.debugMode {
		log.Printf("[Xiaozhang] 开始搜索: %s", keyword)
		log.Printf("[Xiaozhang] 搜索URL: %s", searchURL)
	}
	
	// 发送搜索请求
	resp, err := p.doRequest(client, searchURL, BaseURL, true)
	if err != nil {
		return nil, fmt.Errorf("发送搜索请求失败: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("搜索响应状态码异常: %d", resp.StatusCode)
	}
	
	// 处理响应体(可能是gzip压缩的)
	var reader io.Reader = resp.Body
	
	// 检查Content-Encoding
	contentEncoding := resp.Header.Get("Content-Encoding")
	if p.debugMode {
		log.Printf("[Xiaozhang] Content-Encoding: %s", contentEncoding)
		log.Printf("[Xiaozhang] Content-Type: %s", resp.Header.Get("Content-Type"))
	}
	
	// 如果是gzip压缩,手动解压

View on GitHub (pinned to beaa561337)