fish2018/pansou · error
[ ] API 搜索请求失败
Error message
[%s] API 搜索请求失败: %w
What it means
The Flarum API request in dyyj.executeSearchAPI failed at the transport level after doRequestWithRetry exhausted retries. Wraps DNS/TCP/TLS errors and context deadline exceeded. Retries have already occurred; this is the terminal failure.
Solutions
- curl -v the same apiURL from the host to classify the failure
- Check DNS resolution of the BaseURL host
- Configure proxy environment variables if egress requires one
- Increase RequestTimeout for slow API responses
- Check whether the site moved/renamed its domain
Example fix
// before
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] API 搜索请求失败: %w", p.Name(), err)
}
// after
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("[%s] API 搜索超时(>%s): %w", p.Name(), RequestTimeout, err)
}
return nil, fmt.Errorf("[%s] API 搜索请求失败: %w", p.Name(), err)
} Defensive patterns
Strategy: retry
Validate before calling
conn, err := net.DialTimeout("tcp", host+":443", 3*time.Second)
if err != nil { return errors.New("API host unreachable") }; conn.Close() Try / catch
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
return nil, fmt.Errorf("API timeout: %w", err)
}
return nil, fmt.Errorf("API transport error: %w", err)
} Prevention
- Keep bounded retries with exponential backoff for transient failures
- Set explicit client and context timeouts
- Verify egress/proxy access to the API host in deployment
- Alert on sustained DNS/connect failures for the target domain
When it happens
Trigger: p.doRequestWithRetry(req, client) errored on GET {BaseURL}/api/discussions?... — DNS failure, connection refused/reset, TLS error, or RequestTimeout exceeded.
Common situations: API host unreachable from the deployment network; site blocked datacenter IPs; API endpoint removed (connection-level symptom less common — usually 404, but domain loss gives DNS errors); proxy required but unset.
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/0def0d9812d8737a.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/dyyj/dyyj.go:423
params.Set("filter[q]", keyword)
params.Set("include", "mostRelevantPost")
params.Set("page[limit]", fmt.Sprintf("%d", MaxResults))
apiURL := BaseURL + "/api/discussions?" + params.Encode()
ctx, cancel := context.WithTimeout(context.Background(), RequestTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)
if err != nil {
return nil, fmt.Errorf("[%s] 创建 API 请求失败: %w", p.Name(), err)
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Accept", "application/vnd.api+json, application/json")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
req.Header.Set("Referer", BaseURL+"/")
resp, err := p.doRequestWithRetry(req, client)
if err != nil {
return nil, fmt.Errorf("[%s] API 搜索请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
var payload dyyjAPIResponse
if err := encodingjson.NewDecoder(resp.Body).Decode(&payload); err != nil {
return nil, fmt.Errorf("[%s] 解析 API 响应失败: %w", p.Name(), err)
}
posts := make(map[string]dyyjIncluded, len(payload.Included))
for _, post := range payload.Included {
posts[post.ID] = post
}
results := make([]model.SearchResult, 0, len(payload.Data))
for _, discussion := range payload.Data {
post, ok := posts[discussion.Relationships.MostRelevantPost.Data.ID]
if !ok {
continueView on GitHub (pinned to beaa561337)