目录
前言
场景再现
问题复现
一句话概括问题
影响范围
追因溯果
纵向对比
操作
结果
原因
单因子对照
特例
产生原因
问题结论
解决方案
方案1 - 明修栈道暗度陈仓
方案2 - 不抵抗政策
首页 web前端 js教程 关于同步 ajax 的 cookie问题

关于同步 ajax 的 cookie问题

Oct 24, 2017 am 09:41 AM
ajax cookie ie

前言

遇到这种问题实属无奈,前端的浏览器兼容性一直是一个让人头痛的问问题

仅以此文记录如此尴尬无奈的一天。拿来替大伙儿解闷T_T

场景再现

同事:快来!快来!线上出问题了!!
我:神马?! 咩?! WHAT?! なに?!
同事:是这次发布造成的吗?
我:回滚!回滚!(为什么要在快吃饭的时候掉链子!顾不上肚子了!快查吧)
......

一通混乱的对话后只能静下心来“扫雷”了。

回滚、代理、抓包、对比、单因子排查。。。

一套组合拳打完,大概一炷香的时间,终于找到了破绽,竟然是 ajax 同步回调的问题!不合理啊!不应该啊!还有这种操作?!

问题复现

一句话概括问题

使用 ajax 做“同步”请求,此请求会返回一个 cookie,在success回调中读取此目标cookie 失败!ajax执行结束后 document.cookie 才会被更新

影响范围

PC 端和 Android 端影响范围小,属于偶现。

IOS 端是重灾区,出来 Chrome 和 Safari 浏览器外的绝大多说浏览器都会出现此问题,并且 App 内置的 Webview 环境同样不能幸免。

在本同步请求回调内预读取本请求返回的 cookie 会产生问题。

半壁江山都沦陷了,我要这铁棒有何用!

追因溯果

小范围的兼容问题我姑且可以饶你,奈何你如此猖狂,怎能任你瞒天过海!

纵向对比

排除一些干扰项,还原其本质,我们分别用框架nej,jQueryjs写几个相同功能的“同步” 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([
            &#39;{lib}util/ajax/xdr.js&#39;
        ], function () {
            var _j = NEJ.P(&#39;nej.j&#39;);
            _j._$request(&#39;/api&#39;, {
                sync: true,
                method: &#39;POST&#39;,
                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: &#39;/api&#39;,
            async: false,
            method: &#39;POST&#39;,
            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: &#39;/api&#39;,
            async: false,
            type: &#39;POST&#39;,
            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(&#39;/api&#39;, 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(&#39;ok&#39;);
});

router.get(&#39;/test1&#39;, function (req, res, next) {
    fs.readFile("./nej.html", function (err, data) {
        res.end(data);
    });
});

router.get(&#39;/test2&#39;, function (req, res, next) {
    fs.readFile("./jquery.html", function (err, data) {
        res.end(data);
    });
});

router.get(&#39;/test3&#39;, function (req, res, next) {
    fs.readFile("./js.html", function (err, data) {
        res.end(data);
    });
});

app.use(&#39;/&#39;, router);
http.createServer(app).listen(3000);
登录后复制

好了,万事大吉,run 一把

$ node serve.js
登录后复制

操作

我们依次执行如下操作,

  1. 使用 ios 端 QQ 浏览器,清空所有缓存

  2. 加载其中一个页面,观察是否有目标 cookie 输出

  3. 执行刷新操作,观察是否有目标 cookie 输出,比较 cookie 输出的时间戳,确认是否为上次 cookie 的同步结果而非本次请求获取的 cookie,

  4. 清空所有缓存,切换目标 html 文件,循环执行2,3,4步骤

结果

【nej.html】

  • 纯净环境加载,未读取到目标 cookie

  • 刷新加载,读取到上一次请求返回的 cookie

【jquery.html】

  • 纯净环境加载,未读取到目标 cookie

  • 刷新加载,未读取到目标 cookie

【js.html】

  • 纯净环境加载,未读取到目标 cookie

  • 刷新加载,未读取到目标 cookie

咦?结果不一样!使用 nej 的第二次加载读取到了第一次 cookie。其他的两次均为获取到。

原因

nej 依赖框架的加载是异步的,当同步请求发起时,dom 已经加载完毕,回调相应时,document.cookie已经呈“ready”状态,可读可写。但请求依然获取不到自身返回携带的 cookie。

关于同步 ajax 的 cookie问题

而其他两种加载的机制阻塞了 dom 的加载,导致同步请求发起时,dom 尚未加载完成,回调相应时,document.cookie依然不可写。

单因子对照

我们将以上几个 html 文件的逻辑做下修改。
将同步请求推迟到 document 点击触发时再发起。
如下

$(&#39;document&#39;).click(function () {
    // TODO 发起同步请求
});
登录后复制

依然是上面的执行步骤,来看看此次的结果

结果

【nej.html】

  • 纯净环境加载,未读取到目标 cookie

  • 刷新加载,读取到上一次请求返回的 cookie

【jquery.html】

  • 纯净环境加载,未读取到目标 cookie

  • 刷新加载,读取到上一次请求返回的 cookie

【js.html】

  • 纯净环境加载,未读取到目标 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行

关于同步 ajax 的 cookie问题

而我自己实现的和 nej 的实现均是将success回调绑定在了 onreadystatechange 事件上,唯一的区别就在于此

一个正向的 ajax 请求,会先触发两次onreadystatechange,在触发onload,或许原因在于document.cookie的同步有几率在onload事件触发前完成??I'm not sure.

问题结论

  1. 在 PC 端,Android 端,IOS 端Chrome、Safari 浏览器环境下,ajax 的同步请求的回调方法中,取到本请求返回的 cookie 失败几率低

  2. IOS 端,QQ 浏览器、App 内置Webview浏览器环境下,失败率极高。

