一行代码,把项目搞崩溃了!
共 3494字,需浏览 7分钟
·
2024-05-15 13:48
最近我们团队的同学在开发中发生了一件 “有意思” 的事情,那就是通过一行代码,让网站卡死了,真的是离了大谱。团队同学写了一篇记录,希望能长个记性吧~
背景
今天下午,老鱼简历告警群里突然提示了几个下载简历失败的提示。
我看到后,心里一紧,赶紧打开页面看看我能不能下载,结果我这里下载是正常的。于是我就感觉事情不简单,赶紧本地启动项目调试,我本以为本地启动后控制台会有报错,但是实际上并没有,没办法,只能使用绝招:二分法先定位到出问题代码。漫长的缩短问题代码后,最后问题代码找到了
/**
* 判断是否为链接
* @param urlString 接受一个字符串
* @returns 一个 boolean 值,true 表示是链接,false 表示不是链接
*/
export const isLink = (urlString: string) => {
const regex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
return regex.test(urlString);
};
说实话看到是因为这段代码的我是这样的:
我本来是不会正则的,这个是 AI 帮我写的判断链接的方法。我当时就光测试它是否能判断出一个字符串是否是链接,而忽略了特殊字符串是否会有性能问题。最后,我给大家复现一下这个bug 最小代码示例:
/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test('https://www.suse.com/nbswebapp/createSummary.jsp?')
只要你在游览器控制台输入这个,这个页面立马卡死。
问题原因分析
那这是为什么呢?听听万能的 AI 这么说的:在JavaScript中使用正则表达式时,如果正则表达式的设计不当,特别是在处理复杂的字符串或者特定的模式时,可能会导致性能问题,甚至使浏览器卡死。这种现象通常称为“灾难性回溯”(Catastrophic Backtracking)。在你提供的正则表达式中:
/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
这个表达式用于匹配URL,其中包含了几个可选的组件和量词(如*
和?
),这些都可能导致正则表达式引擎进行大量的回溯尝试,尤其是当输入字符串在某些位置上与模式不匹配时。具体来说,问题可能出现在([\/\w \.-]*)*
这部分。这里使用了两个嵌套的量词(*
外面还有一个*
),这种嵌套的量词是造成灾难性回溯的常见原因。当输入的URL在最后一部分(即[\/\w \.-]*
应该匹配的部分)稍有不符时,正则表达式引擎可能会尝试所有可能的分割方式来匹配整个模式,这会导致处理时间急剧增加。为了避免这种问题,可以尝试优化正则表达式的结构,避免使用嵌套的量词,或者使用更具体的字符类匹配来减少潜在的回溯。例如,可以修改为:
/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]+)?\/?$/
这里,我将内部的*
修改为+
,使得这部分至少匹配一次,减少了嵌套量词的使用,这应该可以帮助减轻浏览器的负担。总之,设计正则表达式时需要注意避免复杂的嵌套和过度的通配符使用,特别是在处理可能非常长或复杂的字符串时。这样不仅可以避免浏览器卡死的问题,还可以提高应用的响应速度和用户体验。实测,。这个正则是有问题的:
我现在是用新的检测字符串是否是链接的方法,充足测试下来没有问题:
/**
* 判断字符串是否为链接
*/
export function isLink(urlString: string): boolean {
const pattern = new RegExp(
'^(https?:\\/\\/)?' + // 协议
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // 域名
'((\\d{1,3}\\.){3}\\d{1,3}))' + // 或IP(v4)地址
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // 端口和路径
'(\\?[;&a-z\\d%_.~+=-]*)?' + // 查询参数
'(\\#[-a-z\\d_]*)?$',
'i',
); // 锚点
return !!pattern.test(urlString);
}
怎么避免?
这次出现这个问题的原因有两个
-
经验不足:如果我知道,不好的正则出导致灾难性回溯的话,我在拿到 AI 给我写的正则,我就会问它给我的正则是否会导致灾难性回溯。 -
没有充足的测试:如果我的项目有对这种工具方法的充足的测试,应该也不会产生这个 bug 了。
总结
遇到 bug 不要慌,从简单到难的使用排查问题的方法。先定位到问题。例如:我遇到bug,先定位前端问题还是后端问题,再定位问题的大的位置,逐渐缩小范围,最终找到问题的位置。然后解决问题。有没有觉得这其实就是使用二分法的思想来定位问题。找到问题的代码了,那其实就胜利一大半了,剩下的就是写出正确的代码,做充足的测试,最后复盘这次 bug,以后不要再犯同样的错就好了!
👇🏻 点击下方阅读原文,获取鱼皮往期编程干货。
往期推荐
我的编程学习小圈子
我做了个网站,帮你写出满分简历
我学计算机的四年,共勉
双非本,投递4000+份简历,上岸了!
我开源了一套 RPC 框架,学爆它!
耗时几个月,我们做的小工具上线啦!