当前位置: 首页 > news >正文

如何解决 Node.js 20 升级中未预期的请求问题

在 Tubi,我们使用 Node.js 为 Web/OTT 应用进行服务端渲染及代理请求。近来,为了从新版本的性能改进和新功能中受益,我们将 Node.js 从 14.x 版本升级到了 20.x。

升级像 Node.js 这样的基础设施绝非易事,尤其是有着许多第三方依赖的大型项目,因为我们无法预测可能会出现的问题。在升级过程中,我们很快便遇到了第一个问题:服务器端发送的每个请求都会失败,并返回 404 的错误。

调试

第一个被怀疑的对象是仓库里已经逐渐被我们弃用的 request 库。我们在 Node.js 20 环境下使用 request 库发送请求复现了该问题:request.get('https://foo.bar/get'),服务端返回了包括 404 响应在内的多种错误。该库使用了 Node.js 内置的 http(s) 模块来发送请求。为了探究发送请求时究竟发生了什么,我们又给测试添加了NODE_DEBUG=http,net 和 -trace-event-categories node.http,node.net.native 标志。

{"cat":"node,node.http","name":"http.client.request","args":{"data":{"path":"/"}}}

以下是我们的发现:

  • 我们请求的是 https://foo.bar/get,但 http.request 的 path 却是 /。
  • 如果我们将 Node.js 降级到 14 版本并使用同样的代码,path 的值则是正确的 /get。这解释了为什么服务器返回了 404 响应。

我们尝试在 Node.js 20 版本下构造两个最小复现案例: 一个案例采用了原生的 Node.js 模块 http,另一个案例使用了 request 库;但这两个案例都无法复现这一异常情况。这让我们开始怀疑,我们的代码库中是否有可能存在问题?

在深入研究代码库之前,我们在该库 GitHub 的 issues 中发现了一个类似问题,其中提到了该问题可能与用于追踪错误的 Sentry SDK 有关。我们通过在 Node.js 20 中引入 Sentry 和该库成功地复现了这一问题。那么,为什么 Request 库 + Sentry + Node.js 20 会导致这一问题出现?我们来看看它们分别对 Node.js 的 http(s) 模块做了什么。

调试内置的 Node.js 模块

为了检查传递给 Node.js 内置 http(s) 模块的真实参数,我们通过在命令中添加了 -expose-internals 参数(以及 -r internal/test/binding 标志来访问 primordials),并从 Node.js 代码库中复制了 http(s) 模块文件:

// 将 
const https = require('https');
// 修改为
const https = require('./path/to/local/https');

现在我们可以打印日志、添加断点了,还可以直接修改模块代码。我们还发现了更多线索:http(s) 模块接受 WHATWG URL 对象或一个普通对象作为参数。在我们的案例中,request 库将一个普通对象作为参数发送给 http(s) 模块,但由于某种原因,http(s) 模块将其视为了 URL 对象。而如果它是一个 URL 对象,Node.js 会将其转换为 http(s) 需要的参数,并将 path 设置为 URL.pathname + URL.search。而这个库传递的对象上不存在这些属性,于是导致了真正发出的请求中 path 为空:

// The request function that http(s) module use:
// <https://github.com/nodejs/node/blob/36c72c8b2fb03414e02ffd8402c05129647ce123/lib/https.js#L367C5-L369>
function request(...args) {...if (isURL(args[0])) {options = urlToHttpOptions(ArrayPrototypeShift(args));}
}
// https://github.com/nodejs/node/blob/36c72c8b2fb03414e02ffd8402c05129647ce123/lib/internal/url.js#L1322C1-L1344
function urlToHttpOptions(url) {const { pathname, search } = url;const options = {...path: `${pathname || ''}${search || ''}`,}return options
}

那为什么 http(s) 模块会将来自该库的普通对象认为是一个 URL 对象呢?在 isURL 函数中,如果在参数中找到 href 和 protocol 字段,则会认为其是一个 URL 对象:

