Heim  >  Artikel  >  Web-Frontend  >  Über das Cookie-Problem der Ajax-Synchronisierung

Über das Cookie-Problem der Ajax-Synchronisierung

一个新手
一个新手Original
2017-10-24 09:41:372055Durchsuche

Vorwort

Es ist wirklich hilflos, auf ein solches Problem zu stoßen.

Dieser Artikel beschreibt nur einen so peinlichen und hilflosen Tag. Benutzen Sie es, um allen die Langeweile zu vertreiben T_T

Stellen Sie die Szene nach

Kollege: Komm schon! Aufleuchten! Im Internet ist ein Fehler aufgetreten! !
Ich: Wow?! WAS?!
Kollege: Liegt es an dieser Veröffentlichung?
Ich: Roll back! Rollback! (Warum verlierst du die Beherrschung, wenn du gerade essen willst? Mach dir keine Sorgen um deinen Magen! Schau es dir schnell an)
...

Ich kann mich erst nach einer Weile beruhigen verwirrendes Gespräch. „Minesweeper“.

Rollback, Proxy, Paketerfassung, Vergleich, Einzelfaktor-Fehlerbehebung. . .

Nachdem ich eine Reihe von Combo-Schlägen ausgeführt hatte, dauerte es etwa ein Räucherstäbchen, um den Fehler endlich zu finden. Es stellte sich heraus, dass es sich um ein Problem mit dem Ajax-Synchronisations-Callback handelte! Unvernünftig! Das sollte nicht sein! Gibt es eine solche Operation? !

Wiedergabe des Problems

Zusammenfassung des Problems in einem Satz

Verwenden Sie Ajax, um eine „Synchronisierungsanfrage“ zu stellen gibt ein Cookie zurück. successDieses Ziel-Cookie konnte im Rückruf nicht gelesen werden! document.cookie wird erst aktualisiert, wenn die Ajax-Ausführung abgeschlossen ist

Einflussbereich

Der Einflussbereich auf PC und Android ist gering und gelegentlich.

IOS ist der am stärksten betroffene Bereich, außer Chrome und Safari, und auch die integrierte Webview-Umgebung der App ist nicht immun.

Das Vorablesen des von dieser Anfrage zurückgegebenen Cookies innerhalb dieses synchronen Anfragerückrufs führt zu Problemen.

Das halbe Land ist gefallen, welchen Nutzen habe ich mit dieser Eisenstange!

Ursache und Wirkung aufspüren

Das kleine Kompatibilitätsproblem kann ich dir verzeihen, aber du bist so arrogant, wie kannst du es verbergen!

Vertikaler Vergleich

Um einige Interferenzelemente zu beseitigen und ihr Wesen wiederherzustellen, verwenden wir die Frameworks nej, jQuery und js, um mehrere „Synchronisationen“ mit dem zu schreiben Gleiche Funktion. Demo, abwarten. .

[nej.html] Verwenden Sie die NEJ-Bibliothek

<!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] Verwenden Sie die jQuery-Bibliothek

<!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] Implementieren Sie Ihre eigene Ajax-Anfrage Die Funktionen

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

sind in den drei Dateien gleich. Nach dem Laden der HTML-Datei wird eine synchrone Anfrage initiiert. Im Rückruf wird document.cookie ausgegeben um festzustellen, ob der Rückruf bereits ausgeführt wird. Das Cookie wird zu diesem Zeitpunkt geschrieben.

Im Folgenden wird Node verwendet, um diesen beschreibbaren Cookie-Dienst zu implementieren.
【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, alles ist in Ordnung, lass uns

$ node serve.js

Operation

Wir führen die folgenden Operationen nacheinander aus,

  1. Verwenden Sie den iOS-QQ-Browser und leeren Sie alle Caches

  2. Laden Sie eine der Seiten und beobachten Sie, ob eine Ziel-Cookie-Ausgabe erfolgt

  3. Führen Sie einen Aktualisierungsvorgang durch, beobachten Sie, ob eine Ziel-Cookie-Ausgabe vorliegt, vergleichen Sie den Zeitstempel der Cookie-Ausgabe und bestätigen Sie, ob es sich um das Synchronisierungsergebnis des letzten Cookies und nicht um das durch diese Anfrage erhaltene Cookie handelt ,

  4. Alle Caches löschen, die Ziel-HTML-Datei wechseln und die Schritte 2, 3 und 4 in einer Schleife ausführen

Ergebnis

[nej.html]

  • Reines Umgebungsladen, das Zielcookie wird nicht gelesen

  • Aktualisieren Sie den Ladevorgang, das Cookie wird zurückgegeben von der vorherigen Anfrage wird gelesen

[jquery.html]

  • Reines Umgebungsladen, das Ziel-Cookie wird nicht gelesen

  • Laden aktualisieren, das Ziel-Cookie wird nicht gelesen

[js.html]

  • Reines Umgebungsladen, das Ziel-Cookie wird nicht gelesen

  • Laden aktualisieren, Ziel-Cookie wird nicht gelesen

Häh? Das Ergebnis ist anders! Der zweite Ladevorgang mit nej liest das erste Cookie. Die anderen beiden Zeiten wurden erhalten.

Grund

nej Das Laden des abhängigen Frameworks erfolgt asynchron. Wenn die synchrone Anfrage initiiert wird, wurde der Dom geladen. Wenn der Rückruf antwortet, ist document.cookie im Zustand „Bereit“. Zustand und ist lesbar und schreibbar. Die Anfrage kann das zurückgegebene Cookie jedoch immer noch nicht selbst abrufen.

Über das Cookie-Problem der Ajax-Synchronisierung

Die anderen beiden Lademechanismen blockieren das Laden von Dom, was dazu führt, dass der Dom nicht geladen wird, wenn die Synchronisierungsanforderung initiiert wird, wenn der Rückruf antwortet , document.cookieImmer noch nicht beschreibbar.

Einzelfaktorkontrolle

Wir werden die Logik der oben genannten HTML-Dateien ändern.
Verzögern Sie die Synchronisierungsanforderung, bis auf das Dokument geklickt wird.
Das Folgende

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

sind immer noch die oben genannten Ausführungsschritte, werfen wir einen Blick auf die Ergebnisse

Ergebnisse

[nej.html]

  • Reines Laden der Umgebung, das Ziel-Cookie wird nicht gelesen

  • Aktualisieren Sie den Ladevorgang, das von der vorherigen Anfrage zurückgegebene Cookie wird gelesen

【jquery.html】

  • Reines Umgebungsladen, das Zielcookie wird nicht gelesen

  • Laden aktualisieren, das letzte Mal wird gelesen. Das von der Anfrage zurückgegebene Cookie

[js.html]

  • wird in eine saubere Umgebung geladen und das Zielcookie wird nicht gelesen

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

Über das Cookie-Problem der Ajax-Synchronisierung

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



Das obige ist der detaillierte Inhalt vonÜber das Cookie-Problem der Ajax-Synchronisierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn