We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
遇到这种问题实属无奈,前端的浏览器兼容性一直是一个让人头痛的问题
仅以此文记录如此尴尬无奈的一天。拿来替大伙儿解闷T_T
同事:快来!快来!线上出问题了!! 我:神马?! 咩?! WHAT?! なに?! 同事:是这次发布造成的吗? 我:回滚!回滚!(为什么要在快吃饭的时候掉链子!顾不上肚子了!快查吧) ......
一通混乱的对话后只能静下心来“扫雷”了。
回滚、代理、抓包、对比、单因子排查。。。
一套组合拳打完,大概一炷香的时间,终于找到了破绽,竟然是 ajax 同步回调的问题!不合理啊!不应该啊!还有这种操作?!
使用 ajax 做“同步”请求,此请求会返回一个 cookie,在success回调中读取此目标cookie 失败!ajax执行结束后 document.cookie 才会被更新
success
document.cookie
PC 端和 Android 端影响范围小,属于偶现。
IOS 端是重灾区,出来 Chrome 和 Safari 浏览器外的绝大多说浏览器都会出现此问题,并且 App 内置的 Webview 环境同样不能幸免。
在本同步请求回调内预读取本请求返回的 cookie 会产生问题。
半壁江山都沦陷了,我要这铁棒有何用!
小范围的兼容问题我姑且可以饶你,奈何你如此猖狂,怎能任你瞒天过海!
排除一些干扰项,还原其本质,我们分别用框架nej,jQuery和js写几个相同功能的“同步” demo,走着瞧着。。
nej
jQuery
js
【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
我们依次执行如下操作,
【nej.html】
【jquery.html】
【js.html】
咦?结果不一样!使用 nej 的第二次加载读取到了第一次 cookie。其他的两次均为获取到。
nej 依赖框架的加载是异步的,当同步请求发起时,dom 已经加载完毕,回调相应时,document.cookie已经呈“ready”状态,可读可写。但请求依然获取不到自身返回携带的 cookie。
而其他两种加载的机制阻塞了 dom 的加载,导致同步请求发起时,dom 尚未加载完成,回调相应时,document.cookie依然不可写。
我们将以上几个 html 文件的逻辑做下修改。 将同步请求推迟到 document 点击触发时再发起。 如下
$('document').click(function () { // TODO 发起同步请求 });
依然是上面的执行步骤,来看看此次的结果
结果和预期一样,本次请求无法获取本期返回的目标 cookie,请求回调执行后,目标cookie才会更新到document.cookie上。
在执行以上操作是,发现,【jquery.html】的执行结果时不时会有两种结果
一言不合看源码
我们在 jquery 的源码中看到,jquery 的success回调绑定在了 onload 事件上
onload
https://code.jquery.com/jquery-3.2.1.js :9533行
而我自己实现的和 nej 的实现均是将success回调绑定在了 onreadystatechange 事件上,唯一的区别就在于此
onreadystatechange
一个正向的 ajax 请求,会先触发两次onreadystatechange,在触发onload,或许原因在于document.cookie的同步有几率在onload事件触发前完成??I'm not sure.
只有问题没有方案的都是在耍流氓!
将回调方法中的 cookie 获取方法转化为异步操作。
_$ajax({ url: '/api', async: false, type: 'POST', success: function (result) { setTimeout(function(){ // do something 在此处获取 cookie 操作是安全的 },0) } });
没有把握的方案,我们是要斟酌着实施的。
如果你不能100%却被操作的安全性,那并不建议你强行使用 ajax 的同步操作,很多机制并不会像我们自以为是的那样理所应当。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
遇到这种问题实属无奈,前端的浏览器兼容性一直是一个让人头痛的问题
仅以此文记录如此尴尬无奈的一天。拿来替大伙儿解闷T_T
场景再现
一通混乱的对话后只能静下心来“扫雷”了。
回滚、代理、抓包、对比、单因子排查。。。
一套组合拳打完,大概一炷香的时间,终于找到了破绽,竟然是 ajax 同步回调的问题!不合理啊!不应该啊!还有这种操作?!
问题复现
一句话概括问题
影响范围
PC 端和 Android 端影响范围小,属于偶现。
IOS 端是重灾区,出来 Chrome 和 Safari 浏览器外的绝大多说浏览器都会出现此问题,并且 App 内置的 Webview 环境同样不能幸免。
在本同步请求回调内预读取本请求返回的 cookie 会产生问题。
追因溯果
纵向对比
排除一些干扰项,还原其本质,我们分别用框架
nej
,jQuery
和js
写几个相同功能的“同步” demo,走着瞧着。。【nej.html】使用 NEJ 库
【jquery.html】使用 jQuery 库
【js.html】自己实现的 ajax 请求函数
三个文件都是一样的,在html 加载完之后发起一个同步请求,该请求会返回一个 cookie,在回调中将
document.cookie
打印出来,检测是否已经在回调时写入的了 cookie。下面使用 node 实现这个可写 cookie 的服务。
【serve.js】
好了,万事大吉,run 一把
操作
我们依次执行如下操作,
结果
【nej.html】
【jquery.html】
【js.html】
咦?结果不一样!使用 nej 的第二次加载读取到了第一次 cookie。其他的两次均为获取到。
原因
nej 依赖框架的加载是异步的,当同步请求发起时,dom 已经加载完毕,回调相应时,
document.cookie
已经呈“ready”状态,可读可写。但请求依然获取不到自身返回携带的 cookie。而其他两种加载的机制阻塞了 dom 的加载,导致同步请求发起时,dom 尚未加载完成,回调相应时,
document.cookie
依然不可写。单因子对照
我们将以上几个 html 文件的逻辑做下修改。
将同步请求推迟到 document 点击触发时再发起。
如下
依然是上面的执行步骤,来看看此次的结果
结果
【nej.html】
【jquery.html】
【js.html】
结果和预期一样,本次请求无法获取本期返回的目标 cookie,请求回调执行后,目标cookie才会更新到
document.cookie
上。特例
在执行以上操作是,发现,【jquery.html】的执行结果时不时会有两种结果
另外一种几率较小,但也会出现
产生原因
我们在 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.问题结论
解决方案
方案1 - 明修栈道暗度陈仓
将回调方法中的 cookie 获取方法转化为异步操作。
方案2 - 不抵抗政策
没有把握的方案,我们是要斟酌着实施的。
如果你不能100%却被操作的安全性,那并不建议你强行使用 ajax 的同步操作,很多机制并不会像我们自以为是的那样理所应当。
The text was updated successfully, but these errors were encountered: