[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-strapi-article:zh:ai-api-test-evidence-data-redaction":3,"blog-article:\u002Fblog\u002Fai-api-test-evidence-data-redaction":928,"blog-related:\u002Fblog\u002Fai-api-test-evidence-data-redaction":929,"blog-translations:\u002Fblog\u002Fai-api-test-evidence-data-redaction":930},{"available":4,"article":5,"relatedArticles":200,"translations":795},true,{"source":6,"id":7,"documentId":8,"translationKey":8,"locale":9,"slug":10,"path":11,"_path":11,"title":12,"description":13,"excerpt":13,"image":14,"publishedAt":15,"updatedAt":16,"author":17,"tags":18,"featured":19,"sortOrder":20,"readingTime":21,"bodyType":22,"bodyBlocks":23,"bodyHtml":199},"strapi",3889,"s1asiwhvad5gsee1qguk5w0e","zh","ai-api-test-evidence-data-redaction","\u002Fblog\u002Fai-api-test-evidence-data-redaction","AI API 地区测试中，如何留存证据并保护密钥与业务数据","代理 IP 地区测试如何留证又保护密钥和业务数据？本文说明记录字段、脱敏范围、留存边界及疑似泄露后的处置顺序，并给出可直接照做的脱敏检查。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_AI_API_cover_882906df7c.webp","2026-11-03T02:56:49.106Z","2026-11-03T02:56:49.088Z","Global Proxies Team",[],false,0,2,"blocks",[24,30,35,39,60,64,68,72,90,94,98,102,116,120,124,128,132,136,140,144,148,152,156,160,164,168,175,179,185,189,195],{"type":25,"children":26},"paragraph",[27],{"text":28,"type":29},"地区测试要留下能复现的证据，但证据里最容易夹带密钥、用户数据和业务内容。原则是先分类再采集：只留判断归因必需的字段，能合成的一律不用真实数据，密钥只在运行时注入。代理 IP 的主机、端口和账号属于接入参数，可以留结构，不能留明文口令。示例中的字段名与脱敏方式按通用实践整理，具体合规要求以所在组织与目标服务的条款为准。","text",{"type":31,"level":21,"children":32},"heading",[33],{"text":34,"type":29},"测试记录应该保留哪些信息、隐藏哪些信息？",{"type":25,"children":36},[37],{"text":38,"type":29},"为便于复现又控制泄露面，记录时区分必要字段与敏感内容：",{"type":40,"format":41,"children":42},"list","unordered",[43,48,52,56],{"type":44,"children":45},"list-item",[46],{"text":47,"type":29},"类别：请求 ｜ 应当保留：URL 路径、方法、状态码、耗时、重试次数 ｜ 必须删除或替换：Authorization、Cookie、API key",{"type":44,"children":49},[50],{"text":51,"type":29},"类别：响应 ｜ 应当保留：状态码、错误类型、上游 request id、限流 header ｜ 必须删除或替换：完整响应正文、模型输出内容",{"type":44,"children":53},[54],{"text":55,"type":29},"类别：身份 ｜ 应当保留：出口 IP 的国家、城市、ASN、IP 类型 ｜ 必须删除或替换：账号邮箱、手机号、账单信息",{"type":44,"children":57},[58],{"text":59,"type":29},"类别：时间 ｜ 应当保留：请求开始与结束时间、时区 ｜ 必须删除或替换：精确到人的操作时间线",{"type":25,"children":61},[62],{"text":63,"type":29},"request ID 可帮助服务方检索请求，但其敏感等级由服务方定义，也可能关联账号、时间或请求内容。对外分享前核对其可见范围，必要时脱敏或通过受控工单提供。代理接入记录可保留必要结构，账号口令不得明文留存。",{"type":31,"level":21,"children":65},[66],{"text":67,"type":29},"怎么避免测试记录暴露代理凭据和 API 密钥？",{"type":25,"children":69},[70],{"text":71,"type":29},"测试使用能稳定返回的最小请求、测试账号和无真实含义的占位数据；不要把个人信息或生产文档放入链路。密钥在运行时从环境变量或密钥管理服务注入，不写入脚本、配置文件或截图。",{"type":40,"format":41,"children":73},[74,78,82,86],{"type":44,"children":75},[76],{"text":77,"type":29},"用环境变量或密钥管理服务在运行时注入；",{"type":44,"children":79},[80],{"text":81,"type":29},"测试账号与生产账号分离，测试密钥单独签发；",{"type":44,"children":83},[84],{"text":85,"type":29},"每个密钥标注用途和有效期，测试结束即吊销；",{"type":44,"children":87},[88],{"text":89,"type":29},"共享屏幕和录屏时，先确认终端历史与环境变量面板不在画面内。",{"type":25,"children":91},[92],{"text":93,"type":29},"截图、终端历史和调试面板也要纳入检查，尤其避免展示完整认证头或代理 URL。",{"type":31,"level":21,"children":95},[96],{"text":97,"type":29},"日志、抓包和截图分别怎么脱敏？",{"type":25,"children":99},[100],{"text":101,"type":29},"脱敏应覆盖采集、存储和导出：",{"type":40,"format":41,"children":103},[104,108,112],{"type":44,"children":105},[106],{"text":107,"type":29},"传输层：抓包工具只保留需要的字段，关闭 body 记录；",{"type":44,"children":109},[110],{"text":111,"type":29},"存储层：日志写入前用正则替换 `Authorization`、`api-key`、`token` 等字段值；",{"type":44,"children":113},[114],{"text":115,"type":29},"导出层：对外分享前再检查一次 header 和 URL query，签名参数常藏在 query 里。",{"type":25,"children":117},[118],{"text":119,"type":29},"导出前再复查一次，避免模板带出原始字段。",{"type":25,"children":121},[122],{"text":123,"type":29},"导出前可以跑一条基于字样的命令做初筛，但要清楚它的边界：它只能命中少数常见写法，大小写变体、URL 编码、JSON 转义、云厂商密钥格式和随机高熵 token 都可能漏过；反过来，命中也可能只是普通文本。它既不能证明日志已脱敏，也不能替代人工复核。",{"type":25,"children":125},[126],{"text":127,"type":29},"```bash\ngrep -Ei 'sk-|bearer |password|api[-_]?key|token=' draft.log\n```",{"type":25,"children":129},[130],{"text":131,"type":29},"比单靠关键词搜索更稳妥的方式，是在采集端按字段白名单留存，并结构化脱敏 `Authorization`、`Cookie`、URL query、代理认证和响应正文。对外分享前再用组织批准的 secret scanner 扫描，并人工复核命中项及抽样内容；代理账号密码只在运行时注入。",{"type":31,"level":21,"children":133},[134],{"text":135,"type":29},"测试证据应保存多久、可以分享给谁？",{"type":25,"children":137},[138],{"text":139,"type":29},"留存期限应由使用目的、数据敏感级别和组织策略共同决定。",{"type":25,"children":141},[142],{"text":143,"type":29},"问题关闭后，按组织的数据分类和保留策略处置原始抓包、完整日志与结构化结论。若需要自动清理，应明确负责人、期限和清理记录，并确认满足审计或事件调查要求。",{"type":25,"children":145},[146],{"text":147,"type":29},"跨团队共享时给出最小必要集，不直接转发原始文件。对外提交工单时，只提供 request id、时间、状态码和链路指标。需要长期留存的，只留结构化记录表，不留可回放的原始流量。",{"type":31,"level":21,"children":149},[150],{"text":151,"type":29},"发现密钥泄露后，应该先做什么？",{"type":25,"children":153},[154],{"text":155,"type":29},"发现疑似泄露后，按组织应急流程处理：",{"type":25,"children":157},[158],{"text":159,"type":29},"1. 立即禁用并轮换受影响的密钥；\n2. 检查调用记录，确认是否有异常请求、地区或用量；\n3. 清理日志、工单、截图、代码仓库和本地缓存中的可控残留，同时保留事件证据；\n4. 通知安全负责人；影响范围不清时升级至内部应急流程。",{"type":25,"children":161},[162],{"text":163,"type":29},"随后确认同批次凭据是否受影响，通知使用方更新密钥，并记录时间线和影响范围。",{"type":31,"level":21,"children":165},[166],{"text":167,"type":29},"FAQ",{"type":25,"children":169},[170],{"text":171,"type":29,"marks":172},"Q：脱敏后的日志可以长期保存吗？",[173],{"type":174},"bold",{"type":25,"children":176},[177],{"text":178,"type":29},"A：按组织的数据保留期限执行，并限制访问范围；脱敏不代表没有重识别风险。",{"type":25,"children":180},[181],{"text":182,"type":29,"marks":183},"Q：请求 ID 算敏感信息吗？",[184],{"type":174},{"type":25,"children":186},[187],{"text":188,"type":29},"A：视服务方定义而定。对外共享前应检查其是否可关联账号、请求内容或内部系统。",{"type":25,"children":190},[191],{"text":192,"type":29,"marks":193},"Q：测试截图能否直接放进工单？",[194],{"type":174},{"type":25,"children":196},[197],{"text":198,"type":29},"A：先检查页面、地址栏、通知、请求头和调试面板是否含凭据、个人信息或业务数据，再决定是否脱敏后入单。","",[201,392,568],{"source":6,"id":202,"documentId":203,"translationKey":203,"locale":9,"slug":204,"path":205,"_path":205,"title":206,"description":207,"excerpt":207,"image":208,"publishedAt":209,"updatedAt":210,"author":17,"tags":211,"featured":19,"sortOrder":20,"readingTime":21,"bodyType":22,"bodyBlocks":212,"bodyHtml":199},3897,"y568ae3jjjs4yuaunluzwsqs","ai-api-egress-region-network-policy-diagnosis","\u002Fblog\u002Fai-api-egress-region-network-policy-diagnosis","测试 AI API 地区切换时，如何判断变化来自网络还是服务策略","切换代理 IP 出口地区后结果变化，如何区分网络与服务策略？本文用配对测试对照链路耗时、出口身份和服务端响应，避免把地区差异当成故障。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_AI_API_cover_0df5f0b27f.webp","2026-11-03T03:01:09.178Z","2026-11-03T03:01:09.154Z",[],[213,217,221,225,239,243,247,251,255,259,263,267,271,275,287,291,295,305,309,313,331,335,349,359,362,368,372,378,382,388],{"type":25,"children":214},[215],{"text":216,"type":29},"换地区之后结果变了，可能是链路、出口身份或服务策略，三者要分开取证。做法是固定请求只改地区，用分阶段耗时、IP 画像和响应证据各查一层。换地区这一步通常由代理 IP 完成，所以“换了地区”和“换了 IP”要当成两个变量记录。测试限制在自有账号和测试额度内，不通过反复请求真实付费接口制造样本。",{"type":31,"level":21,"children":218},[219],{"text":220,"type":29},"地区切换后，哪些因素可能导致结果变化？",{"type":25,"children":222},[223],{"text":224,"type":29},"地区切换可能同时改变链路、出口身份和服务策略相关条件；测试时分别记录这三类证据。",{"type":40,"format":41,"children":226},[227,231,235],{"type":44,"children":228},[229],{"text":230,"type":29},"链路质量：RTT、丢包、TLS 握手、首字节时间。",{"type":44,"children":232},[233],{"text":234,"type":29},"出口身份：IP 归属、ASN、IP 类型与信誉。",{"type":44,"children":236},[237],{"text":238,"type":29},"服务策略：限流、地区可用性、账号权限、模型可用列表。",{"type":25,"children":240},[241],{"text":242,"type":29},"如果同时换地区、出口和请求条件，结果无法区分来自哪项变化，因此对照时每轮只改变一个目标变量。",{"type":31,"level":21,"children":244},[245],{"text":246,"type":29},"怎么设计只改变出口地区的对照测试？",{"type":25,"children":248},[249],{"text":250,"type":29},"固定模型、prompt、客户端、超时和重试；按测试目的只改变地区或出口，并记录实际命中的版本和时间。",{"type":25,"children":252},[253],{"text":254,"type":29},"请求次数与观察时段应按接口波动和测试预算设定；尽量在相近时间重复对照，并避免用单次结果判断地区策略。",{"type":25,"children":256},[257],{"text":258,"type":29},"对照组至少三组：原地区、目标地区、以及目标地区的第二个出口 IP。第二组用来区分“地区本身”和“这个 IP”。",{"type":31,"level":21,"children":260},[261],{"text":262,"type":29},"怎么从 DNS、连接和响应耗时判断网络问题？",{"type":25,"children":264},[265],{"text":266,"type":29},"用 curl 对照时，必须显式使用与被测 API 相同的代理、绕过和凭据配置；凭据应从受限环境注入，不写进命令历史。下列字段是从请求开始累计的时间里程碑，不是独立 TCP\u002FTLS 耗时。代理 CONNECT、代理侧 DNS、连接复用和重定向都会影响解释；跳过的阶段标记为未观测。",{"type":25,"children":268},[269],{"text":270,"type":29},"```bash\ncurl -o \u002Fdev\u002Fnull -s -x \"$PROXY_URL\" -w 'namelookup_cumulative=%{time_namelookup} connect_cumulative=%{time_connect} appconnect_cumulative=%{time_appconnect} ttfb_cumulative=%{time_starttransfer}\\n' \\\nhttps:\u002F\u002Fapi.example.com\u002Fv1\u002Fmodels\n```",{"type":25,"children":272},[273],{"text":274,"type":29},"固定客户端、目标接口和时间窗口建立自身基线，再比较阶段分布；样本不足时只记录现象。`time_connect` 与 `time_appconnect` 不能直接当作到源站的独立 TCP\u002FTLS 时长。首字节慢也可能来自上游处理。",{"type":25,"children":276},[277,279,285],{"text":278,"type":29},"若要建立可比较的地区延迟基线，可参考",{"text":280,"type":29,"marks":281},"SaaS API 地区延迟基线怎么建？请求口径与分位数",[282],{"href":283,"type":284},"https:\u002F\u002Fwww.quanqiudaili.com\u002Fblog\u002Fsaas-api-region-latency-baseline","link",{"text":286,"type":29},"。",{"type":31,"level":21,"children":288},[289],{"text":290,"type":29},"怎么确认出口 IP 的地区和类型？",{"type":25,"children":292},[293],{"text":294,"type":29},"对照多个 IP 数据库与目标服务看到的源 IP\u002F地区标签，并记录来源和查询时间。ASN、组织、城市和 IP 类型标签只能提供线索，不能单独证明出口的物理所在地或住宅\u002F机房属性；平台地区识别、购买地区与物理位置应分开记录。",{"type":25,"children":296},[297,299,304],{"text":298,"type":29},"同一地区用第二个出口复测。结果变化只说明出口或路径相关因素可能参与；固定账号、请求和时间窗口重复对照，再结合链路与服务端证据判断。来源冲突时标记为不确定。相关归因思路可参考",{"text":300,"type":29,"marks":301},"多地区测试发现问题，如何判断是网络故障还是产品缺陷？",[302],{"href":303,"type":284},"https:\u002F\u002Fwww.quanqiudaili.com\u002Fblog\u002Fregional-network-vs-product-defect",{"text":286,"type":29},{"type":31,"level":21,"children":306},[307],{"text":308,"type":29},"哪些响应信息能提示账号或地区策略限制？",{"type":25,"children":310},[311],{"text":312,"type":29},"结合错误体、限流头、账号\u002F模型可用范围和传输记录判断；即使链路不稳定，有明确来源的服务端错误仍可作为证据，但不应单独结束排查。下表列出可继续核验的线索：",{"type":40,"format":41,"children":314},[315,319,323,327],{"type":44,"children":316},[317],{"text":318,"type":29},"证据：429 与 Retry-After ｜ 候选方向：限流或配额 ｜ 下一步核对：查限流头、控制台用量及重复样本",{"type":44,"children":320},[321],{"text":322,"type":29},"证据：403 与地区或模型提示 ｜ 候选方向：可用范围 ｜ 下一步核对：核对服务方文档、账号和模型配置",{"type":44,"children":324},[325],{"text":326,"type":29},"证据：request ID 与限流 header ｜ 候选方向：服务端处理 ｜ 下一步核对：按服务方定义与网关日志核验",{"type":44,"children":328},[329],{"text":330,"type":29},"证据：链路指标稳定但持续失败 ｜ 候选方向：服务策略候选 ｜ 下一步核对：重复请求并核对错误体和账号范围",{"type":31,"level":21,"children":332},[333],{"text":334,"type":29},"确认原因后，下一步分别怎么处理？",{"type":40,"format":41,"children":336},[337,341,345],{"type":44,"children":338},[339],{"text":340,"type":29},"候选方向：链路异常 ｜ 对应证据：阶段耗时反复偏离自身基线 ｜ 下一步动作：换路径或时段复测，再看服务端响应",{"type":44,"children":342},[343],{"text":344,"type":29},"候选方向：出口相关 ｜ 对应证据：固定其余条件后结果随出口变化 ｜ 下一步动作：增加配对样本，核对路径与出口来源",{"type":44,"children":346},[347],{"text":348,"type":29},"候选方向：服务策略候选 ｜ 对应证据：错误体、账号\u002F模型范围和重复样本相互吻合 ｜ 下一步动作：核对配置或向服务方确认",{"type":25,"children":350},[351,353,358],{"text":352,"type":29},"证据不一致时，记录缺少的对照条件并保持未决。curl 参数和时间字段的含义可查",{"text":354,"type":29,"marks":355},"curl 手册",[356],{"href":357,"type":284},"https:\u002F\u002Fcurl.se\u002Fdocs\u002Fmanpage.html",{"text":286,"type":29},{"type":31,"level":21,"children":360},[361],{"text":167,"type":29},{"type":25,"children":363},[364],{"text":365,"type":29,"marks":366},"Q：测试时 API 返回内容不稳定，会影响地区归因吗？",[367],{"type":174},{"type":25,"children":369},[370],{"text":371,"type":29},"A：会。应固定模型、输入和采样窗口；无法固定时，将内容波动作为单独限制记录。",{"type":25,"children":373},[374],{"text":375,"type":29,"marks":376},"Q：同一地区的两个出口结果不同，能直接认定是 IP 信誉吗？",[377],{"type":174},{"type":25,"children":379},[380],{"text":381,"type":29},"A：不能，先排除网络路径、账号状态和上游时间差，再把结论标为待验证。",{"type":25,"children":383},[384],{"text":385,"type":29,"marks":386},"Q：地区策略由服务方临时调整时，旧测试结论还有效吗？",[387],{"type":174},{"type":25,"children":389},[390],{"text":391,"type":29},"A：只对当时的账号、地区和时间窗口有效，策略变化后应重新验证。",{"source":6,"id":393,"documentId":394,"translationKey":394,"locale":9,"slug":395,"path":396,"_path":396,"title":397,"description":398,"excerpt":398,"image":399,"publishedAt":400,"updatedAt":401,"author":17,"tags":402,"featured":19,"sortOrder":20,"readingTime":21,"bodyType":22,"bodyBlocks":403,"bodyHtml":199},3896,"a6t066253yf747tqsxn0ts4x","http-client-proxy-retry-duplicate-submit-test","\u002Fblog\u002Fhttp-client-proxy-retry-duplicate-submit-test","HTTP 客户端升级后，如何检查代理重试是否导致重复提交","HTTP 客户端升级后，如何检查重试是否导致重复提交？本文用请求计数区分自动重试、重定向重放和连接中断，并说明幂等验证要点。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_HTTP_cover_6e441939e4.webp","2026-11-03T03:00:50.977Z","2026-11-03T03:00:50.965Z",[],[404,408,412,416,430,434,438,442,446,464,468,478,482,486,490,504,508,512,516,520,524,535,538,544,548,554,558,564],{"type":25,"children":405},[406],{"text":407,"type":29},"重复提交往往不是业务代码写错，而是客户端在代理 IP 链路上自动重试了非幂等请求。要验证这一点，得让“请求被执行了几次”变成可观测的数字，不靠肉眼核对结果。下面用一个计数接口做靶子，配合注入失败来暴露重试路径。",{"type":31,"level":21,"children":409},[410],{"text":411,"type":29},"怎么判断请求被重复处理了？",{"type":25,"children":413},[414],{"text":415,"type":29},"业务代码调用一次后，客户端可能发送多次网络请求；这不等于业务副作用也执行多次。对照三类记录：客户端发送尝试、服务端接收次数、同一业务键对应的实际副作用次数。再检查幂等键是否一致。只看最终结果不够，因为重复写入可能互相覆盖而不显错。常见重放入口如下：",{"type":40,"format":41,"children":417},[418,422,426],{"type":44,"children":419},[420],{"text":421,"type":29},"重试机制：状态码重试 ｜ 触发条件：503、502、连接超时 ｜ 注入方式：让服务端对部分请求返回 503",{"type":44,"children":423},[424],{"text":425,"type":29},"重试机制：重定向重放 ｜ 触发条件：307、308 通常要求保留方法与请求体 ｜ 注入方式：测试网关让 A 重定向到 B，仅跳转一次；循环作为单独负例并限制跳数",{"type":44,"children":427},[428],{"text":429,"type":29},"重试机制：连接重置重试 ｜ 触发条件：客户端认为请求未发出 ｜ 注入方式：代理侧主动断开连接",{"type":31,"level":21,"children":431},[432],{"text":433,"type":29},"怎么搭建一个可观察请求次数的测试接口？",{"type":25,"children":435},[436],{"text":437,"type":29},"测试接口按业务键统计接收次数和副作用次数，并返回请求序号。日志只保留脱敏关联键、必要状态和计数，过滤认证头、Cookie、URL 凭据与业务正文。测试请求应经过被测客户端和代理路径，以便对照客户端发送、服务端接收及业务处理记录。",{"type":31,"level":21,"children":439},[440],{"text":441,"type":29},"客户端会在什么情况下自动重试？",{"type":25,"children":443},[444],{"text":445,"type":29},"客户端的重试策略由具体库、版本、状态码和请求配置决定，不能只按 HTTP 方法推断。下表列的是需要核验的风险：",{"type":40,"format":41,"children":447},[448,452,456,460],{"type":44,"children":449},[450],{"text":451,"type":29},"方法：GET \u002F HEAD ｜ 方法语义：幂等 ｜ 待核验的风险：通常可安全重试，仍需核对客户端策略与服务端实现",{"type":44,"children":453},[454],{"text":455,"type":29},"方法：PUT \u002F DELETE ｜ 方法语义：幂等 ｜ 待核验的风险：客户端可能重试，需确认服务端确实做过幂等实现",{"type":44,"children":457},[458],{"text":459,"type":29},"方法：POST ｜ 方法语义：非幂等 ｜ 待核验的风险：重试即可能重复创建，需核对客户端策略、幂等键与服务端处理",{"type":44,"children":461},[462],{"text":463,"type":29},"方法：PATCH ｜ 方法语义：非幂等 ｜ 待核验的风险：部分客户端仍会重试，需按具体版本核对",{"type":25,"children":465},[466],{"text":467,"type":29},"升级后要特别确认：新版本是否把 POST 也纳入了可重试范围。这正是重复提交最常见的来源。",{"type":25,"children":469},[470,472,477],{"text":471,"type":29},"需要继续核对投递、签名与重试行为，可参考",{"text":473,"type":29,"marks":474},"SaaS Webhook 地区测试怎么做？到达、签名与重试",[475],{"href":476,"type":284},"https:\u002F\u002Fwww.quanqiudaili.com\u002Fblog\u002Fsaas-webhook-delivery-retry-region-testing",{"text":286,"type":29},{"type":31,"level":21,"children":479},[480],{"text":481,"type":29},"307 和 308 为什么可能重复发送请求？",{"type":25,"children":483},[484],{"text":485,"type":29},"客户端若自动跟随 307\u002F308，通常会尝试保留方法和请求体重新发送，但请求体是否可重放、原请求是否已被处理、服务端是否有副作用，都取决于客户端实现与应用设计。",{"type":25,"children":487},[488],{"text":489,"type":29},"测试时检查客户端是否自动跟随、请求体能否重放，以及 A、B 两端分别收到几次请求：",{"type":40,"format":41,"children":491},[492,496,500],{"type":44,"children":493},[494],{"text":495,"type":29},"是否携带同一个幂等键；",{"type":44,"children":497},[498],{"text":499,"type":29},"是否重复执行了创建逻辑；",{"type":44,"children":501},[502],{"text":503,"type":29},"是否存在重定向循环。",{"type":25,"children":505},[506],{"text":507,"type":29},"仅在隔离的测试环境中，可将 `Location` 指向计数接口，观察客户端是否再次发送请求。请求次数增加说明发生了重放；还需检查服务端是否重复执行了业务操作。若方法或请求体发生变化，应记录具体客户端行为，并对照其版本文档。",{"type":31,"level":21,"children":509},[510],{"text":511,"type":29},"代理连接中断时，写请求会不会被重新发送？",{"type":25,"children":513},[514],{"text":515,"type":29},"断开位置决定服务端是否处理过请求。可在受控环境中分别模拟发送前失败、服务端接收后失败和业务提交后丢弃响应，再对照客户端日志、接收次数与副作用记录。连接重置本身无法说明请求是否已执行。",{"type":31,"level":21,"children":517},[518],{"text":519,"type":29},"怎么模拟失败并确认修复有效？",{"type":25,"children":521},[522],{"text":523,"type":29},"在隔离环境中逐项注入故障：发送前断开、业务提交后丢弃响应、返回 503、A 到 B 的一次 307，以及受控超时。重定向循环另作负例并限制跳数。每轮只改一个条件，记录客户端发送、服务端接收和业务副作用次数。修复后重跑相同用例；只有业务记录没有重复副作用且各层计数符合预期，才算通过。",{"type":25,"children":525},[526,528,533],{"text":527,"type":29},"307\u002F308 的 HTTP 语义见 ",{"text":529,"type":29,"marks":530},"RFC 9110",[531],{"href":532,"type":284},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc9110.html",{"text":534,"type":29},"；客户端是否自动跟随及请求体能否重放，应按具体版本验证。",{"type":31,"level":21,"children":536},[537],{"text":167,"type":29},{"type":25,"children":539},[540],{"text":541,"type":29,"marks":542},"Q：幂等键能避免所有重复写入吗？",[543],{"type":174},{"type":25,"children":545},[546],{"text":547,"type":29},"A：不能，需确认服务端确实支持幂等键，并验证键的作用范围和保留时间。",{"type":25,"children":549},[550],{"text":551,"type":29,"marks":552},"Q：读请求和写请求可以使用相同重试策略吗？",[553],{"type":174},{"type":25,"children":555},[556],{"text":557,"type":29},"A：不应默认相同；写请求需评估服务端处理状态未知时的重复执行风险。",{"type":25,"children":559},[560],{"text":561,"type":29,"marks":562},"Q：客户端没有收到响应，能否判断服务端未处理请求？",[563],{"type":174},{"type":25,"children":565},[566],{"text":567,"type":29},"A：不能。连接中断时处理状态可能未知，应通过服务端记录或业务状态核对。",{"source":6,"id":569,"documentId":570,"translationKey":570,"locale":9,"slug":571,"path":572,"_path":572,"title":573,"description":574,"excerpt":574,"image":575,"publishedAt":576,"updatedAt":577,"author":17,"tags":578,"featured":19,"sortOrder":20,"readingTime":579,"bodyType":22,"bodyBlocks":580,"bodyHtml":199},3895,"tf37dm2ee9mh0u8xbco8p5ch","go-transport-proxy-connection-regression","\u002Fblog\u002Fgo-transport-proxy-connection-regression","Go 版本或 Transport 配置变更后，如何重新检查代理连接","Go 版本或 Transport 配置变更后，如何检查代理连接？本文核对代理配置来源，并对照连接复用、TLS、HTTP\u002F2 和分阶段耗时。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_Go_Transport_cover_4e51fe43de.webp","2026-11-03T03:00:34.422Z","2026-11-03T03:00:34.401Z",[],4,[581,585,589,593,607,611,615,619,623,627,631,635,639,643,647,651,661,665,669,683,687,691,695,709,713,717,721,725,729,751,755,762,765,771,775,781,785,791],{"type":25,"children":582},[583],{"text":584,"type":29},"Go 的代理 IP 行为由版本、环境变量和 `Transport` 配置共同决定，任何一项变化都可能改变实际走向。回归的关键是把“代理从哪来”和“请求走到哪一步”都变成可观测的。下面用标准库的 `httptrace` 与会话复用指标做示范，测试在自有服务与测试出口上进行。",{"type":31,"level":21,"children":586},[587],{"text":588,"type":29},"升级后先确认 Go 版本和哪些配置变了",{"type":25,"children":590},[591],{"text":592,"type":29},"先记录 Go 版本、构建选项和代理环境配置，作为升级前后对照基线：",{"type":40,"format":41,"children":594},[595,599,603],{"type":44,"children":596},[597],{"text":598,"type":29},"Go 版本：`go version`，大版本升级可能改变默认 TLS 与 HTTP\u002F2 行为；",{"type":44,"children":600},[601],{"text":602,"type":29},"GOFLAGS 与构建标签：是否启用或关闭了某些默认行为；",{"type":44,"children":604},[605],{"text":606,"type":29},"环境变量：`HTTP_PROXY`、`HTTPS_PROXY`、`NO_PROXY` 是否被设置，以及大小写写法。",{"type":25,"children":608},[609],{"text":610,"type":29},"`HTTP_PROXY` 与 `http_proxy` 的处理及 `NO_PROXY` 匹配规则应按 Go 版本核实。回归前记录变量是否设置、脱敏后的主机\u002F端口与绕过规则，再和升级前记录对比。不要输出含 URL userinfo、口令或完整凭据的代理环境变量。",{"type":31,"level":21,"children":612},[613],{"text":614,"type":29},"Go 请求最终使用了哪种代理配置？",{"type":25,"children":616},[617],{"text":618,"type":29},"最终用哪套配置，以当前请求实际使用的 `Transport` 和绕过规则为准。除了 `Proxy`，还要检查自定义 `DialContext`、`NO_PROXY`、重定向和连接池；环境变量是否被读取还取决于读取时机，不能只看代码或环境变量就下判断。",{"type":25,"children":620},[621],{"text":622,"type":29},"```go\ntr := &http.Transport{\nProxy: http.ProxyFromEnvironment, \u002F\u002F 读环境变量\n\u002F\u002F Proxy: http.ProxyURL(proxyURL), \u002F\u002F 显式指定\nTLSHandshakeTimeout: 5 * time.Second,\n}\nclient := &http.Client{Transport: tr, Timeout: 30 * time.Second}\nreq, _ := http.NewRequest(\"GET\", \"https:\u002F\u002Fapi.example.com\u002Fv1\u002Fmodels\", nil)\nreq.Header.Set(\"Authorization\", \"Bearer \"+os.Getenv(\"API_KEY\"))\nresp, err := client.Do(req)\n```",{"type":25,"children":624},[625],{"text":626,"type":29},"若 `Transport.Proxy` 显式设置了代理函数或地址，请求将按该配置处理，不会再由另一套环境变量配置替代。使用标准库 `ProxyFromEnvironment` 时，代理环境配置存在进程级缓存行为；按目标 Go 版本核对实现。修改环境变量后，应在设置好环境的新进程中测试，只新建 Transport 不保证刷新缓存。需要运行时动态选择代理时，用明确管理的 `Proxy` 函数或 `ProxyURL`，并单独管理连接池。",{"type":25,"children":628},[629],{"text":630,"type":29},"在隔离测试环境中，可将代理地址指向不可用端口作为负向测试。请求仍成功只说明该请求没有依赖这个代理配置；还应排查 `NO_PROXY`、其他代理设置、连接复用等因素，并核对最终出口。",{"type":25,"children":632},[633],{"text":634,"type":29},"`NO_PROXY` 命中时请求会绕过代理；应把匹配结果与实际出口一起核对。",{"type":31,"level":21,"children":636},[637],{"text":638,"type":29},"怎么用 `httptrace` 定位连接耗时？",{"type":25,"children":640},[641],{"text":642,"type":29},"`httptrace` 提供的是阶段事件，不是现成的耗时报表；需要在事件回调里记时间戳、算差值，再和总耗时对照。",{"type":25,"children":644},[645],{"text":646,"type":29},"```go\nstart := time.Now()\nvar dnsStart, connStart, tlsStart time.Time\ntrace := &httptrace.ClientTrace{\nDNSStart:          func(httptrace.DNSStartInfo) { dnsStart = time.Now() },\nDNSDone:           func(httptrace.DNSDoneInfo) { log.Printf(\"dns %v\", time.Since(dnsStart)) },\nConnectStart:      func(network, addr string) { connStart = time.Now(); log.Printf(\"connect start %s %s\", network, addr) },\nConnectDone:       func(network, addr string, err error) { log.Printf(\"connect done %s %v %v\", addr, time.Since(connStart), err) },\nTLSHandshakeStart: func() { tlsStart = time.Now() },\nTLSHandshakeDone: func(cs tls.ConnectionState, err error) {\nlog.Printf(\"tls %v proto=%s\", time.Since(tlsStart), cs.NegotiatedProtocol)\n},\nGotFirstResponseByte: func() { log.Printf(\"ttfb %v\", time.Since(start)) },\n}\nreq = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))\n```",{"type":25,"children":648},[649],{"text":650,"type":29},"开始事件与完成事件要成对记录，才能计算 DNS、拨号、TLS 等阶段时长；只注册起点不会得到 duration。示例中的单一时间变量只适用于串行、单次尝试；Happy Eyeballs 等情况下可能并发或多次触发回调，应按连接尝试关联时间戳，并用互斥锁或并发安全结构保护。若连接复用，可能没有新的拨号事件，缺事件不等于零耗时。代理下 `ConnectStart` 的目标通常是代理地址；经 HTTP 代理时 DNS 也可能由代理完成。",{"type":25,"children":652},[653,655,660],{"text":654,"type":29},"移动应用场景的 DNS、连接和 TLS 分阶段定位方法，可参考",{"text":656,"type":29,"marks":657},"移动 App 网络兼容测试：五个失败阶段定位法",[658],{"href":659,"type":284},"https:\u002F\u002Fwww.quanqiudaili.com\u002Fblog\u002Fmobile-app-network-egress-compatibility-testing",{"text":286,"type":29},{"type":31,"level":21,"children":662},[663],{"text":664,"type":29},"怎么确认连接复用和超时设置仍然生效？",{"type":25,"children":666},[667],{"text":668,"type":29},"检查连接池配置与 `GotConn` 事件，比较同一负载下的复用结果：",{"type":40,"format":41,"children":670},[671,675,679],{"type":44,"children":672},[673],{"text":674,"type":29},"`DisableKeepAlives: true` 会让每个请求重新建连；",{"type":44,"children":676},[677],{"text":678,"type":29},"`TLSHandshakeTimeout` 过短会在跨境链路上频繁失败；",{"type":44,"children":680},[681],{"text":682,"type":29},"`MaxIdleConnsPerHost` 过低会导致高并发时连接被丢弃重建。",{"type":25,"children":684},[685],{"text":686,"type":29},"观测方式：看 `httptrace.GotConn` 回调里 `info.Reused` 的取值，统计连续请求中的复用比例。复用比例受请求模式、连接池和服务端行为影响，不设统一阈值；将升级前后相同负载下的结果对照，异常时再检查配置和默认值。",{"type":31,"level":21,"children":688},[689],{"text":690,"type":29},"升级后 HTTP\u002F2 或 TLS 协商有什么变化？",{"type":25,"children":692},[693],{"text":694,"type":29},"Go 版本升级可能改变 HTTP\u002F2 启用条件和 TLS 默认值，判断时以 Go 客户端自己的结果为准。",{"type":40,"format":41,"children":696},[697,701,705],{"type":44,"children":698},[699],{"text":700,"type":29},"自定义 `TLSClientConfig` 可能关闭 HTTP\u002F2；",{"type":44,"children":702},[703],{"text":704,"type":29},"`ForceAttemptHTTP2` 的设置会影响协商结果；",{"type":44,"children":706},[707],{"text":708,"type":29},"最低 TLS 版本提升后，旧代理或中间设备可能握手失败。",{"type":25,"children":710},[711],{"text":712,"type":29},"以 Go 的 `resp.Proto`、`resp.TLS.NegotiatedProtocol`（TLS 响应时）及客户端日志作为 Go 实际协商结果。curl 只能作显式相同代理、目标和路径下的独立对照，不能代表 Go 客户端结果：",{"type":25,"children":714},[715],{"text":716,"type":29},"```bash\ncurl -sI -o \u002Fdev\u002Fnull -w '%{http_version}\\n' https:\u002F\u002Fapi.example.com\u002F\n```",{"type":25,"children":718},[719],{"text":720,"type":29},"该命令只显示 curl 自己的协商结果；Go 的结果以 `resp.Proto` 与 TLS 状态为准。两者不一致时，先确认代理、目标和请求路径相同，再检查 Go 版本、`tls.Config` 与代理隧道能力。",{"type":31,"level":21,"children":722},[723],{"text":724,"type":29},"怎么判断代理连接回归通过？",{"type":25,"children":726},[727],{"text":728,"type":29},"可按下表对照代理来源、分阶段耗时、连接复用、协议协商及超时重试。",{"type":40,"format":41,"children":730},[731,735,739,743,747],{"type":44,"children":732},[733],{"text":734,"type":29},"检查项：代理来源 ｜ 观测点：检查 `Transport.Proxy`、绕过规则及实际出口 ｜ 正常表现：与预期代理配置和出口一致",{"type":44,"children":736},[737],{"text":738,"type":29},"检查项：分阶段耗时 ｜ 观测点：`httptrace` 成对事件算出的各段时长 ｜ 正常表现：各段与升级前同一量级",{"type":44,"children":740},[741],{"text":742,"type":29},"检查项：连接复用 ｜ 观测点：`GotConn` 的 `Reused` ｜ 正常表现：与升级前相同负载下的基线对照",{"type":44,"children":744},[745],{"text":746,"type":29},"检查项：Go 协商协议 ｜ 观测点：`resp.Proto` 与 TLS 状态 ｜ 正常表现：按目标服务、代理路径和升级前基线核对",{"type":44,"children":748},[749],{"text":750,"type":29},"检查项：超时与重试 ｜ 观测点：三类超时的触发日志 ｜ 正常表现：各按预期触发，次数不变",{"type":25,"children":752},[753],{"text":754,"type":29},"如果实际出口与配置不符，先检查 `Transport.Proxy` 和绕过规则；若某阶段耗时变化，检查对应的连接与超时配置；HTTP\u002F2 协商结果变化时，再核对 Go 版本、TLS 配置和代理支持情况。",{"type":25,"children":756},[757],{"text":758,"type":29,"marks":759},"Go net\u002Fhttp 官方文档",[760],{"href":761,"type":284},"https:\u002F\u002Fpkg.go.dev\u002Fnet\u002Fhttp",{"type":31,"level":21,"children":763},[764],{"text":167,"type":29},{"type":25,"children":766},[767],{"text":768,"type":29,"marks":769},"Q：环境变量代理设置会自动覆盖自定义 Transport 吗？",[770],{"type":174},{"type":25,"children":772},[773],{"text":774,"type":29},"A：取决于 `Transport.Proxy` 是否显式设置及 Go 版本行为，应以实际配置和版本文档为准。",{"type":25,"children":776},[777],{"text":778,"type":29,"marks":779},"Q：测试关闭连接复用会代表线上表现吗？",[780],{"type":174},{"type":25,"children":782},[783],{"text":784,"type":29},"A：不会。它适合隔离连接问题，结果应与默认复用配置分开记录。",{"type":25,"children":786},[787],{"text":788,"type":29,"marks":789},"Q：只在一个 Go 版本通过是否足够？",[790],{"type":174},{"type":25,"children":792},[793],{"text":794,"type":29},"A：若生产使用多个版本，应覆盖实际部署版本及升级目标版本。",[796],{"source":6,"id":7,"documentId":8,"translationKey":8,"locale":9,"slug":10,"path":11,"_path":11,"title":12,"description":13,"excerpt":13,"image":14,"publishedAt":15,"updatedAt":16,"author":17,"tags":797,"featured":19,"sortOrder":20,"readingTime":21,"bodyType":22,"bodyBlocks":798,"bodyHtml":199},[],[799,802,805,808,822,825,828,831,845,848,851,854,865,868,871,874,877,880,883,886,889,892,895,898,901,904,909,912,917,920,925],{"type":25,"children":800},[801],{"text":28,"type":29},{"type":31,"level":21,"children":803},[804],{"text":34,"type":29},{"type":25,"children":806},[807],{"text":38,"type":29},{"type":40,"format":41,"children":809},[810,813,816,819],{"type":44,"children":811},[812],{"text":47,"type":29},{"type":44,"children":814},[815],{"text":51,"type":29},{"type":44,"children":817},[818],{"text":55,"type":29},{"type":44,"children":820},[821],{"text":59,"type":29},{"type":25,"children":823},[824],{"text":63,"type":29},{"type":31,"level":21,"children":826},[827],{"text":67,"type":29},{"type":25,"children":829},[830],{"text":71,"type":29},{"type":40,"format":41,"children":832},[833,836,839,842],{"type":44,"children":834},[835],{"text":77,"type":29},{"type":44,"children":837},[838],{"text":81,"type":29},{"type":44,"children":840},[841],{"text":85,"type":29},{"type":44,"children":843},[844],{"text":89,"type":29},{"type":25,"children":846},[847],{"text":93,"type":29},{"type":31,"level":21,"children":849},[850],{"text":97,"type":29},{"type":25,"children":852},[853],{"text":101,"type":29},{"type":40,"format":41,"children":855},[856,859,862],{"type":44,"children":857},[858],{"text":107,"type":29},{"type":44,"children":860},[861],{"text":111,"type":29},{"type":44,"children":863},[864],{"text":115,"type":29},{"type":25,"children":866},[867],{"text":119,"type":29},{"type":25,"children":869},[870],{"text":123,"type":29},{"type":25,"children":872},[873],{"text":127,"type":29},{"type":25,"children":875},[876],{"text":131,"type":29},{"type":31,"level":21,"children":878},[879],{"text":135,"type":29},{"type":25,"children":881},[882],{"text":139,"type":29},{"type":25,"children":884},[885],{"text":143,"type":29},{"type":25,"children":887},[888],{"text":147,"type":29},{"type":31,"level":21,"children":890},[891],{"text":151,"type":29},{"type":25,"children":893},[894],{"text":155,"type":29},{"type":25,"children":896},[897],{"text":159,"type":29},{"type":25,"children":899},[900],{"text":163,"type":29},{"type":31,"level":21,"children":902},[903],{"text":167,"type":29},{"type":25,"children":905},[906],{"text":171,"type":29,"marks":907},[908],{"type":174},{"type":25,"children":910},[911],{"text":178,"type":29},{"type":25,"children":913},[914],{"text":182,"type":29,"marks":915},[916],{"type":174},{"type":25,"children":918},[919],{"text":188,"type":29},{"type":25,"children":921},[922],{"text":192,"type":29,"marks":923},[924],{"type":174},{"type":25,"children":926},[927],{"text":198,"type":29},null,[],[]]