Outlook加载项部分客户端fetch()调用无返回如何调试
Outlook加载项fetch请求无响应问题排查
问题背景
- 基于TypeScript开发的现代风格Outlook加载项,核心逻辑为用户发送邮件时,从Azure Web函数拉取XML配置数据
- 绝大多数客户环境运行正常,仅某客户组织内部分客户端调用
fetch()时无返回结果,安装加载项后这类客户端会卡在邮件发送流程,无法正常发信 - 经Application Insights链路排查确认:异常客户端正常发起了
fetch()请求,服务端Azure Web函数也已收到请求并正常返回对应XML数据,但异常机器上的fetch()调用始终处于pending状态,无任何异常、错误信息抛出 - 已对比正常/异常客户端表层环境参数:操作系统均为Windows 10、Edge浏览器版本均为103.0、Outlook JavaScript SDK版本均为2.5.9,无明显差异
问题相关代码
let myXMLData: string = ""; try { debug.Log("Rules.FetchConfigurationFile", "Using fetch"); const response = await fetch(configurationURL, { method: "POST", mode: "cors", //headers: { "Content-Type": "application/json"}, body: encodeURIComponent(emailAddress) }); if (!response.ok) { myXMLData = "Not found"; debug.Log("Rules.fetchConfigurationFile", "Rules not found. Response: " + response.status); throw response.statusText; } else { myXMLData = await response.text(); debug.Log("Rules.fetchConfigurationFile", "Loaded Rules: " + myXMLData); } } catch (ex) { debug.LogException("Error occurred in Rules.FetchConfigurationFile", ex); } // // And decode any encoded XMLData. // let decodedXMLData: string = decodeURIComponent(myXMLData); return decodedXMLData;
注:debug.Log()为Application Insights日志上报方法
日志对比
正常客户端日志(UTC时间)
7/6/2022, 10:53:23.946 PM Rules.FetchConfigurationFile: Using fetch
7/6/2022, 10:53:25.076 PM Rules.fetchConfigurationFile: Loaded Rules: [内容省略]
异常客户端日志(UTC时间)
7/6/2022, 10:48:59.829 PM Rules.FetchConfigurationFile: Using fetch
- 异常客户端无后续执行日志、无异常/错误上报,服务端侧可确认已正常返回所需XML数据
排查调试步骤
- 第一步先加超时兜底,避免阻塞用户发信
现有代码无超时控制,请求挂起会直接卡死整个邮件发送流程,先通过AbortController增加超时逻辑,超时后走降级流程,不阻塞用户正常发信,同时增加超时场景的日志标记:const controller = new AbortController(); // 10秒超时兜底 const timeoutId = setTimeout(() => controller.abort(), 10000); try { const response = await fetch(configurationURL, { signal: controller.signal, method: "POST", mode: "cors", // 显式声明urlencoded请求体类型,避免代理拦截 headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: encodeURIComponent(emailAddress) }); // 原有响应处理逻辑保持不变 } catch (ex) { if (ex.name === 'AbortError') { debug.Log("Rules.FetchConfigurationFile", "Fetch request timed out after 10s"); } debug.LogException("Error occurred in Rules.FetchConfigurationFile", ex); } finally { clearTimeout(timeoutId); } - 第二步拆分日志点位,定位挂死阶段
现有日志只在请求发起前打了点,无法判断是请求阶段挂死还是读响应体阶段挂死,把两个阶段拆开打点:
如果拿不到响应头日志,说明请求在网络层被拦截挂死;如果能拿到响应头日志但拿不到响应体日志,说明响应体在传输过程中被本地拦截层丢弃。debug.Log("Rules.FetchConfigurationFile", "Start send fetch request"); const response = await fetch(/* 配置参数 */); // 能打印下面这条日志,说明已经拿到响应头,挂死发生在读取响应体阶段 debug.Log("Rules.FetchConfigurationFile", `Got response header, status code: ${response.status}`); const responseText = await response.text(); // 能打印下面这条日志,说明响应体读取完成 debug.Log("Rules.FetchConfigurationFile", "Got full response body"); - 第三步排查本地流量拦截层
这类服务端已正常返回、客户端无报错永久pending的场景,90%以上是本地企业安全软件吞包导致:- 排查异常机器是否安装了端点防护、SSL流量审计、上网行为代理类工具(常见的企业级工具包括Zscaler、Symantec、国内的天擎、深信服EDR等),这类工具会注入系统网络栈,对HTTPS流量做中间人解密检测,当请求格式、响应内容不符合其内置规则时,会直接丢弃响应包,既不返回给上层应用,也不抛出网络错误
- 临时退出上述安全软件/代理做验证,或者将Azure函数的域名加到这类工具的SSL解密白名单、流量拦截白名单中测试
- 在异常机器上用Fiddler/Charles抓加载项的请求流量,看抓包工具能否正常收到服务端返回的XML响应,如果抓包工具能收到响应但加载项内拿不到,即可确认是本地拦截层的问题
- 第四步排查Webview运行时与组策略限制
现代Outlook加载项运行在嵌入式Webview中,即使表层Edge版本号一致,企业组策略也可能给Webview配置不同的安全限制:- 确认异常机器上Outlook使用的Webview runtime版本,是否和正常机器一致,有没有被组策略强制使用旧内核
- 检查异常机器的Windows安全区域设置、Edge组策略,有没有将Azure函数域名划入高安全等级区域,限制跨域POST请求、限制特定大小的响应体接收
- 检查Azure函数返回的CORS响应头,确保
Access-Control-Allow-Origin值和加载项域名精准匹配,不要在带身份认证的场景下使用通配符*,避免Webview跨域校验时静默拦截响应
内容的提问来源于stack exchange,提问作者MikeS
相关产品推荐
相关产品推荐

