Maison  >  Article  >  interface Web  >  À propos du problème des cookies lors de la synchronisation d'Ajax

À propos du problème des cookies lors de la synchronisation d'Ajax

一个新手
一个新手original
2017-10-24 09:41:372000parcourir

Avant-propos

Il est vraiment impuissant de rencontrer ce genre de problème. La compatibilité des navigateurs frontaux a toujours été un casse-tête.

Cet article n'enregistre qu'une journée aussi embarrassante et impuissante . Utilisez-le pour soulager l'ennui de tout le monde T_T

Reconstituez la scène

Collègue : Allez ! Allez! Quelque chose s'est mal passé en ligne ! !
Moi : Wow ?! Quoi ?! QUOI ?! Nana ?!
Collègue : Est-ce causé par cette libération ?
Moi : Reculez ! retour en arrière ! (Pourquoi tu t'emportes quand tu es sur le point de manger ! Ne t'inquiète pas pour ton estomac ! Vérifiez-le vite)
...

Je ne peux me calmer qu'après un conversation déroutante." Démineur".

Rollback, proxy, capture de paquets, comparaison, dépannage à un seul facteur. . .

Après avoir terminé une série de coups de poing combo, il a fallu environ un bâton d'encens pour enfin trouver la faille. Il s'est avéré que c'était un problème avec le rappel de synchronisation ajax ! Déraisonnable! Cela ne devrait pas être le cas ! Existe-t-il une telle opération ? !

Reproduction du problème

Résumé du problème en une phrase

Utilisez ajax pour faire une requête "Synchronisation" Cette requête. renverra un cookie successÉchec de la lecture de ce cookie cible dans le rappel ! document.cookie ne sera pas mis à jour tant que l'exécution ajax n'est pas terminée

Portée d'influence

La portée d'influence sur PC et Android est petite et occasionnelle.

IOS est le domaine le plus durement touché. La plupart des navigateurs autres que Chrome et Safari auront ce problème, et l'environnement Webview intégré de l'application n'est pas non plus à l'abri de ce problème.

La prélecture du cookie renvoyé par cette requête dans ce rappel de requête synchrone entraînera des problèmes.

La moitié du pays est tombée, à quoi me sert cette barre de fer !

Retracer la cause et l'effet

Je peux vous pardonner le problème de compatibilité à petite échelle, mais vous êtes tellement arrogant, comment pouvez-vous le cacher !

Comparaison verticale

Pour éliminer certains éléments d'interférence et restaurer son essence, nous utilisons les frameworks nej, jQuery et js pour écrire plusieurs "synchronisations" avec le même fonction. Démo, attendez et voyez. .

[nej.html] Utilisez la bibliothèque 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] Utilisez la bibliothèque 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] Implémentez la vôtre requête ajax Les fonctions

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

sont les mêmes dans les trois fichiers. Une fois le code HTML chargé, une requête synchrone est lancée. La requête renverra un cookie, document.cookie sera imprimé. out pour détecter si le rappel est déjà en cours. Le cookie est écrit à ce moment-là.

Ce qui suit utilise un nœud pour implémenter ce service de cookies inscriptibles.
【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);

D'accord, tout va bien, lançons

$ node serve.js

Opération

Nous effectuons les opérations suivantes dans l'ordre,

  1. Utilisez le navigateur iOS QQ et effacez tous les caches

  2. Chargez l'une des pages et observez s'il y a une sortie de cookie cible

  3. Effectuez une opération d'actualisation, observez s'il existe une sortie de cookie cible, comparez l'horodatage de la sortie du cookie et confirmez s'il s'agit du résultat de synchronisation du dernier cookie plutôt que du cookie obtenu par cette requête. ,

  4. Effacez tous les caches, changez de fichier HTML cible et exécutez les étapes 2, 3 et 4 en boucle

Résultat

[nej.html]

  • Chargement de l'environnement pur, le cookie cible n'est pas lu

  • Actualiser le chargement, le cookie renvoyé par la requête précédente est lu

[jquery.html]

  • Chargement de l'environnement pur, le cookie cible n'est pas lu

  • Chargement de l'actualisation, le cookie cible n'est pas lu

[js.html]

  • Chargement de l'environnement pur, le le cookie cible n'est pas lu

  • Actualisation du chargement, le cookie cible n'est pas lu

Hein ? Le résultat est différent ! Le deuxième chargement utilisant nej lit le premier cookie. Les deux autres temps ont été obtenus.

Raison

nej Le chargement du framework dépendant est asynchrone Lorsque la requête synchrone est lancée, le dom a été chargé Lorsque le rappel répond, document.cookie est déjà dans le "prêt". " état et est lisible et écrit. Mais la requête ne peut toujours pas obtenir le cookie renvoyé par elle-même.

À propos du problème des cookies lors de la synchronisation d'Ajax

Les deux autres mécanismes de chargement bloquent le chargement du dom, empêchant le dom d'être chargé lorsque la demande de synchronisation est lancée lorsque le rappel répond. , document.cookieToujours pas inscriptible.

Contrôle à facteur unique

Nous modifierons la logique des fichiers html ci-dessus.
Retardez la demande de synchronisation jusqu'à ce que le document soit cliqué.
Ce qui suit

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

sont toujours les étapes d'exécution ci-dessus, jetons un coup d'œil aux résultats

Résultats

[nej.html]

  • Chargement en environnement pur, le cookie cible n'est pas lu

  • Actualiser le chargement, le cookie renvoyé par la requête précédente est lu

【jquery.html】

  • Chargement de l'environnement pur, le cookie cible n'est pas lu

  • Actualiser le chargement, la dernière fois est lu Le cookie renvoyé par la requête

[js.html]

  • est chargé dans un environnement propre et le cookie cible n'est pas lu

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

À propos du problème des cookies lors de la synchronisation d'Ajax

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



Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn