You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vercel部署的Go Serverless函数调用POST接口返回无效请求排查

问题原因
  • 函数入口未校验请求方法
    你当前没有对触发函数的入站请求方法做校验,调用方如果以GET方法请求该函数接口,就会出现日志里显示入站请求为GET的情况。
  • 目标API请求构造错误
    根据OAuth2标准,/connect/token接口要求授权参数放在application/x-www-form-urlencoded格式的请求体中,你当前把所有参数都拼接在了URL查询参数中,且请求体传了nil,完全不符合接口要求,同时敏感参数放在URL查询参数中会被日志系统记录,存在严重的安全泄漏风险。
  • 请求头设置错误
    • 标准HTTP头没有Accepts字段,你应该设置的是Content-Type: application/x-www-form-urlencoded,用来告知服务端当前请求体是表单格式
    • 即使要设置接收类型,正确的头名是Accept,值也需要写完整的MIME类型application/x-www-form-urlencoded
  • 错误处理不符合Serverless规范
    Go的Vercel Serverless函数中禁止直接调用os.Exit(1),该操作会直接终止运行时进程,不仅会导致自定义日志来不及输出,还会让Vercel返回通用的无效请求错误,不会返回你预期的报错信息。
  • 参数获取逻辑错误
    你获取入站parameter参数时,调用了自己刚刚构造的发往目标API的空请求的req.URL.Query().Get,永远取不到传入的参数值,应该读取触发函数的入站请求r的参数。
修复后的代码
import (
    "net/http"
    "net/url"
    "os"
    "io/ioutil"
    "log"
    "strings"
)

func Fetch(w http.ResponseWriter, r *http.Request) {
    // 先校验入站请求方法,仅允许POST调用
    if r.Method != http.MethodPost {
        w.WriteHeader(http.StatusMethodNotAllowed)
        w.Write([]byte("only POST method is allowed"))
        return
    }

    client := &http.Client{}
    // 构造表单参数
    q := url.Values{}
    q.Add("grant_type", "authorization_code")
    q.Add("client_id", os.Getenv("CLIENT_ID"))
    q.Add("client_secret", os.Getenv("CLIENT_SECRET"))
    q.Add("redirect_uri", "https://url.com/callback")
    // 从入站请求中获取parameter参数
    q.Add("parameter", r.URL.Query().Get("parameter"))

    // 构造发往目标接口的POST请求
    req, err := http.NewRequest("POST", "https://auth.truelayer.com/connect/token", nil)
    if err != nil {
        log.Printf("create request failed: %v", err)
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte("internal server error"))
        return
    }
    // 正确设置Content-Type头
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    // 表单内容写入请求体
    req.Body = ioutil.NopCloser(strings.NewReader(q.Encode()))
    // 设置ContentLength避免部分服务端拒绝请求
    req.ContentLength = int64(len(q.Encode()))

    resp, err := client.Do(req)
    if err != nil {
        log.Printf("send request failed: %v", err)
        w.WriteHeader(http.StatusBadGateway)
        w.Write([]byte("failed to call auth service"))
        return
    }
    defer resp.Body.Close()

    respBody, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Printf("read response failed: %v", err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    // 透传响应头和内容
    for k, v := range resp.Header {
        w.Header()[k] = v
    }
    w.WriteHeader(resp.StatusCode)
    w.Write(respBody)
}
额外注意事项
  • 请先确认Vercel环境变量中CLIENT_ID和CLIENT_SECRET已经正确配置
  • 调用该函数的前端请求必须使用POST方法,不要用GET请求调用
  • 不要直接返回原始错误信息到前端,避免泄漏服务端敏感配置

内容的提问来源于stack exchange,提问作者Jon Nicholson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 01:30:06