Ajax GET Запрос отправляется дважды
Я столкнулся с непонятным поведением при выполнении Ajax-запроса как части моего приложения Flask. Я написал, что обработчик получает щелчок div
, а затем отправляет запрос Ajax с определенными данными на определенный маршрут, указанный в моем app.py
. Затем данные вставляются в базу данных. Хотя этот подход работал нормально при запуске моего приложения Flask на моей собственной машине, при перемещении моего приложения в другую хостинговую службу (Pythonanywhere) каждый раз, когда я нажимаю div
, запрос отправляется дважды, так как Свидетельством тому являются данные, вставляемые дважды в базу данных.
Подобные варианты этого вопроса уже задавались (например,здесь и здесь), но все эти вопросы касаются запросов POST
, в то время как мой использует GET
. Кроме того, эти вопросы обычно включали HTML form
, который был представлен вместе с запросом POST
, и, следовательно, дополнительный запрос. Однако в моем коде нет форм.
Пример моего кода (упрощенный, но по сути такой же, как мои текущие усилия):
В frontend.html
:
<div class='wrapper'>
<div class='submit_stamp' data-timestamp='2019-8-2'>Submit</div>
</div>
В frontend.js
:
$('.wrapper').on('click', '.submit_stamp', function(){
$.ajax({
url: "/submit_time",
type: "get",
data: {time: $(this).data('timestamp')},
success: function(response) {
$('.wrapper').append(response.html);
},
});
});
В app.py
:
@app.route('/submit_time')
def submit_time():
db_manager.submit_stamp(flask.request.args.get('time'))
return flask.jsonify({'html':'<p>Added timestamp</p>'})
Таким образом, всякий раз, когда я щелкаю элемент submit_stamp
, запрос Ajax запускается дважды, временная метка дважды вставляется в мою базу данных, и "Added timestamp"
добавляется дважды к .wrapper
. Вот некоторые вещи, которые я сделал, чтобы это исправить:
Добавление event.stopPropagation()
в обработчик
Использование системы логических флагов, в которой переменная устанавливается на true
сразу после щелчка, и сбрасывается на false
в обработчике success
в .ajax
. Я обернул $.ajax
этим логическим значением в условном выражении.
Ни один из этих патчей не сработал. Что меня смущает, так это то, почему $.ajax
вызывается один раз при работе на моей машине, но вызывается дважды при работе на хостинге. Это связано с кешем? Как я могу решить эту проблему? Большое спасибо!
Изменить:
Как ни странно, дубликаты запросов происходят нечасто. Иногда делается только один запрос, в других случаях запросы дублируются. Однако я проверил вывод Network XHR в Chrome, и он отображает только один заголовок запроса.
Вывод журнала доступа (с удаленными IP-адресами):
<IP1> - - [05/Aug/2019:16:35:03 +0000] "GET /submit_time?payload=.... HTTP/1.1" 200 76 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1" "<IP>" response-time=0.217
<IP2> - - [05/Aug/2019:16:35:05 +0000] "GET /submit_time?payload=.... HTTP/1.1" 200 71 "http://www.katchup.work/create" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" "<IP2>" response-time=0.198
Ответы
Ответ 1
Спасибо всем, кто откликнулся. В конечном итоге мне удалось решить эту проблему с помощью двух разных решений:
1) Сначала я смог заблокировать ошибочный запрос, проверив IP-адрес в бэкэнде:
@app.route('/submit_time')
def submit_time():
_ip = flask.request.environ.get('HTTP_X_REAL_IP', flask.request.remote_addr)
if _ip == '128.177.108.218':
return flask.jsonify({'route':'UNDEFINED-RESULT'.lower()})
return flask.jsonify({"html":'<p>Added timestamp</p>'})
Вышесказанное - скорее временный взлом, поскольку нет гарантии, что целевой IP останется прежним.
2) Однако я обнаружил, что при работе по HTTPS также был удален повторяющийся запрос. Первоначально я загружал свое приложение с панели инструментов Pythonanywhere, что привело к http://www.testsite.com
. Однако после того, как я установил соответствующий сертификат SSL, обновил страницу и снова запустил запрос, я обнаружил, что был получен желаемый результат.
Я присуждаю вознаграждение @computercarguy, поскольку его сообщение побудило меня подумать о внешних/сетевых причинах, по которым моя первоначальная попытка не удалась.
Ответ 2
С вашим последним обновлением я бы сказал, что это не дубликат запроса. В вашем журнале говорится, что один запрос был от Mozilla на компьютере под управлением Windows, а другой запрос поступил от Chrome на Mac, это просто 2 разных запроса, поступивших из двух разных мест, которые оказались во времени близкими друг к другу. Даже если это был тест с виртуальной машины, он не должен записывать несколько ОС или браузеров, поскольку ВМ позаботится обо всех переводах, предотвращая путаницу, подобную этой.
Вы не включаете IP-адреса, но если они являются публичными адресами (как, например, в 127.xxx, 10.xxx или 192.xxx), то это определенно два разных пользователя, которые используют ваше программное обеспечение одновременно время.
Если вы отслеживаете, что это один и тот же пользователь, это могут быть просто те, кто использует ваше программное обеспечение на 2 разных устройствах (например, настольный компьютер или мобильный телефон). Если это не разрешено, убедитесь, что их доступ отражает это. Если его можно отследить через DNS в разных географических точках, у вас может быть взломанная учетная запись для блокировки, пока реальный пользователь не сможет подтвердить свою личность.
Как бы вы ни делали это с новыми данными, я не думаю, что это на самом деле ваше программное обеспечение, если вы не можете воспроизвести его с помощью тестирования даже достаточно надежно. Потратьте время, чтобы подумать, что это может быть не ошибка, а что-то еще. Разработчики программного обеспечения должны думать, что все является ошибкой и их ошибкой, когда это может быть что-то мягкое или злонамеренная атака, которая раньше не рассматривалась.
Удачи и, надеюсь, я дал вам кое-что подумать!
Ответ 3
Очень необычное решение, но оно должно работать (если нет, я думаю, что проблему нельзя решить с помощью js.)
РЕДАКТИРОВАНИЕ: проверьте отправленный идентификатор в запросе ajax! (Так что проверяйте на стороне сервера!) Это наверняка будет уникальный идентификатор, поэтому вы можете проверять, имеет ли @computercarguy право или нет.
var ids = [];
var generateId = function(elem)
{
let r = Math.random().toString(36).substring(7);
while ($.inArray(r, ids) !== -1)
{
r = Math.random().toString(36).substring(7);
}
ids.push(r);
elem.attr("id", r);
};
$(document).ready(function()
{
$(".wrapper").find(".submit_stamp").each(function()
{
generateId($(this));
});
console.log(ids);
});
var ajaxHandler = function(stampElem, usedId)
{
let testData = new FormData();
testData.append("time", stampElem.data('timestamp'));
testData.append("ID", usedId);
$.ajax({
url: "/submit_time",
type: "get",
data: testData,
success: function(response)
{
$('.wrapper').append(response.html);
generateId(stampElem);
if (stampElem.attr("id").length)
{
console.log("new id:"+stampElem.attr("id"));
}
},
});
};
$(".wrapper").on("click", ".submit_stamp", function(ev)
{
ev.preventDefault();
ev.stopImmediatePropagation();
if ($(this).attr("id").length)
{
let id = $(this).attr("id");
$("#"+id).one("click",
$.proxy(
ajaxHandler, null, $(this), id
)
);
$(this).attr("id", "");
}
});
Ответ 4
Поэтому прежде всего я бы использовал приведенный ниже синтаксис в качестве личного предпочтения
$('.wrapper').click(function (event) {
event.stopImmediatePropagation();
$.ajax({
url: "/submit_time",
type: "get",
data: {
time: $(this).data('timestamp')
},
success: function (response) {
$('.wrapper').append(response.html);
},
});
});
Также, как я уже сказал, вы должны убедиться, что при обращении к двум параллельным запросам они действительно относятся к одному IP + клиенту, иначе вы можете запутаться между параллельным запросом из разных мест, который будет повторяться как таковой
Ответ 5
Небольшое изменение в вашем файле JS.
$('.wrapper').on('click', '.submit_stamp', function(event){
event.preventDefault();
event.stopImmediatePropagation();
$.ajax({
url: "/submit_time",
type: "get",
data: {time: $(this).data('timestamp')},
success: function(response) {
$('.wrapper').append(response.html);
},
});
});