flipped-aurora/gin-vue-admin · error

上游接口返回状态码 %d: %s

Error message

上游接口返回状态码 %d: %s

What it means

fetchPublicUpstream treats any upstream HTTP status >= 400 as an error and includes the status code plus the raw response body in the message. This is not a Go error wrap — it reflects the GVA main service rejecting the request (auth failure, validation error, 404, 500, etc.).

Source

Thrown at server/mcp/http_client.go:129

	req, err := http.NewRequestWithContext(timeoutCtx, http.MethodGet, upstreamURL(path), nil)
	if err != nil {
		return nil, fmt.Errorf("构建请求失败: %w", err)
	}
	req.Header.Set("Accept", "application/json")

	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)
	}
	if resp.StatusCode >= http.StatusBadRequest {
		return nil, fmt.Errorf("上游接口返回状态码 %d: %s", resp.StatusCode, string(rawBody))
	}

	var result upstreamEnvelope[T]
	if err := json.Unmarshal(rawBody, &result); err != nil {
		return nil, fmt.Errorf("解析响应失败: %w", err)
	}
	if result.Code != 0 {
		return nil, fmt.Errorf("上游接口业务错误: %s", result.Msg)
	}
	return &result, nil
}

func getUpstream[T any](ctx context.Context, endpoint string, query url.Values) (*upstreamEnvelope[T], error) {
	return doUpstream[T](ctx, http.MethodGet, endpoint, query, nil)
}

func postUpstream[T any](ctx context.Context, endpoint string, body any) (*upstreamEnvelope[T], error) {
	return doUpstream[T](ctx, http.MethodPost, endpoint, nil, body)

View on GitHub (pinned to 3136500ef3)

Solutions

  1. Read the status code and body snippet in the message — it names the exact rejection reason
  2. 401/403: refresh or re-acquire the auth token and check the user's Casbin policies
  3. 404: compare the endpoint path against the actual GVA route registration
  4. 500: check GVA server logs at the corresponding endpoint for the panic/error
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: verify token validity
resp, err := getUpstream[map[string]any](ctx, "/api/user/userInfo", nil)
if err != nil && strings.Contains(err.Error(), "状态码 401") {
	// token expired — re-authenticate before real calls
}

Type guard

func isUpstreamStatusError(err error) (code int, body string, ok bool) {
	m := regexp.MustCompile(`状态码 (\d{3}): (.*)`).FindStringSubmatch(err.Error())
	if m == nil {
		return 0, "", false
	}
	c, _ := strconv.Atoi(m[1])
	return c, m[2], true
}

Try / catch

data, err := getUpstream[Item](ctx, "/api/item", q)
if err != nil {
	if code, body, ok := isUpstreamStatusError(err); ok {
		switch {
		case code == 401 || code == 403:
			// refresh token / report permission problem
		case code == 404:
			// fix endpoint path
		default:
			log.Printf("upstream %d: %s", code, body)
		}
	}
	return err
}

Prevention

When it happens

Trigger: Expired or invalid auth token sent in the upstream auth header; endpoint path wrong causing 404; request payload failing server validation causing 400; upstream panic causing 500.

Common situations: Token in context expired between MCP login and call; upstreamBaseURL pointing to a wrong port/path; API version mismatch between MCP tool endpoints and server routes; Casbin denying the MCP user's role.

Related errors


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