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

如何用Golang http.Client检测TCP连接状态?解决支付API超时判定问题

区分HTTP请求的TCP连接超时与响应超时问题

问题场景

对接第三方支付API时遇到超时判定歧义:有时请求已被对方接收并处理,但我方系统因超时标记失败。当前代码无法区分TCP连接阶段超时和TCP已建立但响应超时,两类场景统一返回net/http: timeout awaiting response headers错误,无法准确确认交易是否被服务器接收。

当前代码核心问题

现有http.Client配置中,连接超时(Dial.Timeout)与响应头超时(ResponseHeaderTimeout)的错误被合并返回,无法通过错误类型直接区分超时发生的阶段。

解决方案

通过自定义Dialer钩子记录TCP连接状态,同时拆分错误捕获逻辑,精准区分不同阶段的超时:

步骤1:实现带连接状态标记的自定义Dialer

import (
    "net"
    "sync/atomic"
    "time"
)

// 原子变量标记TCP连接是否建立(保证并发安全)
var connEstablished uint32

// 自定义Dial函数,连接成功时标记状态
func customDial(network, address string) (net.Conn, error) {
    dialer := &net.Dialer{
        Timeout:   10 * time.Second,  // 连接阶段超时时间
        KeepAlive: 10 * time.Second,
    }
    conn, err := dialer.Dial(network, address)
    if err == nil {
        // 连接成功,原子更新状态标记
        atomic.StoreUint32(&connEstablished, 1)
    }
    return conn, err
}

步骤2:配置客户端并执行请求,区分超时阶段

import (
    "fmt"
    "net/http"
    "net/url"
    "strings"
)

func main() {
    // 每次请求前重置连接状态标记
    atomic.StoreUint32(&connEstablished, 0)

    client := &http.Client{
        Transport: &http.Transport{
            Dial:                  customDial,
            TLSHandshakeTimeout:   10 * time.Second,  // TLS握手超时
            ResponseHeaderTimeout: 10 * time.Second,  // 响应头返回超时
        },
    }

    urlStr := "http://192.168.7.154:8000/"
    method := "GET"

    params := url.Values{
        "username": {"TEST"},
    }

    payload := strings.NewReader(params.Encode())
    req, err := http.NewRequest(method, urlStr, payload)
    if err != nil {
        fmt.Println("请求初始化错误:", err)
        return
    }

    startTime := time.Now().UnixMicro()
    _, err = client.Do(req)
    processTime := time.Now().UnixMicro() - startTime

    if err != nil {
        currentConnStatus := atomic.LoadUint32(&connEstablished)
        if currentConnStatus == 1 {
            fmt.Printf("TCP连接已建立,但响应超时 | 耗时: %d微秒 | 错误: %v\n", processTime, err)
            // 此时交易大概率已被服务器接收,需调用支付平台查询接口确认状态
        } else {
            fmt.Printf("TCP连接阶段超时 | 耗时: %d微秒 | 错误: %v\n", processTime, err)
            // 交易未被服务器接收,可安全重试请求
        }
        return
    }

    // 请求成功后的处理逻辑(示例)
    fmt.Printf("请求成功 | 耗时: %d微秒\n", processTime)
}

关键说明

  • 并发安全的状态标记:使用atomic包操作状态变量,避免多请求场景下的状态混乱
  • 超时阶段区分:
    • 若connEstablished为1,说明超时发生在响应阶段,交易已送达服务器,不能直接重试,必须通过查询接口确认最终状态
    • 若标记为0,说明超时发生在TCP连接阶段,交易未被服务器接收,可直接重试
  • 超时边界清晰:保持各阶段超时配置独立,明确连接、TLS握手、响应头返回的时间限制

额外建议(支付场景专属)

仅靠连接状态判断仍存在风险,需配合以下机制保障交易准确性:

  • 实现幂等请求:为每个交易生成唯一标识,确保重复请求不会触发重复扣费
  • 强制查询机制:超时后必须调用支付平台的交易查询接口,以查询结果作为交易最终状态依据

内容的提问来源于stack exchange,提问作者Lezir Opav

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 23:50:11