// <https://github.com/nodejs/node/blob/36c72c8b2fb03414e02ffd8402c05129647ce123/lib/internal/url.js#L761>
function isURL(self) {return Boolean(self?.href && self.protocol && self.auth === undefined);
}

那么这两个属性是如何被添加到传递给 http(s) 模块的对象上的呢?我们检查了库和 Sentry 库的源代码,发现 request 库添加了 href 属性,而 Sentry 添加了 protocol 属性。

谜团终于解开了!

如何修复这一问题?

我们理解了造成 404 错误的原因,也很清楚因为 request 库、Sentry 和 Node.js 都存在各自的问题,所以修复这一问题将变得十分棘手;但问题的根源在于 Node.js 方面:isURL 函数无法准确判断是否为 URL 对象。因此,我们必须找到一种准确判断 URL 对象的方法。这应该不会太难吧?

  • 我们可以直接使用 object instanceof URL 吗?很遗憾,结果是不行,因为它无法识别来自其他实现(例如 Electron)的 URL 对象。
  • 我们是否可以检查其他属性,或者将属性从 protocol 改回 origin 呢?虽然我们可以这样做,但这并不是一个最理想的解决方案,因为其他属性可能会引起性能问题。

我们无法找到一个最直接的解决方案,但我们注意到 isURL 函数上的另一个检查 self.auth === undefined 解决了类似的问题;这就是为什么我们添加了遗留的 URL 对象属性 self.path === undefined: path,而非 WHATWG URL 对象属性。通过验证 path 是否为 undefined 来确定是否为 WHATWG URL 对象,这是更合理的。在我们的案例中,path不是 undefined,这意味着该对象不是 WHATWG URL 对象。

于是我们决定在 GitHub 上开启了一个讨论,提交一个包含修复方案的 Pull Request,并对 Yagiz Nizipli 的这一评论做了深入思考:“这有点像鸡生蛋还是蛋生鸡的问题,我们既不能检查 URL 对象,也不能正确检查 url。我们唯一能做的就是检查它们的行为 / 定义。”

好消息是,我们提出的修复方案将在 v20.6.0 版本中发布!对于那些继续使用 v18.x 的用户,这一修复方案很可能也会在 v18.18.0 版本中提供!

总结

Node.js、 request 库和 Sentry 库的变化导致 http(s) 模块误将一个普通对象识别为 URL 对象;这就是 http(s) 模块重置了 path 并引发了问题的原因。以下是 Node.js 和这两个库近期实现的一些变更:

  • Node.js 近期实现了将 URL 对象的检测从带有 origin 和 href 改为带有 protocol 和 href 以提高性能。
  • Request 库向 http(s) 模块的请求选项中添加了 href 属性(或许是为了调试目的)。
  • Sentry 库中添加了一个 protocol 属性以修复一个 bug。

我们的收获

我们在解决了 Node.js 20 升级中出现的意外请求问题后,希望与大家分享这样几点收获:

  • 分而治之

由多个第三方库交互而引发的问题可能很难调试与修复,但有一种方法是从底层开始,逐步追踪问题的源头,将其分解为更小的问题,以更容易处理。

  • 避免使用已弃用的库

这相当于在代码中放置了一个定时炸弹,它最终一定会爆炸。我们在实践中便遇到了这一问题。我们有计划替换已弃用的库,但在工作完成之前,这一定时炸弹就爆炸了。

  • 避免 patch 原生模块

在 Node.js 上 patch 原生模块(如 http(s))会使调试变得更加困难,尤其是对于第三方库,工程师们应避免这种做法。

  • 有时我们无法找到最完美的解决方案,我们能做的就是全力以赴。

Tubi 正在招聘

如你所见,Tubi 技术团队成功解决了在 Node.js 20 升级过程中出现的未预期的请求问题。我们快速锁定了由第三方依赖引起的 URL 对象检测变化是这一问题的根源。如果你和我们一样对这一类问题的解决感兴趣,欢迎加入我们!

Tubi 技术团队正在招聘,点击此处可查看 Tubi 的热招岗位。

