/**
* 基础禁止调试代码
*/
(()=> {
function ban() {
setInterval(()=> {
debugger;
}, 50);
}
try {
ban();
} catch (err) { }
})();
(()=> {
function ban() {
setInterval(()=> { debugger; }, 50);
}
try {
ban();
} catch (err) { }
})();
// 加密前
(()=> {
function ban() {
setInterval(()=> {
Function('debugger')();
}, 50);
}
try {
ban();
} catch (err) { }
})();
// 加密后
eval(function(c,g,a,b,d,e){d=String;if(!"".replace(/^/,String)){for(;a--;)e[a]=b[a]||a;b=[function(f){return e[f]}];d=function(){return"\w+"};a=1}for(;a--;)b[a]&&(c=c.replace(new RegExp("\b"+d(a)+"\b","g"),b[a]));return c}('(()=>{1 0(){2(()=>{3("4")()},5)}6{0()}7(8){}})();',9,9,"block function setInterval Function debugger 50 try catch err".split(" "),0,{}));
(()=> {
function block() {
if (window.outerHeight - window.innerHeight > 200 || window.outerWidth - window.innerWidth > 200) {
document.body.innerHTML="检测到非法调试,请关闭后刷新重试!";
}
setInterval(()=> {
(function () {
return false;
}
['constructor']('debugger')
['call']());
}, 50);
}
try {
block();
} catch (err) { }
})();
禁止的方法有了,如何反禁止呢? 你想到了吗?
文最初发表于HTTP Toolkit网站,经原作者 Tim Perry 授权由 InfoQ 中文站翻译分享。
万物都会有终结,HTTP API 也不例外。不论你的 API 今天看上去多么伟大,迟早有一天你会想发布一个全新的版本,新版本能更好地解决相同问题,在各方面可能都会有所改善,但是它因为有了新参数,与旧版本也无法兼容,或者你只是想彻底关闭旧的 API。总而言之,你现在的 API 不会永远存在。
但是,这并非轻而易举就能完成的,因为你的 API 有客户端。如果你关闭端点、参数或整个 API 而没有做出恰当的警告的话,那他们肯定会非常不爽。
那么,该怎样安全地关闭 API,让你的用户尽可能地感到轻松愉快呢?
在这方面,我们有正确的做事方式,包括两个新的头信息草案,它们正在被新的 IETF “Building Blocks for HTTP APIs”工作组进行标准化,旨在形成一个精确的过程。我们了解一下。
初始第一步:检查相关的 API 是否真的有客户端。
希望你能有某些 API 的度量指标,至少在某些地方存有日志。如果没有的话,那把它们添加上,如果你有这些东西的话,并且你能确定没有人再使用这个 API 了,那么恭喜你,你赢了。现在,你就可以把它关掉,删掉代码,不要再管这篇文章了,好好睡一觉。
下一个问题,如果比较遗憾,你无法去睡觉的话,那就要问问自己,除了关闭这个 API,还有没有其他方案。你关闭的所有东西都有可能破坏别人的代码,并且会消耗他们的时间来修复这些问题。如果 API 能继续运行的话,对客户端的生态系统和整个 web 的健康都是有好处的。
在很多场景下,旧的 API 可以在内部进行转换,透明地转化成对新 API 的调用,这样可以避免维护两个完全独立的版本。这是Stripe的API版本管理方式的一个基本组成部分,他们在所有发生变化的 API 中都包含了转换,以确保对不兼容的旧版本 API 的请求能继续像以前那样运行,根据需要自动转换请求和响应从而可以使用较新的代码。
这样的转换并不总是可行的,而且如果永远这样做的话会带来明显的额外复杂性,但是如果你可以做到这一点的话,就能为用户提供非常有价值的稳定性,并且可以节省大量废弃旧版本或维护旧版本相关的工作。
但是,如果这个服务/端点/参数已经用到了生产环境,而且继续支持它是不现实的做法,那么它必须要被淘汰。
要实现这一点,我们就要有一个计划。我们首先要问自己三个关键的问题:
计划准备就绪之后,我们就该把它告诉人们了。
首先,要把这一决定告诉人们。
发邮件到邮件列表,在 Twitter 或微博上发帖,如果有 API 规范的话,对其进行更新(比如,OpenAPI 在operations和parameters上有一个deprecated字段),并在相关的在线文档上大声强调这一点。
你应该包含上文提到的所有信息:他们应该做些什么作为替代方案,你建议他们什么时候开始迁移以及他们必须要进行迁移的最后期限(如果已经确定期限的话)。
在将这些信息告诉给人们后,接下来就该告诉计算机,而这就是新的 IETF 头信息可以发挥作用的地方。
Deprecation头信息能告诉客户端请求的资源现在依然像以前那样运行,但是这种方式已经不再推荐使用了。通过一个简单的 HTTP 头信息,我们就可以声明这一点:
Deprecation: true
另外,我们还能提供一个日期。这个日期告诉用户他们何时该开始进行迁移。这个日期可以是一个过去时间(这代表他们应该立即开始迁移),也能是将来时间(通常这意味着他们要迁移到的新环境还没有准备就绪)。如下所示:
Deprecation: Thu, 21 Jan 2021 23:59:59 GMT
如果你要废弃整个端点或服务,那么你可以在每个响应中都带上这样的头信息。如果你想要废弃的是一个具体的特性,可能是一个参数、请求方法或者请求体中的某个特定字段的话,那么你应该在该特性被使用的时候才在响应中包含这个头信息。
为了给客户端更多的信息,我们还可以使用Link HTTP 响应头信息链接至端点或人类易读的文档。在同一个Link头信息中,我们可以包含多个这样的链接,只需要使用逗号进行分割即可(后面我们会看到一个完整的例子)。该规范定义了四个与 API 废弃相关的链接:
我们可以为 deprecation 链接指向一个人类易于阅读的描述:
Link: <https://developer.example.com/deprecation>; rel="deprecation"; type="text/html"
这是告诉用户发生了什么以及他们该怎么办的主要方式。你应该始终使用它。如果还没有完整的详情和最终的关闭日期,那么即使只是一个占位符,这也是很有帮助的。在这种情况下,不要忘记让用户订阅更新,这可以采用邮件列表、RSS 或其他类似的方式来实现。
如果你希望客户端转移至 API 相同端点的最新版本,那么可以使用该链接指向它,如下所示:
Link: <https://api.example.com/v10/customers>; rel="latest-version"
如果你的 API 有多个可用的版本,通常最好每次向前迁移一个版本,而不是直接从最老的、现已废弃的版本跳到最新的版本。为了帮助解决这个问题,我们链接至已废弃版本的下一个版本,而不是最新版本,如下所示:
Link: <https://api.example.com/v2/customers>; rel="successor-version"
如果该 API 没有新的等价版本,用户最好迁移到一个完全不同的资源,它可能是一个很好的替代方案,那么我们使用 alternate 链接来指明这一点,如下所示:
Link: <https://api.example.com/v2/users/123/clients>; rel="alternate"
如果你知道了 API 何时完全关闭的话,那么就应该添加一个Sunset头信息。
Sunset 头信息告诉客户端 API 何时会停止运行。这是一个强制的截止时间:API 客户端必须要在这个日期前进行迁移,我们承诺在这个时间前不会破坏任何事情。
在这里,我们必须要提供一个时间,它应该是一个未来的时间。不过,如果它是一个过去的时间,这也是可以的:此时就相当于说“这个 API 会在任意时刻关闭,你需要立即停止使用它”。它如下所示:
Sunset: Tue, 20 Jul 2021 23:59:59 GMT
这非常简单,它不仅可以用到 API 关闭的场景中:我们能用它来标记将来 URL 迁移的 HTTP 重定向,或者表明特定 URL 有限的生命周期(适用于临时性的内容,或者适用于具有监管要求的特定资源,比如数据保留策略)。它所说明的就是“这个端点可能在该日期后不会再按照你的预期运行,请做好准备”。
该规范也提供了一个 Sunset 链接的关系。按照设计,它会链接至关于关闭特定端点更加详细的信息(如果你有 deprecation 链接的话,它们可能会是同一个)或者关于服务的通用 Sunset 策略。如下所示:
Link: <http://developer.example.com/our-sunset-policy>;rel="sunset";type="text/html"
在这里我们也要指出,通用的 Sunset 策略是非常有用的!Sunset 策略会告诉客户端,当我们关闭端点的时候(比如,一年后替代方案上线),用户该如何确保他们能得知这一情况(邮件列表、状态页面、HTTP 头信息等)以及他们通常应该做些什么(更新、检查文档、遵从Link头信息)。
如果马上就要废弃某个 API 的话,添加这样的链接作用其实不大,但是如果你能在一年前就将其发布出去的话,你的客户端可能已经为此做好了准备。
除此之外,发布 Sunset/Deprecation 策略的最好时间就是现在。如果你恰好正以某种方式编写 Deprecation 文档的话,这么做是值得考虑的。
按照设计,这些组成部分能很好地协作。例如,为了表明某个最近废弃的 API,该 API 会在 6 个月内彻底关闭,我们要链接至文档并提供下一个版本的直接链接,那么我们应该在响应中包含如下的头信息链接:
Deprecation: Thu, 21 Jan 2021 23:59:59 GMT
Sunset: Tue, 20 Jul 2021 23:59:59 GMT
Link: <https://api.example.com/v2/customers>; rel="successor-version",
<https://developer.example.com/shutting-down-customers-v1>; rel="deprecation"
如果所有这些都已经准备到位,并且 sunset 截止时间已过,那么我们就可以将 API 关闭了。
但是,这并不意味着你需要立即且彻底消灭该 API。渐进式关闭能有助于确保任何使用该 API 的所有客户端都有最后的机会在它彻底消失前得到最后一次警告。GitHub 在 2018 年移除一些加密支持的时候曾经这样做:首先禁用一个小时,然后启用它,最后在两周后彻底禁用了它。
这里还有另外一个技巧:安卓在 2015 年为已废弃的原生API增加了越来越多的延迟,在彻底关闭 API 前,最终达到了 16 秒的等待。
这些渐进式的关闭为那些错过截止日期的客户端提供了一些灵活性,并且能帮助那些没有注意到废弃时间点的客户端,从而能在 API 彻底关闭之前处理一些问题。
不管采用哪种方式,只要你尽了最大的努力去沟通关于 API 关闭的事情,那么现在就可以关闭端点/特性/整个服务,删除代码,然后睡个好觉。
像这样小心谨慎地进行废弃和关闭,可以让你的客户端尽可能清楚地知道他们该如何依赖你的 API,何时需要采取行动,以及他们需要做什么。这种变更可能是一件大事儿,这些信息是很重要的。
这些新的草案头信息让我们不仅可以与人类沟通,还能将这些信息暴露给自动化系统。随着这些头信息的普及,我很高兴地开始看到有更多的工具建立在它们之上。通用的 HTTP 客户端可以根据这些数据自动记录有用的警告日志,API 生成器本身也能根据 API 规范处理越来越多的问题,而 HTTP 调试器(如HTTP Toolkit)可以在截获的实时流量中为你突出显示废弃端点的使用。这是一个令人激动的时刻,我们可以开始安全地关闭 API 了!
需要注意的是,这些头信息是 HTTP 规范的草案。在最终确定前,它们有可能会发生变化。也就是说,它们经历了几轮修改,从现在开始,它们不太可能发生巨大的变化,现在能广泛测试它们了。
不过这也意味着还有时间进行反馈! 如果你对它们的工作方式和如何更好地运行有想法的话,请与“Building Blocks for HTTP APIs”工作组联系。你可以给邮件列表发邮件:httpapi@ietf.org,或者在这里查看之前的邮件列表讨论。
原文链接:
https://httptoolkit.tech/blog/how-to-turn-off-your-old-apis/
这两天碰到一个需求:在用户刷新页面或者关闭页面的时候,前端要给后台发一条请求,释放该页面的授权占用。
分析了一下,这不就是在页面卸载时发请求嘛,三下五除二就实现一版:
window.addEventListener("beforeunload", ()=> {
let oReq=new XMLHttpRequest();
oReq.open("POST", "http://127.0.0.1:1991/loginout");
oReq.send(JSON.stringify({name: "编程三昧"}));
测试发现:
既然异步 Ajax 不行,那就试试同步的吧,结果直接报错了:
搜了一下,解释如下:
Chrome now disallows synchronous XHR during page dismissal when the page is being navigated away from or closed by the user. This involves the following events (when fired on the path of page dismissal): beforeunload, unload, pagehide, and visibilitychange.
概括起来就是:对现在的 Chrome 来说,在页面导航离开或者被用户关闭时,不允许发送同步 XHR 请求,涉及到的事件有:beforeunload、unload、pagehide 和 visibilitychange。
虽然问题没解决,但是却长知识了,这波不太亏!
后来通过搜索,看到有一个接口是专门来干这事的,那就是 navigator.sendBeacon() 。
这个方法主要用于满足统计和诊断代码的需要,这些代码通常尝试在卸载(unload)文档之前向web服务器发送数据。
navigator.sendBeacon(url, data);
当用户代理成功把数据加入传输队列时,sendBeacon() 方法将会返回 true,否则返回 false。
既然有了接口,那实现起来就简单了。
window.addEventListener("beforeunload", (e)=> {
const data={name: "编程三昧"};
window.navigator.sendBeacon("http://127.0.0.1:1991/loginout", JSON.stringify(data));
});
不管是刷新页面还是关闭页面,后台都能接收到前端发送过来的请求,完美实现需求。
浏览器现在功能越来越强大,支持的 API 也越来越丰富,放在之前很难实现的功能,现在可能就是轻而易举的事,还是要多关注技术动态。
~
~本文完,感谢阅读!
~
学习有趣的知识,结识有趣的朋友,塑造有趣的灵魂!
大家好,我是〖编程三昧〗的作者 隐逸王,我的公众号是『编程三昧』,欢迎关注,希望大家多多指教!
你来,怀揣期望,我有墨香相迎! 你归,无论得失,唯以余韵相赠!
知识与技能并重,内力和外功兼修,理论和实践两手都要抓、两手都要硬!
*请认真填写需求信息,我们会在24小时内与您取得联系。