解决方案

只有问题没有方案的都是在耍流氓!

方案1 - 明修栈道暗度陈仓

将回调方法中的 cookie 获取方法转化为异步操作。

_$ajax({
    url: &#39;/api&#39;,
    async: false,
    type: &#39;POST&#39;,
    success: function (result) {
        setTimeout(function(){
            // do something 在此处获取 cookie 操作是安全的
        },0)
    }
});
登录后复制

方案2 - 不抵抗政策

没有把握的方案,我们是要斟酌着实施的。

如果你不能100%却被操作的安全性,那并不建议你强行使用 ajax 的同步操作,很多机制并不会像我们自以为是的那样理所应当。



以上是关于同步 ajax 的 cookie问题 的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

解决jQuery AJAX请求遇到403错误的方法 解决jQuery AJAX请求遇到403错误的方法 Feb 20, 2024 am 10:07 AM

标题:解决jQueryAJAX请求出现403错误的方法及代码示例403错误是指服务器禁止访问资源的请求,通常会导致出现这个错误的原因是请求缺少权限或者被服务器拒绝。在进行jQueryAJAX请求时,有时候会遇到这种情况,本文将介绍如何解决这个问题,并提供代码示例。解决方法:检查权限:首先要确保请求的URL地址是正确的,同时验证是否有足够的权限来访问该资

解决jQuery AJAX请求403错误的方法 解决jQuery AJAX请求403错误的方法 Feb 19, 2024 pm 05:55 PM

jQuery是一个流行的JavaScript库,用于简化客户端端的开发。而AJAX则是在不重新加载整个网页的情况下,通过发送异步请求和与服务器交互的技术。然而在使用jQuery进行AJAX请求时,有时会遇到403错误。403错误通常是服务器禁止访问的错误,可能是由于安全策略或权限问题导致的。在本文中,我们将讨论如何解决jQueryAJAX请求遭遇403错误

win11无法使用ie11浏览器怎么办?(win11用不了ie浏览器) win11无法使用ie11浏览器怎么办?(win11用不了ie浏览器) Feb 10, 2024 am 10:30 AM

越来越多的用户开始升级win11系统,由于每个用户的使用习惯不同,还是有不少用户在使用ie11浏览器,那么win11系统用不了ie浏览器,该怎么办呢?windows11还支持ie11吗?下面就来看看解决办法。win11无法使用ie11浏览器的解决方法1、首先右键开始菜单,选择“命令提示符(管理员)”打开。2、打开之后,直接输入“Netshwinsockreset”,回车确定。3、确定之后再输入“netshadvfirewallreset&rdqu

浏览器cookie的存储位置详解 浏览器cookie的存储位置详解 Jan 19, 2024 am 09:15 AM

随着互联网的普及,我们使用浏览器进行上网已经成为一种生活方式。在日常使用浏览器过程中,我们经常会遇到需要输入账号密码的情况,如网购、社交、邮件等。这些信息需要浏览器记录下来,以便于下次访问时不需要再次输入,这时候Cookie就派上了用场。什么是Cookie?Cookie是指由服务器端发送到用户浏览器上并存储在本地的一种小型数据文件,它包含了一些网站的用户行为

如何解决jQuery AJAX报错403的问题? 如何解决jQuery AJAX报错403的问题? Feb 23, 2024 pm 04:27 PM

如何解决jQueryAJAX报错403的问题?在开发网页应用程序时,经常会使用jQuery来发送异步请求。然而,有时候在使用jQueryAJAX时可能会遇到错误代码403,表示服务器禁止访问。这种情况通常是由服务器端的安全设置所导致的,但可以通过一些方法来解决这个问题。本文将介绍如何解决jQueryAJAX报错403的问题,并提供具体的代码示例。一、使

PHP 与 Ajax:构建一个自动完成建议引擎 PHP 与 Ajax:构建一个自动完成建议引擎 Jun 02, 2024 pm 08:39 PM

使用PHP和Ajax构建自动完成建议引擎:服务器端脚本:处理Ajax请求并返回建议(autocomplete.php)。客户端脚本:发送Ajax请求并显示建议(autocomplete.js)。实战案例:在HTML页面中包含脚本并指定search-input元素标识符。

如何使用Ajax从PHP方法中获取变量? 如何使用Ajax从PHP方法中获取变量? Mar 09, 2024 pm 05:36 PM

使用Ajax从PHP方法中获取变量是Web开发中常见的场景,通过Ajax可以实现页面无需刷新即可动态获取数据。在本文中,将介绍如何使用Ajax从PHP方法中获取变量,并提供具体的代码示例。首先,我们需要编写一个PHP文件来处理Ajax请求,并返回所需的变量。下面是一个简单的PHP文件getData.php的示例代码:

Win10打开IE自动跳转到Edge怎么取消_IE浏览器页面自动跳转的解决办法 Win10打开IE自动跳转到Edge怎么取消_IE浏览器页面自动跳转的解决办法 Mar 20, 2024 pm 09:21 PM

近期不少的win10用户们在使用电脑浏览器的时候发现自己的ie浏览器总是自动的跳转到edge浏览器,那么win10打开ie自动跳转edge怎么关闭?。下面就让本站来为用户们来仔细的介绍一下win10打开ie自动跳转edge关闭方法吧。1、我们登录edge浏览器,点击右上角...,找下拉的设置选项。2、我们进入设置后,在左侧栏点击默认浏览器。3、最后我们在兼容性中,勾选不允许IE模式下重新加载网站,重启ie浏览器即可。

See all articles