flipped-aurora/gin-vue-admin · error

请求上游服务失败: %w

Error message

请求上游服务失败: %w

What it means

http.DefaultClient.Do returned a transport-level error (connection refused, DNS failure, TLS error, timeout via the request context); it is wrapped as '请求上游服务失败'. The request never received an HTTP response, so the problem is network/connectivity, not the upstream's answer.

Source

Thrown at server/mcp/http_client.go:203

	timeoutCtx, cancel := context.WithTimeout(ctx, requestTimeout())
	defer cancel()

	req, err := http.NewRequestWithContext(timeoutCtx, method, requestURL.String(), reader)
	if err != nil {
		return nil, fmt.Errorf("创建上游请求失败: %w", err)
	}
	req.Header.Set("Accept", "application/json")
	req.Header.Set(upstreamAuthHeader, token)
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	// 链路传播:外部 AI → MCP 进程 → GVA 主服务串成同一条 trace
	logger.InjectTraceHeaders(timeoutCtx, req)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("请求上游服务失败: %w", err)
	}
	defer resp.Body.Close()

	rawBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("读取上游响应失败: %w", err)
	}

	var result upstreamEnvelope[T]
	if len(rawBody) > 0 {
		if err := json.Unmarshal(rawBody, &result); err != nil {
			// 上游返回非 JSON(如 404/502 的 HTML 或网关错误页):先暴露真实状态码,
			// 不让解析错误掩盖真实的 HTTP 失败
			if resp.StatusCode >= http.StatusBadRequest {
				return nil, fmt.Errorf("上游请求失败,状态码: %d,响应: %s", resp.StatusCode, truncateUpstreamBody(rawBody))
			}
			return nil, fmt.Errorf("解析上游响应失败: %w", err)
		}

View on GitHub (pinned to 3136500ef3)

Solutions

  1. Check the wrapped error: 'connection refused' → upstream down/wrong port; 'no such host' → DNS; 'context deadline exceeded' → timeout
  2. Confirm the GVA server is running and listening at the host:port in upstreamBaseURL
  3. curl the same base URL from the MCP process's network position to reproduce
  4. If it is a timeout, raise requestTimeout() or optimize the slow upstream endpoint
  5. In containers, fix the base URL to the service hostname instead of localhost
Defensive patterns

Strategy: retry

Validate before calling

func upstreamAlive(ctx context.Context, base string) error {
	c, cancel := context.WithTimeout(ctx, 3*time.Second)
	defer cancel()
	req, _ := http.NewRequestWithContext(c, http.MethodGet, strings.TrimRight(base, "/")+"/health", nil)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

Try / catch

result, err := getUpstream[Data](ctx, "/api/x", nil)
if err != nil {
	if strings.Contains(err.Error(), "请求上游服务失败") {
		// classify: refused → down, no such host → DNS, deadline exceeded → timeout
		// apply bounded retry with backoff for transient cases
	}
	return err
}

Prevention

When it happens

Trigger: GVA main service not running or listening on a different port than upstreamBaseURL; firewall/security group blocking; DNS name unresolvable; requestTimeout() elapsed before a response; TLS certificate problems on https base URLs.

Common situations: Dev environment where the backend port changed; upstreamBaseURL pointing to localhost from inside a container (should use host.docker.internal/service name); server overloaded and exceeding the timeout; VPN/proxy required for the upstream address.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of flipped-aurora/gin-vue-admin@3136500ef3 (2026-08-31). Data as JSON: /api/errors/d8d749baba3d25ab. Report an issue: GitHub.