Home  >  Article  >  Web Front-end  >  About synchronizing ajax cookies

About synchronizing ajax cookies

一个新手
一个新手Original
2017-10-24 09:41:372064browse

Preface

It is really helpless to encounter this kind of problem. Front-end browser compatibility has always been a headache.

This article only records such an embarrassing and helpless day. . Use it to relieve everyone's boredom T_T

Reappearance of the scene

Colleague: Come on! Come on! Something went wrong online! !
Me: What?! What?! WHAT?! Nako?!
Colleague: Is it caused by this release?
Me: Roll back! rollback! (Why do you want to lose track when you are about to eat! You can’t take care of your stomach! Check it out quickly)
......

I can only calm down after a chaotic conversation." Minesweeper".

Rollback, proxy, packet capture, comparison, single factor troubleshooting. . .

After finishing a set of combo punches, it took about a stick of incense to finally find the flaw. It turned out to be a problem with the ajax synchronization callback! Unreasonable! It shouldn’t be! Is there such an operation? !

Recurrence of the problem

Summary of the problem in one sentence

Use ajax to make a "Synchronization" request. This request will return a cookie. successFailed to read this target cookie in the callback! document.cookie will be updated only after ajax execution is completed.

Affected scope

The PC and Android terminals have a small impact and are occasional occurrences.

IOS is the hardest hit area. Most browsers other than Chrome and Safari will have this problem, and the built-in Webview environment of the App is also not immune.

Pre-reading the cookie returned by this request within this synchronous request callback will cause problems.

Half the country has fallen, what use do I need this iron rod for!

Chasing the cause and effect

I can forgive you for the small-scale compatibility issue, but you are so arrogant, how can I let you hide the truth!

Vertical comparison

To eliminate some interference items and restore its essence, we use the frameworks nej, jQuery and js respectively Write several "synchronization" demos with the same function and wait and see. .

[nej.html]Use NEJ library

<!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]Use jQuery library

<!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 request function implemented by yourself

<!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>

The three files are the same. After the html is loaded, a synchronous request is initiated. The request will return a cookie. In the callback, document.cookie will be printed out to check whether the callback is already in progress. The cookie is written at the time.

The following uses node to implement this writable cookie service.
【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);

Okay, everything is fine, run it

$ node serve.js

Operation

We perform the following operations in sequence,

  1. Use the ios QQ browser to clear all caches

  2. Load one of the pages and observe whether there is a target cookie output

  3. Perform a refresh operation, observe whether there is a target cookie output, compare the timestamp of the cookie output, and confirm whether it is the synchronization result of the last cookie rather than the cookie obtained by this request.

  4. Clear all Cache, switch the target html file, and execute steps 2, 3, and 4 in a loop

Result

[nej.html]

  • Pure environment loading, the target cookie was not read

  • Refresh the load, the cookie returned by the previous request was read

[jquery. html】

  • Pure environment loading, the target cookie is not read

  • Refresh loading, the target cookie is not read

[js.html]

  • Pure environment loading, the target cookie was not read

  • Refreshed loading, not Read the target cookie

Eh? The result is different! The second load using nej reads the first cookie. The other two times were obtained.

Reason

nej The loading of the dependent framework is asynchronous. When the synchronous request is initiated, the dom has been loaded, and when the callback responds, document.cookiehas been presented as "ready" ” status, readable and writable. But the request still cannot obtain the cookie returned by itself.

About synchronizing ajax cookies

The other two loading mechanisms block the loading of dom, causing the dom not to be loaded when the synchronization request is initiated. When the callback responds, document.cookie is still not writable.

Single factor control

We will modify the logic of the above several html files.
Delay the synchronization request until the document is clicked and triggered.
As follows

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

Still the above execution steps, let’s take a look at the results

Results

[nej.html]

  • Pure environment loading, the target cookie was not read

  • Refresh loading, the cookie returned by the previous request was read

[jquery.html]

  • Pure environment loading, the target cookie is not read

  • Refresh the load, read the last request return Cookie

[js.html]

  • Pure environment loading, the target cookie

    was not read
  • 刷新加载,读取到上一次请求返回的 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行

About synchronizing ajax cookies

而我自己实现的和 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 的同步操作,很多机制并不会像我们自以为是的那样理所应当。



The above is the detailed content of About synchronizing ajax cookies. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn