關於同步 ajax 的 cookie問題
前言
遇到這種問題實屬無奈,前端的瀏覽器相容性一直是個讓人頭痛的問問題
僅以此文記錄如此尷尬無奈的一天。拿來替大夥兒解悶T_T
場景再現
同事:快來!快來!線上出問題了! !
我:神馬?! 咩?! WHAT?! なに?!
同事:是這次發布造成的嗎?
我:回滾!復原! (為什麼要在快吃飯的時候掉鍊子!顧不上肚子了!快查吧)
......
一通混亂的對話後只能靜下心來“掃雷」了。
回滾、代理、抓包、對比、單因子排查。 。 。
一組組合拳頭打完,大概一炷香的時間,終於找到了破綻,竟然是 ajax 同步回調的問題!不合理啊!不應該啊!還有這種操作? !
問題復現
一句話概括問題
使用ajax 做「同步」請求,此請求會傳回一個cookie,在
success
回呼中讀取此目標cookie 失敗! ajax執行結束後document.cookie
才會被更新
影響範圍
PC 端和 Android 端影響範圍小,屬於偶現。
IOS 端是重災區,出來 Chrome 和 Safari 瀏覽器外的絕大多說瀏覽器都會出現此問題,且 App 內建的 Webview 環境同樣不能倖免。
在本同步請求回呼內預先讀取本請求傳回的 cookie 會產生問題。
半壁江山都淪陷了,我要這鐵棒有何用!
追因溯果
小範圍的兼容問題我姑且可以饒你,奈何你如此猖獗,怎能任你瞞天過海!
縱向對比
排除一些幹擾項,還原其本質,我們分別用框架nej
,jQuery
和js
寫幾個相同功能的「同步」 demo,走著瞧著。 。
【nej.html】使用NEJ 函式庫
<!DOCTYPE html> <html> <head> <title>nej</title> <meta charset="utf-8" /> </head> <body> test <script src="http://nej.netease.com/nej/src/define.js?pro=./"></script> <script> define([ '{lib}util/ajax/xdr.js' ], function () { var _j = NEJ.P('nej.j'); _j._$request('/api', { sync: true, method: 'POST', onload: function (_data) { alert("cookie:\n" + document.cookie) } }); }); </script> </body> </html>
【jquery.html】使用jQuery 函式庫
<!DOCTYPE html> <html> <head> <title>jquery</title> <meta charset="utf-8" /> </head> <body> jquery <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script> $.ajax({ url: '/api', async: false, method: 'POST', success: function (result) { alert("cookie:\n" + document.cookie) } }); </script> </body> </html>
【js.html】自己實作的ajax 請求函式
<!DOCTYPE html> <html> <head> <title>JS</title> <meta charset="utf-8" /> </head> <body> js <script> var _$ajax = (function () { /** * 生产XHR兼容IE6 */ var createXHR = function () { if (typeof XMLHttpRequest != "undefined") { // 非IE6浏览器 return new XMLHttpRequest(); } else if (typeof ActiveXObject != "undefined") { // IE6浏览器 var version = [ "MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp", ]; for (var i = 0; i < version.length; i++) { try { return new ActiveXObject(version[i]); } catch (e) { return null } } } else { throw new Error("您的系统或浏览器不支持XHR对象!"); } }; /** * 将JSON格式转化为字符串 */ var formatParams = function (data) { var arr = []; for (var name in data) { arr.push(name + "=" + data[name]); } arr.push("nocache=" + new Date().getTime()); return arr.join("&"); }; /** * 字符串转换为JSON对象,兼容IE6 */ var _getJson = (function () { var e = function (e) { try { return new Function("return " + e)() } catch (n) { return null } }; return function (n) { if ("string" != typeof n) return n; try { if (window.JSON && JSON.parse) return JSON.parse(n) } catch (t) { } return e(n) }; })(); /** * 回调函数 */ var callBack = function (xhr, options) { if (xhr.readyState == 4 && !options.requestDone) { var status = xhr.status; if (status >= 200 && status < 300) { options.success && options.success(_getJson(xhr.responseText)); } else { options.error && options.error(); } //清空状态 this.xhr = null; clearTimeout(options.reqTimeout); } else if (!options.requestDone) { //设置超时 if (!options.reqTimeout) { options.reqTimeout = setTimeout(function () { options.requestDone = true; !!this.xhr && this.xhr.abort(); clearTimeout(options.reqTimeout); }, !options.timeout ? 5000 : options.timeout); } } }; return function (options) { options = options || {}; options.requestDone = false; options.type = (options.type || "GET").toUpperCase(); options.dataType = options.dataType || "json"; options.contentType = options.contentType || "application/x-www-form-urlencoded"; options.async = options.async; var params = options.data; //创建 - 第一步 var xhr = createXHR(); //接收 - 第三步 xhr.onreadystatechange = function () { callBack(xhr, options); }; //连接 和 发送 - 第二步 if (options.type == "GET") { params = formatParams(params); xhr.open("GET", options.url + "?" + params, options.async); xhr.send(null); } else if (options.type == "POST") { xhr.open("POST", options.url, options.async); //设置表单提交时的内容类型 xhr.setRequestHeader("Content-Type", options.contentType); xhr.send(params); } } })(); _$ajax({ url: '/api', async: false, type: 'POST', success: function (result) { alert("cookie:\n" + document.cookie) } }); </script> </body> </html>
三個檔案都是一樣的,在html 載入完之後發起一個同步請求,該請求會傳回一個cookie,在回調中將document.cookie
列印出來,偵測是否已經在回調時寫入的了cookie。
下面使用 node 實作這個可寫 cookie 的服務。
【serve.js】
var express = require("express"); var http = require("http"); var fs = require("fs"); var app = express(); var router = express.Router(); router.post('/api', function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With"); res.header("Set-Cookie", ["target=ccccccc|" + new Date()]); res.end('ok'); }); router.get('/test1', function (req, res, next) { fs.readFile("./nej.html", function (err, data) { res.end(data); }); }); router.get('/test2', function (req, res, next) { fs.readFile("./jquery.html", function (err, data) { res.end(data); }); }); router.get('/test3', function (req, res, next) { fs.readFile("./js.html", function (err, data) { res.end(data); }); }); app.use('/', router); http.createServer(app).listen(3000);
好了,萬事大吉,run 一把
$ node serve.js
操作
我們依序執行如下操作,
#使用ios 端QQ 瀏覽器,清空所有快取
載入其中一個頁面,觀察是否有目標cookie 輸出
執行刷新操作,觀察是否有目標cookie 輸出,比較cookie 輸出的時間戳,確認是否為上次cookie 的同步結果而非本次請求獲取的cookie,
清空所有緩存,切換目標html 文件,循環執行2,3,4步驟
結果
【nej.html】
純淨環境加載,未讀取到目標cookie
刷新加載,讀取到上一次請求返回的cookie
【jquery. html】
純淨環境加載,未讀取到目標cookie
刷新加載,未讀取到目標cookie
【js.html】
純淨環境加載,未讀取到目標cookie
- ##刷新加載,未讀取到目標cookie
document.cookie已經呈現「ready ”狀態,可讀可寫。但請求依然取得不到自身回傳攜帶的 cookie。
document.cookie仍不可寫。
將同步請求延後到 document 點擊觸發時再發起。
如下
$('document').click(function () { // TODO 发起同步请求 });
- #純淨環境加載,未讀取到目標cookie
- 刷新加載,讀取到上一次請求返回的cookie
- 純淨環境加載,未讀取到目標cookie
- 刷新加載,讀取到上一次請求返回的cookie
- #純淨環境加載,未讀取到目標cookie
刷新加载,读取到上一次请求返回的 cookie
结果和预期一样,本次请求无法获取本期返回的目标 cookie,请求回调执行后,目标cookie才会更新到document.cookie
上。
特例
在执行以上操作是,发现,【jquery.html】的执行结果时不时会有两种结果
纯净环境加载,未读取到目标 cookie
刷新加载,读取到上一次请求返回的 cookie
另外一种几率较小,但也会出现纯净环境加载,读取到目标 cookie
刷新加载,读取到目标 cookie
产生原因
一言不合看源码
我们在 jquery 的源码中看到,jquery 的success
回调绑定在了 onload
事件上
https://code.jquery.com/jquery-3.2.1.js :9533行
而我自己实现的和 nej 的实现均是将success
回调绑定在了 onreadystatechange
事件上,唯一的区别就在于此
一个正向的 ajax 请求,会先触发两次onreadystatechange
,在触发onload
,或许原因在于document.cookie
的同步有几率在onload
事件触发前完成??I'm not sure.
问题结论
在 PC 端,Android 端,IOS 端Chrome、Safari 浏览器环境下,ajax 的同步请求的回调方法中,取到本请求返回的 cookie 失败几率低
IOS 端,QQ 浏览器、App 内置Webview浏览器环境下,失败率极高。
解决方案
只有问题没有方案的都是在耍流氓!
方案1 - 明修栈道暗度陈仓
将回调方法中的 cookie 获取方法转化为异步操作。
_$ajax({ url: '/api', async: false, type: 'POST', success: function (result) { setTimeout(function(){ // do something 在此处获取 cookie 操作是安全的 },0) } });
方案2 - 不抵抗政策
没有把握的方案,我们是要斟酌着实施的。
如果你不能100%却被操作的安全性,那并不建议你强行使用 ajax 的同步操作,很多机制并不会像我们自以为是的那样理所应当。
以上是關於同步 ajax 的 cookie問題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

標題:解決jQueryAJAX請求出現403錯誤的方法及程式碼範例403錯誤是指伺服器禁止存取資源的請求,通常會導致出現這個錯誤的原因是請求缺少權限或被伺服器拒絕。在進行jQueryAJAX請求時,有時會遇到這種情況,本文將介紹如何解決這個問題,並提供程式碼範例。解決方法:檢查權限:首先要確保請求的URL位址是正確的,同時驗證是否有足夠的權限來存取該資

使用PHP和Ajax建置自動完成建議引擎:伺服器端腳本:處理Ajax請求並傳回建議(autocomplete.php)。客戶端腳本:發送Ajax請求並顯示建議(autocomplete.js)。實戰案例:在HTML頁面中包含腳本並指定search-input元素識別碼。

jQuery是一個受歡迎的JavaScript函式庫,用來簡化客戶端端的開發。而AJAX則是在不重新載入整個網頁的情況下,透過發送非同步請求和與伺服器互動的技術。然而在使用jQuery進行AJAX請求時,有時會遇到403錯誤。 403錯誤通常是伺服器禁止存取的錯誤,可能是由於安全性原則或權限問題導致的。在本文中,我們將討論如何解決jQueryAJAX請求遭遇403錯誤

如何解決jQueryAJAX報錯403的問題?在開發網頁應用程式時,經常會使用jQuery來發送非同步請求。然而,有時在使用jQueryAJAX時可能會遇到錯誤代碼403,表示伺服器禁止存取。這種情況通常是由伺服器端的安全性設定所導致的,但可以透過一些方法來解決這個問題。本文將介紹如何解決jQueryAJAX報錯403的問題,並提供具體的程式碼範例。一、使

隨著網路的普及,我們使用瀏覽器進行上網已經成為一種生活方式。在日常使用瀏覽器過程中,我們常會遇到需要輸入帳號密碼的情況,如網購、社交、郵件等。這些資訊需要瀏覽器記錄下來,以便下次造訪時不需要再次輸入,這時候Cookie就派上了用場。什麼是Cookie? Cookie是指由伺服器端發送到使用者瀏覽器上並儲存在本地的一種小型資料文件,它包含了一些網站的使用者行為

越來越多的用戶開始升級win11系統,由於每個用戶的使用習慣不同,還是有不少用戶在使用ie11瀏覽器,那麼win11系統用不了ie瀏覽器,該怎麼辦呢? windows11還支援ie11嗎?下面就來看看解決方法。 win11無法使用ie11瀏覽器的解決方法1、先右鍵開始選單,選擇「命令提示字元(管理員)」開啟。 2.開啟之後,直接輸入“Netshwinsockreset”,回車確定。 3.確定之後再輸入「netshadvfirewallreset&rdqu

使用Ajax從PHP方法取得變數是Web開發中常見的場景,透過Ajax可以實作頁面無需刷新即可動態取得資料。在本文中,將介紹如何使用Ajax從PHP方法中取得變量,並提供具體的程式碼範例。首先,我們需要寫一個PHP檔案來處理Ajax請求,並傳回所需的變數。下面是一個簡單的PHP檔案getData.php的範例程式碼:

Cookie設定的常見問題及解決方案,需要具體程式碼範例隨著網路的發展,Cookie作為一種最常見的常規技術,已廣泛應用於網站和應用程式中。 Cookie,簡單來說,是一種儲存在使用者電腦上的資料文件,可用於儲存使用者在網站上的信息,包括登入名稱、購物車內容、網站首選項等等。 Cookie對於開發人員來說是必不可少的工具,但同時,Cookie設定常常會遇到
