이런 문제가 발생하면 정말 속수무책입니다. 프론트엔드 브라우저 호환성은 언제나 골치 아픈 문제였습니다.
이 글은 그토록 당황스럽고 무기력했던 날만을 기록합니다. 모두의 심심함을 달래는데 활용해보세요 T_T
동료: 어서! 어서 해봐요! 온라인에서 문제가 발생했습니다. !
나: 대체 뭐야?! 뭐야?!
동료: 이번 출시로 인한 건가요?
나: 롤백하세요! 롤백! (먹으려고 할 때 왜 체인을 떨어뜨리는가! 배를 돌볼 수 없잖아! 빨리 확인)
...
혼란스러운 대화 끝에 진정할 수 있는 건 '지뢰 제거' 뿐이다. ".
롤백, 프록시, 패킷 캡처, 비교, 단일 요소 문제 해결. . .
콤보 펀치 세트를 마친 후 마침내 결함을 찾는 데 향 한 개 정도가 걸렸습니다. Ajax 동기화 콜백에 문제가 있는 것으로 밝혀졌습니다! 무모한! 그러면 안 됩니다! 그런 수술이 있나요? !
ajax를 사용하여 "동기화" 요청을 하면
성공
에서 이 대상 쿠키를 반환합니다. > 콜백이 실패했습니다!document.cookie
는 ajax 실행이 완료될 때까지 업데이트되지 않습니다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
咦?结果不一样!使用 nej 的第二次加载读取到了第一次 cookie。其他的两次均为获取到。
nej 依赖框架的加载是异步的,当同步请求发起时,dom 已经加载完毕,回调相应时,document.cookie
已经呈“ready”状态,可读可写。但请求依然获取不到自身返回携带的 cookie。
而其他两种加载的机制阻塞了 dom 的加载,导致同步请求发起时,dom 尚未加载完成,回调相应时,document.cookie
PC와 Android에 미치는 영향 범위는 작으며 간헐적으로 발생합니다.
IOS가 가장 큰 타격을 받는 분야입니다. Chrome과 Safari를 제외한 대부분의 브라우저에서 이 문제가 발생하며, 앱에 내장된 Webview 환경도 면역되지 않습니다.
이 동기 요청 콜백 내에서 이 요청에 의해 반환된 쿠키를 미리 읽으면 문제가 발생할 수 있습니다.
국토의 절반이 망했는데 이 쇠막대가 무슨 소용이 있겠는가!수직 비교
nej
, jQuery
및 js
프레임워크를 사용하여 여러 항목을 작성합니다. 동일 "동기화" 기능의 데모입니다. 기다려 보겠습니다. . 【nej.html】NEJ 라이브러리 사용
$('document').click(function () { // TODO 发起同步请求 });
_$ajax({ url: '/api', async: false, type: 'POST', success: function (result) { setTimeout(function(){ // do something 在此处获取 cookie 操作是安全的 },0) } });
【js.html】직접 구현한 ajax 요청 기능
rrreeedocument.cookie
를 인쇄하여 쿠키를 반환하는 동기 요청을 시작하여 콜백 중에 쿠키가 기록되었는지 확인합니다. 다음은 노드를 사용하여 쓰기 가능한 쿠키 서비스를 구현합니다.
【serve.js】rrreee
Operationdocument.cookie
가 이미 "준비" 상태입니다. 상태를 읽고 쓸 수 있습니다. 그러나 요청은 여전히 자체적으로 반환된 쿠키를 얻을 수 없습니다. 🎜🎜🎜 🎜다른 두 가지 로드 메커니즘은 DOM 로드를 차단하여 동기 요청이 시작될 때 DOM이 로드되지 않게 만듭니다. 콜백이 응답하면 document.cookie
는 여전히 쓸 수 없습니다. 🎜🎜Single Factor Control🎜🎜위 HTML 파일의 로직을 수정하겠습니다. 🎜문서를 클릭하고 트리거될 때까지 동기화 요청을 지연하세요. 🎜다음🎜rrreee🎜은 여전히 위의 실행 단계입니다. 결과를 살펴보겠습니다🎜🎜Results🎜🎜[nej.html]🎜🎜🎜🎜순수 환경 로딩, 대상 쿠키를 읽지 못했습니다🎜🎜🎜🎜새로 고침 로딩 , 이전 요청에서 반환된 쿠키 읽기🎜🎜🎜🎜[jquery.html]🎜🎜🎜🎜순수한 환경 로딩, 대상 쿠키가 읽히지 않음🎜🎜🎜🎜로드를 새로 고치고 이전 요청에서 반환된 쿠키 읽기🎜🎜 🎜🎜【js.html】🎜🎜🎜🎜순수 환경 로딩, 대상 쿠키를 읽지 않음🎜刷新加载,读取到上一次请求返回的 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浏览器环境下,失败率极高。
只有问题没有方案的都是在耍流氓!
将回调方法中的 cookie 获取方法转化为异步操作。
_$ajax({ url: '/api', async: false, type: 'POST', success: function (result) { setTimeout(function(){ // do something 在此处获取 cookie 操作是安全的 },0) } });
没有把握的方案,我们是要斟酌着实施的。
如果你不能100%却被操作的安全性,那并不建议你强行使用 ajax 的同步操作,很多机制并不会像我们自以为是的那样理所应当。
위 내용은 Ajax 동기화의 쿠키 문제에 대하여의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!