fish2018/pansou · error
[ ] 会话请求失败
Error message
[%s] 会话请求失败: %w
What it means
nsgame's postSessionRaw wraps any error returned by the HTTP client's Do() when POSTing to the session endpoints (/traffic/session/challenge or /traffic/session/issue). It prefixes the plugin name and the Chinese text '会话请求失败' (session request failed) and preserves the underlying error via %w. This means the request never got an HTTP response at all — it failed at the transport level (DNS, TCP, TLS, timeout, or context cancellation).
Solutions
- Check basic connectivity to the site: curl -v https://nsthwj.cn/ from the machine running the plugin.
- Unwrap the error with errors.Unwrap or inspect with errors.As(*url.Error)/net.Error to see the root cause (timeout vs DNS vs TLS).
- Configure a proxy (HTTP_PROXY/HTTPS_PROXY or the plugin's client transport) if the site is unreachable from your network.
- Increase the http.Client Timeout if errors.As reveals context deadline exceeded on slow endpoints.
- Retry with backoff; transient connection resets are common with such sites.
Example fix
// before
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("[%s] 会话请求失败: %w", p.Name(), err)
}
// after
resp, err := client.Do(req)
if err != nil {
var nerr net.Error
if errors.As(err, &nerr) && nerr.Timeout() {
return nil, fmt.Errorf("[%s] 会话请求超时: %w", p.Name(), err)
}
return nil, fmt.Errorf("[%s] 会话请求失败: %w", p.Name(), err)
} Defensive patterns
Strategy: retry
Validate before calling
// preflight
if _, err := net.LookupHost("nsthwj.cn"); err != nil { return fmt.Errorf("DNS 解析失败: %w", err) } Type guard
var nerr net.Error isNetworkTimeout := errors.As(err, &nerr) && nerr.Timeout()
Try / catch
data, err := p.postSessionRaw(client, path, body)
if err != nil {
var nerr net.Error
if errors.As(err, &nerr) && nerr.Timeout() {
// retry with backoff
} else {
return fmt.Errorf("session transport failed: %w", err)
}
} Prevention
- Set a sane http.Client Timeout and retry transport errors with exponential backoff.
- Configure proxy environment variables when the target site is region-blocked.
- Monitor DNS/connectivity from the deployment host before blaming the plugin.
- Reuse a single shared http.Client with keep-alives to avoid stale-connection failures.
When it happens
Trigger: p.postSessionRaw calls client.Do(req) and err != nil: DNS resolution failure for nsthwj.cn, TCP connect failure, TLS handshake failure, context/deadline exceeded, or connection reset before any response bytes arrive. Raised from ensureSession (challenge/issue calls) and postSession.
Common situations: Target site nsthwj.cn is down or blocked (GFW/regional block), no network in a container, missing proxy config, TLS cert issues with a MITM proxy, or the site drops long-lived connections so a stale client fails.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/ea7563e935e07116.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/nsgame/nsgame.go:365
func (p *NSGameAsyncPlugin) postSessionRaw(client *http.Client, path string, body []byte) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
var reader io.Reader
if body != nil {
reader = strings.NewReader(string(body))
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+path, reader)
if err != nil {
return nil, fmt.Errorf("[%s] 创建会话请求失败: %w", p.Name(), err)
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
p.setRequestHeaders(req, "https://nsthwj.cn/")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("[%s] 会话请求失败: %w", p.Name(), err)
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("[%s] 会话返回状态码: %d", p.Name(), resp.StatusCode)
}
return data, nil
}
func solveChallenge(challenge string, difficultyBits int) string {
for nonce := int64(0); nonce < 10000000; nonce++ {
sum := sha256.Sum256([]byte(fmt.Sprintf("%s:%d", challenge, nonce)))
fullBytes, remainder := difficultyBits/8, difficultyBits%8
valid := true
for i := 0; i < fullBytes; i++ {
if sum[i] != 0 {
valid = false
breakView on GitHub (pinned to beaa561337)