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