作者:Zhuo Zhang, Tubi Senior Software Engineer

相关文章:

如何解决 Node.js 20 升级中未预期的请求问题

在 Tubi&#xff0c;我们使用 Node.js 为 Web/OTT 应用进行服务端渲染及代理请求。近来&#xff0c;为了从新版本的性能改进和新功能中受益&#xff0c;我们将 Node.js 从 14.x 版本升级到了 20.x。 升级像 Node.js 这样的基础设施绝非易事&#xff0c;尤其是有着许多第三方依…...

no tests were found

将带有Test的方法返回类型设为void...

泛型擦除是什么

//在编译阶段使用泛型,运行阶段取消泛型,就是擦除. //因为泛型其实只是在编译器中实现的而虚拟机并不认识泛型类项,所以要在虚拟机中将泛型类型进行擦除, //擦除是将泛型以其父类代替,如String变成了object等. //在使用的时候还是进行带强制类型转化,只不过这是比较安全的转换,…...

7、线性数据结构-切片

切片slice 容器容量可变&#xff0c;所以长度不能定死长度可变&#xff0c;元素个数可变底层必须依赖数组&#xff0c;可以理解它依赖于顺序表&#xff0c;表现也像个可变容量和长度顺序表引用类型&#xff0c;和值类型有区别 定义切片 var s1 []int //长度、容量为0的切片&…...

linux grub2 不引导修复 grub2-install:error:/usr/lib/grub/x86_64-efi/modinfo.sh

系统部署在物理机上&#xff0c;开机后一直pxe不进系统&#xff0c;怀疑GRUB丢失。 查看bios 里 采用uefi 启动方式&#xff0c; 无硬盘系统引导选项&#xff0c; 且BMC设置为硬盘永久启动也无效。 挂载光驱ISO进入救援模式,sda为系统盘&#xff0c;重装grub报错 grub2-inst…...

建筑楼宇智慧能源管理系统,轻松解决能源管理问题

随着科技的进步与人们节能减排意识的不断增强&#xff0c;建筑楼宇是当下节能减排的重要工具。通过能源管理平台解决能效管理、降低用能成本、一体化管控、精细化管理和服务提供有力支撑。 建筑楼宇智慧能源管理系统是一种利用先进手段&#xff0c;采用微服务架构&#xff0c;…...

【洛谷算法题】P5711-闰年判断【入门2分支结构】

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5711-闰年判断【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格式&a…...

ArcGIS10.8 连接 PostgreSQL 及遇到的两个问题

前提 以前同事用过我的电脑连PostgreSQL&#xff0c;失败了。当时不知道原因&#xff0c;只能使用GeoServer来发布数据了。现在终于搞明白了&#xff0c;原因是ArcGIS10.2版本太老&#xff0c;无法连接PostgreSQL9.4。参考这里 为了适应时代的发展&#xff0c;那我就用新的Ar…...

深入跨域 - 从初识到入门 | 京东物流技术团队

前言 跨域这两个字就像一块狗皮膏药一样黏在每一个前端开发者身上&#xff0c;无论你在工作上或者面试中无可避免会遇到这个问题。如果在网上搜索跨域问题&#xff0c;会出现许许多多方案&#xff0c;这些方案有好有坏&#xff0c;但是对于阐述跨域的原理和在什么情况下需要用…...

WebSocket真实项目总结

websocket websocket是什么? websocket是一种网络通讯协议。 websocket 是HTML5开始提供的一种在单个TCP链接上进行全双工通讯的协议。 为什么需要websocket? 初次接触websocket&#xff0c;都会带着疑惑去学习&#xff0c;既然已经有了HTTP协议&#xff0c;为什么还需要另一…...

Python 如何实现解释器(Interpreter)设计模式?什么是解释器设计模式?

什么是解释器&#xff08;Interpreter&#xff09;设计模式&#xff1f; 解释器&#xff08;Interpreter&#xff09;设计模式是一种行为型设计模式&#xff0c;它定义了一种语言文法的表示&#xff0c;并提供了一个解释器&#xff0c;用于解释语言中的句子。该模式使得可以定…...

