ホームページ  >  記事  >  ウェブフロントエンド  >  Ajax Cookie の同期について

Ajax Cookie の同期について

一个新手
一个新手オリジナル
2017-10-24 09:41:372005ブラウズ

前書き

この種の問題に遭遇すると、本当にどうしようもないです。

この記事は、そのような恥ずかしくてどうしようもない日を記録するだけです。皆様の退屈解消にご利用くださいT_T

あのシーンが再現されています

同僚:さあ!来て!オンラインで問題が発生しました! !
私: 一体何ですか?! ナナ?! 同僚: このリリースが原因ですか?
私: ロールバック!ロールバック! (なぜ食べようとしているのにチェーンを落とすのですか! 胃の世話ができません! すぐに確認してください)
...

混乱した会話の後、私は落ち着いて「地雷を除去することしかできません」 」。

ロールバック、プロキシ、パケットキャプチャ、比較、単一要素のトラブルシューティング。 。 。

一連のコンボパンチを終えた後、最終的に欠陥を見つけるのに線香一本ほどかかりました。それは、ajax 同期コールバックに問題があることが判明しました。理不尽!そんなはずはありません!そのような操作はありますか? !

問題の再発

問題を一文で要約

ajax を使用して「

同期」リクエストを行うと、このリクエストは successでこのターゲット Cookie を返します。 > コールバックが失敗しました! document.cookie は、ajax の実行が完了するまで更新されません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

影響範囲

PC と Android への影響は小さく、まれに発生します。


iOS は最も影響を受けやすい領域です。Chrome と Safari を除くほとんどのブラウザではこの問題が発生し、アプリの組み込み Webview 環境も影響を受けません。

この同期リクエスト コールバック内でこのリクエストによって返される Cookie を事前に読み取ると、問題が発生します。

国の半分が崩壊したのに、この鉄の棒は何の役に立つのでしょう!

原因と結果を追跡
  • 小規模な互換性の問題については許せますが、あなたはとても傲慢です、どうやってそれを隠すことができますか?

  • 垂直比較

    いくつかの干渉項目を排除し、その本質を復元するために、フレームワーク nejjQuery、および js を使用して、いくつかのフレームワークを作成します。同一 「同期」機能のデモです。様子を見てみましょう。 。
【nej.html】NEJライブラリを使用

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

【jquery.html】jQueryライブラリを使用
    _$ajax({
        url: &#39;/api&#39;,
        async: false,
        type: &#39;POST&#39;,
        success: function (result) {
            setTimeout(function(){
                // do something 在此处获取 cookie 操作是安全的
            },0)
        }
    });
  • 【js.html】自作したajaxリクエスト関数

    rrreee

    htmlで読み込んだ後の3つのファイルは同じです次に、同期リクエストを開始します。これにより、コールバック内で Cookie が返され、コールバック中に Cookie が書き込まれたかどうかを確認します。
  • 以下では、ノードを使用してこの書き込み可能な Cookie サービスを実装します。

    【serve.js】

    rrreee
  • OK、すべて問題ありません。
rrreee

操作を実行します

    次の操作を順番に実行します。
    1. iOS で QQ ブラウザを使用します。 、すべてのキャッシュをクリアします🎜🎜🎜🎜 ページの 1 つをロードし、ターゲットの Cookie 出力があるかどうかを確認します🎜🎜🎜🎜 更新操作を実行し、ターゲットの Cookie 出力があるかどうかを確認し、Cookie 出力のタイムスタンプを比較して確認します最後の Cookie の同期結果であるかどうか このリクエストで取得した Cookie の代わりに、🎜🎜🎜🎜すべてのキャッシュをクリアし、対象の HTML ファイルを切り替え、手順 2、3、4 をループします🎜🎜
    🎜結果🎜🎜[nej.html]🎜🎜🎜🎜純粋な環境の読み込み、ターゲットのCookieは読み込まれません🎜🎜🎜🎜リフレッシュ読み込み、前のリクエストによって返されたCookieは読み込まれます🎜🎜🎜🎜【jquery.html】🎜🎜 🎜🎜純粋な環境の読み込み、ターゲットの Cookie は読み込まれません🎜🎜 🎜🎜更新と読み込み、ターゲットの Cookie は読み込まれません🎜🎜🎜🎜[js.html]🎜🎜🎜🎜純粋な環境の読み込み、ターゲット Cookie は読み込まれません🎜 🎜🎜🎜リフレッシュしてロードしても、対象のCookieが読み込まれていません🎜🎜🎜 🎜あれ?結果は違います! nej を使用した 2 回目のロードでは、最初の Cookie を読み取ります。残りの2回は取得できました。 🎜🎜理由🎜🎜nej 依存するフレームワークのロードは非同期です。同期リクエストが開始されると、コールバックが応答される時点で、document.cookie はすでに「準備完了」になっています。状態であり、読み書き可能です。ただし、リクエスト自体は依然として返される Cookie を取得できません。 🎜🎜Ajax Cookie の同期について🎜 🎜他の 2 つのロード メカニズムは DOM のロードをブロックするため、同期リクエストが開始されたときに DOM はロードされず、document.cookie はまだ書き込み不可能です。 🎜🎜単一要素制御🎜🎜上記のHTMLファイルのロジックを修正していきます。 🎜ドキュメントがクリックされてトリガーされるまで同期リクエストを遅らせます。 🎜以下🎜rrreee🎜はまだ上記の実行手順です。結果を見てみましょう🎜🎜結果🎜🎜[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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。