解决 Script Error 的另类思路

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

本文由小芭乐发表

前端的同学如果用 window.onerror 事件做过监控,应该知道,跨域的脚本会给出 "Script Error." 提示,拿不到具体的错误信息和堆栈信息。

这里读者可以跟我一起做一个实验,来深入了解这个事情。先做一下实验准备:

app.js

创建一个 Node APP,只做静态服务器,提供两个端口用于做跨域实验。

const express = require('express');const app = express();app.use(express.static('./public'));app.listen(3000);
app.listen(4000);

public/index.html

创建一个静态页面,监听 window.onerror 事件,并且输出事件的堆栈。同时分别加载两个域的 JS 文件。

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>Script Error Test</title><meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body><button id="btn-3000">3000</button><button id="btn-4000">4000</button><div><pre id="info"></pre></div>
</body>
<script>
window.addEventListener('error', evt => {const info = evt.error ? evt.error.stack : evt.message;document.querySelector('#info').textContent = info;
});
</script>
<script src="http://127.0.0.1:3000/at3000.js"></script>
<script src="http://127.0.0.1:4000/at4000.js"></script>
</html>

public/at3000.js

创建一个在 3000 端口执行的脚本,监听 3000 按钮的点击事件,并且抛出一个异常:

const btn3k = document.querySelector('#btn-3000');
btn3k.addEventListener('click', () => {throw new Error('Fail 3000');
});

public/at4000.js

同样的,创建一个在 4000 端口执行的脚本:

const btn4k = document.querySelector('#btn-4000');
btn4k.addEventListener('click', () => {throw new Error('Fail 4000');
});

复现 Script Error

这个时候,我们启动 Node APP:node app.js,然后访问 http://127.0.0.1:3000

分别点击按钮 3000 和 4000,我们发现,同域下面的 3000 按钮点击后,异常消息可以捕获到。而跨域的 4000 按钮,只有一个 Script Error。

img点击 3000 按钮

img点击 4000 按钮

我们复现了 "Script Error."!

有同学举手,我知道,只要加一个跨域头就可以了!

Access-Control-Allow-Origin

没错,我们可以给静态文件服务器加上跨域协议头:

app.use(express.static('./public', {setHeaders(res) {res.set('access-control-allow-origin', res.req.get('origin'));res.set('access-control-allow-credentials', 'true');}
}));

同时,加载 JS 的时候,加上跨域声明:

<script src="http://127.0.0.1:4000/at4000.js" crossorigin="anonymous"></script>

这样,无论 3000 还是 4000 按钮,我们点击都能获得异常信息。

但是,这个方案有两个致命的弱点:

  • 如果 JS 声明了 crossorigin="anonymous" 但是响应头没有正确,JS 会直接无法执行
  • 我们并不总是有静态服务器的配置权限,跨域头不是想加就能加

img声明了 crossorigin 但是没有响应跨域头的 JS

另类思路

如果我告诉你,可以不加跨域头,只是在 JS 文件加载之前加载一个「特别的」JS,一样可以达到目的,你信不信?

<script src="http://127.0.0.1:3000/inject-event-target.js"></script>
<script src="http://127.0.0.1:3000/at3000.js"></script>
<script src="http://127.0.0.1:4000/at4000.js"></script>

这个神奇的 inject-event-target.js 可以让我们在没有跨域头的情况下,拿到 4000 按钮事件处理器的执行异常信息。

img点击 3000

img点击 4000

如果你觉得神奇,请点赞后,继续往下阅读。这个魔法 JS,其实也很简单:

const originAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, listener, options) {const wrappedListener = function (...args) {try {return listener.apply(this, args);}catch (err) {throw err;}}return originAddEventListener.call(this, type, wrappedListener, options);
}

原理也非笔者原创,而是从这篇文章学习而来。

简单解释一下:

  • 改写了 EventTarget 的 addEventListener 方法;
  • 对传入的 listener 进行包装,返回包装过的 listener,对其执行进行 try-catch;
  • 浏览器不会对 try-catch 起来的异常进行跨域拦截,所以 catch 到的时候,是有堆栈信息的;
  • 重新 throw 出来异常的时候,执行的是同域代码,所以 window.onerror 捕获的时候不会丢失堆栈信息;