单片机与PLC的区别有哪些?

单片机与PLC的区别有哪些? 什么是单片机&#xff1f; 单片机&#xff08;Microcontroller&#xff0c;缩写MCU&#xff09;是一种集成了中央处理器&#xff08;CPU&#xff09;、存储器和输入/输出接口等功能模块的微型计算机系统。它通常被用于嵌入式系统和控制系统中&#x…...

修改浏览器滚动条样式--ios同款

::-webkit-scrollbar{width: 5px;height: 5px; } ::-webkit-scrollbar-thumb{border-radius: 1em;background-color: rgba(50,50,50,.3); } ::-webkit-scrollbar-track{border-radius: 1em;background-color: rgba(50,50,50,.1); } 修改滚动条样式用到的CSS伪类&#xff1a; :…...

python自动化测试selenium核心技术3种等待方式详解

这篇文章主要为大家介绍了python自动化测试selenium的核心技术三种等待方式示例详解&#xff0c;有需要的朋友可以借鉴参考下&#xff0c;希望能够有所帮助&#xff0c;祝大家多多进步早日升职加薪 UI自动化测试过程中&#xff0c;可能会出现因测试环境不稳定、网络慢等情况&a…...

苹果手机照片如何导入电脑?无损快速的传输办法分享!

前些天小编的朋友联系到我&#xff0c;说是自己苹果手机里面的照片太多&#xff0c;有好几千张&#xff0c;不知道该怎么快而无损地传到电脑。我想遇到这种情况的不止是小编的朋友&#xff0c;生活中遇到手机照片导入电脑的同学不在少数。不管是苹果手机还是安卓手机&#xff0…...

csh 脚本批量处理文件并将文件扔给程序

文章目录 前言程序批量造 case 并将 cmd 扔给程序运行批量收集数据汇总 前言 Linux下我们经常会写一些shell脚本来辅助我们学习或者工作&#xff0c;从而提高效率。 之前就写过一篇博客&#xff1a;Linux下利用shell脚本批量产生内容有规律变化的文件 程序 批量造 case 并将…...

程序员技能成长树,程序员的曙光

一、背景 初创的计算机公司&#xff0c;主要低市场占有率和日益增长的市场规模之间的矛盾&#xff0c;此时只有一件事情&#xff0c;那就是快速抢占市场&#xff0c;在面对计算机飞速发展的时期&#xff0c;企业广泛的招聘计算机人才进行信息化项目建设&#xff0c;随着公司业…...

灰度图处理方法

做深度学习项目图像处理的时候常常涉及到灰度图处理&#xff0c;这里对自己处理灰度图的方式做一个记录&#xff0c;后续有更新的话会在此更新 一&#xff0c;多维数组可视化 将多维数组可视化为灰度图 img_gray Image.fromarray(img, modeL) # 实现array到image的转换,m…...

微信小程序:仅前端实现对象数组的模糊查询

效果 核心代码 //对数组进行过滤&#xff0c;返回数组中每一想满足name值包括变量query的 let result array.filter(item > { return item.name.includes(query); }); 完整代码 wxml <input type"text" placeholder"请输入名称" placeholder-styl…...

【done】剑指offer63:股票的最大利润

力扣188&#xff0c;https://leetcode.cn/problems/gu-piao-de-zui-da-li-run-lcof/description/&#xff08;注意&#xff1a;本题与主站 121 题相同&#xff1a;https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/&#xff09; 动态规划思路&#xff1b; 方…...

glm-5-free不输付费版!DMXAPIAI模型聚合平台,如何调用免费大模型API?

中关村论坛发布AutoGLM 沉思智能体&#xff0c;具备深度研究与电脑操作双重能力&#xff0c;GLM-5.1 编程能力与全球顶尖模型 Claude Opus 4.6 差距仅2.6 分&#xff0c;整体呈现技术迭代、商业化与资本市场的全面提速态势。智谱AI正式推出GLM-5-free开源模型&#xff0c;凭借与…...

