Я получил "тихий" кульбит при синхронизации файлов cookie ajax

Ajax
Я получил "тихий" кульбит при синхронизации файлов cookie ajax

предисловие

Это действительно беспомощно, чтобы столкнуться с такой проблемой, браузерная совместимость интерфейса всегда была головной болью.

В этой статье описан только такой смущающий и беспомощный день. Используй его, чтобы развеять всем скуку T_T

воспроизведение сцены

同事:快来! Приходить!线上出问题了! !
Я: Боже конь?! 咩?! ЧТО?! なに?!
Коллега: Это вызвано этим релизом?
Я: Вернись! Отказ! (Почему вы бросаете цепочку, когда вы собираетесь есть! Я не могу позаботиться о своем животе! Проверьте это)
......

После проходного запутанного диалога остается только перейти к «заминированию».

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

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

Повторение проблемы

Используйте AJAX, чтобы сделать "Синхронизировать», этот запрос вернет файл cookie вsuccessНе удалось прочитать этот целевой файл cookie в обратном вызове! После завершения выполнения ajaxdocument.cookieбудет обновлено

Сфера влияния

Сторона ПК и сторона Android имеют небольшую сферу влияния и появляются время от времени.

Конец iOS является наиболее пострадавшей областью.Большинство браузеров Chrome и Safari сообщают об этой проблеме, и встроенная среда WebView, встроенная в приложение, также не используется.

Предварительное чтение файла cookie, возвращаемого этим запросом в рамках этого обратного вызова синхронного запроса, может вызвать проблемы.

Полстраны пало, на что мне этот железный прут!

прослеживаемость

Я могу избавить вас от мелких проблем с совместимостью, но как вы можете быть такими высокомерными!

Вертикальное сравнение

Для устранения некоторых предметов помех и восстановления их сути используем фреймворк соответственноnej,jQueryа такжеjs写几个相同功能的“同步” 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([
            '{lib}util/ajax/xdr.js'
        ], function () {
            var _j = NEJ.P('nej.j');
            _j._$request('/api', {
                sync: true,
                method: 'POST',
                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: '/api',
            async: false,
            method: 'POST',
            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: '/api',
            async: false,
            type: 'POST',
            success: function (result) {
                alert("cookie:\n" + document.cookie)
            }
        });
    </script>
</body>
</html>

Три документа одинаковы, инициируйте запрос синхронизации после завершения загрузки HTML, запрос вернет файл cookie, будет в обратном вызовеdocument.cookie


[Hev.js]

var express = require("express");
var http = require("http");
var fs = require("fs");
var app = express();

var router = express.Router();
router.post('/api', 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('ok');
});

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

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

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

app.use('/', router);
http.createServer(app).listen(3000);

Ладно, все хорошо, побежали

$ node serve.js

действовать

Делаем последовательно следующее

  1. Используйте браузер QQ на стороне ios и очистите все кеши
  2. Загрузите одну из страниц и следите за выводом целевого файла cookie.
  3. Выполните операцию обновления, посмотрите, есть ли выходные данные целевого файла cookie, сравните метку времени выхода файла cookie и подтвердите, является ли это результатом синхронизации последнего файла cookie, а не файла cookie, полученного по этому запросу.
  4. Очистите все кеши, переключите целевой HTML-файл и выполните шаги 2, 3 и 4 в цикле.

результат

[Nej.html]

  • Чистая загрузка среды, целевой файл cookie не читается
  • Обновить загрузку, прочитать файл cookie, возвращенный последним запросом

【jquery.html】

  • Чистая загрузка среды, целевой файл cookie не читается
  • Обновить загрузку, целевой файл cookie не читается

【js.html】

  • Чистая загрузка среды, целевой файл cookie не читается
  • Обновить загрузку, целевой файл cookie не читается

Хм? Результат разный! Прочитайте первый файл cookie, используя вторую загрузку NEJ. Остальные два раза получается.

причина

Загрузка nej-зависимого фреймворка асинхронная, при инициации синхронного запроса dom загружен, колбэк соответствующий.document.cookieУже в "готовом" состоянии, доступен для чтения и записи. Однако запрос по-прежнему не может получить куки-файл, переносимый его собственным возвратом.

Два других механизма загрузки блокируют загрузку dom, в результате чего при инициации синхронного запроса dom еще не загружен, а при соответствующем обратном вызове,document.cookieВсе еще недоступно для записи.

однофакторный контроль

Мы изменим логику вышеуказанных html-файлов.
Отложите запросы на синхронизацию до тех пор, пока не сработает щелчок по документу.
следующим образом

$('document').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'ssuccessonloadСобытие

code.jQuery.com/jQuery-3.2. …:9533 строки

И моя собственная реализация, и реализация nej обеsuccessОбратный вызов привязан кonreadystatechangeНа мероприятии разница только в этом

Сначала запрос ajax будет инициирован дважды.onreadystatechange, после срабатыванияonload, возможно потому чтоdocument.cookieсинхронизация имеет вероятностьonloadВыполнить до запуска события? ? Я не уверен.

вывод проблемы

  1. На стороне ПК, Android, Chrome на стороне IS, среде браузера Safari в методе обратного вызова синхронного запроса Ajax используйте шанс сбоя файла cookie, чтобы вернуть этот запрос.
  2. Сторона IOS, браузер QQ, встроенная в приложение среда браузера Webview, частота отказов чрезвычайно высока.

решение

Хулиганят те, у кого только проблемы и нет планов!

Схема 1 - Тьма Чэньцан с Планк-роуд Минсю

Преобразуйте метод получения файлов cookie в методе обратного вызова в асинхронную операцию.

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

Вариант 2 - Политика непротивления

Мы не уверены в плане, мы должны рассмотреть реализацию.

Если вы не можете быть на 100% безопасными для работы, не рекомендуется форсировать синхронную работу ajax.Многие механизмы не так самоуверенны, как мы думаем.

Ссылки

Хорошее чтение может вылечить глупость

Посоветуйте хорошую книгу
Дизайн JavaScript Framework (2-е издание)
Автор: Ситу Чжэнмэй

Эта книга всесторонне объясняет проектирование фреймворка JavaScript и связанные с ним знания.Основное содержание включает в себя начальный модуль, языковой модуль, обнюхивание браузера и обнаружение функций, фабрику классов, механизм селектора, модуль узла, модуль кэширования данных, модуль стиля, модуль атрибута, событие для ПК и мобильных устройств. система, система событий jQuery, асинхронная модель, модуль взаимодействия данных, движок анимации, MVVM, шаблон интерфейса (статический шаблон), динамический шаблон MVVM, стена производительности и сложная стена, компоненты, компоненты в схеме эпохи jQuery, схема компонентов avalon2 , схема компонентов реакции и т.д.
Эта книга подходит для чтения интерфейсными дизайнерами, разработчиками JavaScript, дизайнерами мобильных интерфейсов, программистами и менеджерами проектов, а также в качестве соответствующих учебных книг и материалов для профессиональной подготовки.