实际上,利用包装 addEventListener,我们还可以达到「扩展堆栈」的效果:

img堆栈扩展效果

我们不仅知道异常堆栈,而且还知道导致该异常的事件处理器,是在何处添加进去的。实现这个效果,也很简单:

 (() => {const originAddEventListener = EventTarget.prototype.addEventListener;EventTarget.prototype.addEventListener = function (type, listener, options) {
+    // 捕获添加事件时的堆栈
+    const addStack = new Error(`Event (${type})`).stack;const wrappedListener = function (...args) {try {return listener.apply(this, args);}catch (err) {
+        // 异常发生时,扩展堆栈
+        err.stack += '\n' + addStack;throw err;}}return originAddEventListener.call(this, type, wrappedListener, options);}})();

同样的道理,我们也可以对 setTimeout、setInterval、requestAnimationFrame 甚至 XMLHttpRequest 做这样的拦截,得到一些我们本来得不到的信息。

此文已由作者授权腾讯云+社区发布,更多原文请点击

搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!

转载于:https://my.oschina.net/qcloudcommunity/blog/2963894

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/450123.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

迅雷影音怎样 1.5倍速度播放

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 看视频 觉得播放速度太慢&#xff0c;想让1.5速度播放可以这样设置&#xff1a; 点击快进按钮&#xff0c;点一次变为1.1倍&#xff0c…

git pull时冲突的几种解决方式

仅结合本人使用场景&#xff0c;方法可能不是最优的 1. 忽略本地修改&#xff0c;强制拉取远程到本地 主要是项目中的文档目录&#xff0c;看的时候可能多了些标注&#xff0c;现在远程文档更新&#xff0c;本地的版本已无用&#xff0c;可以强拉 git fetch --allgit reset --h…

Linux:echo命令详解

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 echo命令 用于字符串的输出 格式 echo string使用echo实现更复杂的输出格式控制 1.显示普通字符串: echo "It is a test"这里…

学习 shell脚本之前的基础知识

见 : http://www.92csz.com/study/linux/12.htm【什么是shell】 简单点理解&#xff0c;就是系统跟计算机硬件交互时使用的中间介质&#xff0c;它只是系统的一个工具。实际上&#xff0c;在shell和计算机硬件之间还有一层东西那就是系统内核了。打个比方&#xff0c;如果把计算…

Git cherry-pick后再merge出现一个“奇怪”的现象

背景描述&#xff1a;有的时候基于一个master branch拉出一个独立feature分支做开发时&#xff0c;两条分支都在并行开发&#xff0c;如果master分支增加了某些功能&#xff0c;解决了某些关键bug&#xff0c;而独立feature分支不需要所有的增加的commit&#xff0c;只需要某一…

Sublime Text3中文环境设置

Sublime Text3中文环境设置 1、首先打开安装好的的Sublime软件,选择Preferences下面的Package Contorol选项出现弹窗方框 2、在弹窗输入install package,选择对应&#xff08;默认第一个&#xff0c;如图这个&#xff09;命令点击进入;安装的时候&#xff0c;左下角会有进度条显…

C/C++图形化编程(2)

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 站在巨人的肩上是为了超过巨人&#x…

Git clone之后你的硬盘上究竟发生了什么?

网上关于Git的使用有太多的博客&#xff0c;文章在讲解了&#xff0c;大部分是在讲解命令的用法&#xff0c;剩下一部分则在讲解git的内部原理&#xff0c;看过讲解基础命令使用的文章后&#xff0c;正常的开发使用是没有什么问题的了&#xff0c;而如果想更深入的了解git“高级…

感知机模型的对偶形式[转载]

转自:https://blog.csdn.net/jaster_wisdom/article/details/78240949#commentBox 1.区分一下易混淆的两个概念&#xff0c;梯度下降和随机梯度下降&#xff1a; 梯度下降&#xff1a;一次将误分类集合中所有误分类点的梯度下降&#xff1b; 随机梯度下降&#xff1a;随机选取一…

go语言渐入佳境[6]-operator运算符

运算符和其他语言一样&#xff0c;Go语言支持多种运算符&#xff0c;用于对变量进行运算。12345678910111213package mainimport "fmt"func main(){ //math() //relation() //logic() //wei() Assign()}算术运算符123456789101112func math(){ a : 4 b:2 fmt.Printf(…