大一C语言期末必考|程序结构+流程控制(详解+例题+易错点)一

&#x1f525;个人主页&#xff1a;北极的代码&#xff08;欢迎来访&#xff09; &#x1f3ac;作者简介&#xff1a;java后端学习者 ❄️个人专栏&#xff1a;苍穹外卖日记&#xff0c;SSM框架深入&#xff0c;JavaWeb ✨命运的结局尽可永在&#xff0c;不屈的挑战却不可须臾或…...

论文写作卡壳不用愁!这几款AI工具帮你高效赶稿

写论文最怕思路卡壳&#xff1f;大纲憋不出来、正文续写没头绪、降重改到崩溃&#xff0c;还担心AI生成痕迹过不了检测&#xff1f;以下几款实用AI写作工具直击本硕生核心需求&#xff0c;从初稿到答辩全流程辅助&#xff0c;让写作效率直接翻倍。 一、SpeedAI科研小助手&#…...

单线程 Redis 的高性能之道

引言Redis 以单线程模型处理网络请求与命令操作&#xff0c;却能在高并发场景下保持惊人的吞吐能力。这背后离不开三大基石&#xff1a;全内存存储、高效数据结构&#xff08;哈希表、跳表等&#xff09;以及 epoll 多路复用机制&#xff0c;让单线程能够高效处理海量连接。 随…...

终极指南:如何免费解锁Cursor Pro功能,彻底解决API限制问题

终极指南&#xff1a;如何免费解锁Cursor Pro功能&#xff0c;彻底解决API限制问题 【免费下载链接】cursor-free-vip [Support 0.45]&#xff08;Multi Language 多语言&#xff09;自动注册 Cursor Ai &#xff0c;自动重置机器ID &#xff0c; 免费升级使用Pro 功能: Youve …...

所有下载都一定要直接从个人服务器直接下载--------因为个人宽带的上传速度一点也不慢

可以看到居然速度高达10M/S如果你直接从云服务器下载速度就非常慢&#xff1a;这就是1M的宽带&#xff0c;所以很慢。所以如果是下载apk文件&#xff0c;一定要从自己的服务器直接下载&#xff1a;就是带10001端口号的个人服务器。...

强强联合:在快马平台用AI模型驱动你的下一代智能agent应用

最近在尝试用AI辅助开发时&#xff0c;发现了一个特别有意思的方向——智能agent框架。这类框架就像是AI应用的"骨架"&#xff0c;而平台内置的AI模型则为其注入了"灵魂"。今天想分享下在InsCode(快马)平台上实现的一个创作辅助agent&#xff0c;整个过程让…...

惯性导航解算及误差分析

目录 1.连续时间下三维运动的微分性质 1.1 旋转矩阵的微分方程 1.2 四元数的微分方程 1.3 旋转向量的微分方程 2.惯性导航解算 2.1 姿态更新 2.2 速度更新 2.3 位置更新 3.惯性导航误差分析 3.1 姿态误差微分方程 3.2 速度误差微分方程 3.3 位置误差方程 3.4 bias…...

Hi3519 DV500上跑YOLOv5n,从7秒到34毫秒:一个模型算子优化带来的200倍加速实战

Hi3519 DV500上YOLOv5n性能优化实战&#xff1a;从7秒到34毫秒的200倍加速秘诀 当我们在嵌入式设备上部署目标检测模型时&#xff0c;性能往往是最大的挑战。最近在Hi3519 DV500芯片上部署YOLOv5n模型的经历让我深刻体会到了这一点——最初的推理时间竟然长达7秒&#xff0c;完…...

nVisual设备板卡关联

在线模型库导入&#xff1a;ODF-12x2 这个型号的设备打开模型库点左侧模型搜索需要添加板卡设备型号&#xff0c;点击建模双击板卡搜索板卡名称点击绿色按钮添加添加完成点应用到实例...