/**
* 基礎禁止調試代碼
*/
(()=> {
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小時內與您取得聯系。