记录腾讯云中矿机病毒处理过程(重装系统了fu*k)

2019-1-21日常上班的周一 刚想学学kafka&#xff0c;登录与服务器看看把&#xff0c;谁知ssh特别慢&#xff0c;很奇怪&#xff0c;我以为是我网速问题&#xff0c;断了wifi&#xff0c;换了网线&#xff0c;通过iterm想要ssh rootx.x.x.x&#xff0c;但是上不去&#xff1f; 就…

对象反序列化出现类型不匹配的情况(spring-boot-devtools)

目前在做springboot项目的shiro session redis共享功能。但是有一个对象我把它放到redis中之后再取出来就会出现类型不匹配的异常 AuthorizationUser user (AuthorizationUser) cache.getSuper(key); 异常信息&#xff1a; java.lang.ClassCastException: com.ch.evaluation.a…

音视频多媒体协议相关资料汇总

未知问题&#xff1a; 编码&#xff0c;封装&#xff0c;协议的区别&#xff1a; 如何将TS源流重新封装并通过P2P协议传输在安卓终端和苹果终端播放封装 介绍完了视频编码后&#xff0c;再来介绍一些封装。沿用前面的比喻&#xff0c;封装可以理解为采用哪种货车去运输&…

谷歌地图VS苹果地图:大数据领域竞争

摘要&#xff1a;iOS 6推出之后&#xff0c;争议最大的是什么&#xff1f;苹果地图。苹果地图成为人们抨击iOS 6的首选&#xff0c;而苹果放弃谷歌地图选择自力更生是迫不得已。苹果和谷歌之间的竞争领域可以用三个字来概括&#xff1a;大数据。谷歌拥有大数据&#xff0c;而苹…

微软正在考虑将Windows默认浏览器改为Chromium

据外媒报道&#xff0c;微软正在构建一个基于Chromium的浏览器&#xff0c;代号为Anaheim&#xff0c;目标是取代Windows中的Edge。 Microsoft Edge是微软于2015年推出的浏览器&#xff0c;该浏览器取代了IE成为Windows 10的默认浏览器。尽管如此&#xff0c;Microsoft Edge并没…

三次握手的第三个ACK包丢了,会发生什么?

转载自三次握手的第三个ACK包丢了&#xff0c;TCP的处理方式 三次握手的第三个ACK包丢了&#xff0c;客户端认为连接建立&#xff0c;写数据时&#xff0c;会触发RST。 当Client端收到Server的SYNACK应答后&#xff0c;其状态变为ESTABLISHED&#xff0c;并发送ACK包给Server&a…

一分钟了解四层/七层反向代理

转自公众号&#xff1a;架构师之路今天花几分钟简单和大家解释一下。场景&#xff1a;访问用户通过proxy请求被访问的真实服务器 路径&#xff1a;用户 -> proxy -> real-server什么是代理&#xff1f; 回答&#xff1a;[proxy]代表[访问用户]&#xff0c;此时proxy是代理…

tcp建立连接为什么需要三次握手

这是一个看似很“简单”的问题&#xff0c;但貌似并没有一个官方统一的答案。搜索了相关的资料&#xff0c;列举出一些答案。 以下部分转载自&#xff1a;tcp建立连接为什么需要三次握手 在《计算机网络》一书中其中有提到&#xff0c;三次握手的目的是“为了防止已经失效的连…

Tcp三次握手和四次挥手状态图

三次握手 四次挥手 正常情况下 同时挥手 SYN攻击&#xff1a; 在三次握手过程中&#xff0c;Server发送SYN-ACK之后&#xff0c;收到Client的ACK之前的TCP连接称为半连接&#xff08;half-open connect&#xff09;&#xff0c;此时Server处于SYN_RCVD状态&#xff0c;当…

财务自由之路——为什么选择淘宝(下)

接上文~一、淘宝之前的大佬们是怎么试错的?我们看看在淘宝之前的大佬们是怎么试错迭代产品的。都知道飞机是莱特兄弟发明的&#xff0c;但很少有人知道为什么是他们。在内燃机发明后的很长一段时间内全球各地发明家都在投入研究飞机&#xff0c;莱特兄弟相对于其他竞争者&…