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