作为Flask应用程序的一部分运行Ajax请求时,我遇到了令人困惑的行为。我已经编写了一个处理程序,接收到div
单击,然后将包含某些数据的Ajax请求发送到我的app.py
中指定的特定路由。然后将数据插入数据库。尽管在自己的计算机上运行Flask应用程序时,这种方法效果很好,但将应用程序移至另一个托管服务(Pythonanywhere)时,每次我单击div
时,请求都会被发送两次,因为数据两次插入数据库证明了这一点。
之前,有人问过这个问题的类似变体(例如here和here),但是当我使用{{1 }}。此外,这些问题通常涉及与POST
请求一起提交的HTML GET
,因此是附加请求。但是,我的代码没有任何形式。
我的代码示例(简化了,但与我目前的工作基本相同):
在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
元素时,Ajax请求都会触发两次,时间戳两次插入到我的数据库中,并且@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
之后。我为解决此问题所做的一些事情包括:
在处理程序中添加"Added timestamp"
使用布尔标志系统,在单击后立即将变量设置为.wrapper
,并在event.stopPropagation()
的{{1}}处理程序中将其重置为true
。我用这个布尔值将false
包裹在条件语句中。
这些补丁均无效。但是,令我感到困惑的是,为什么success
在我的计算机上运行时被调用一次,而在托管服务上运行时被调用两次。它与缓存有关吗?我该如何解决这个问题?非常感谢你!
编辑:
奇怪的是,重复请求很少发生。有时,仅发出一个请求,而其他时间,则重复请求。但是,我已经检查了Chrome中的Network XHR输出,它仅显示单个请求标头。
访问日志输出(删除IP):
.ajax
答案 0 :(得分:4)
对于您的最新更新,我不得不说这不是重复的请求。在您的日志中,一个请求来自一台基于Windows的计算机上的Mozilla,另一个请求来自于Mac上的Chrome,这是两个不同位置发出的两个不同请求,它们恰好在时间上彼此接近。即使是在虚拟机上进行的测试,也不应记录多个操作系统或浏览器,因为VM会处理所有翻译,避免出现这种混乱情况。
您不包括IP地址,但是如果它们是公共地址(例如127.xxx,10.xxx或192.xxx之外的其他地址),则它们肯定是恰好正在使用您的软件的两个不同用户同时。
如果您要跟踪它是同一用户,则可能只是他们在2种不同的设备(例如台式机和移动电话)上使用您的软件。如果不允许这样做,请确保其访问权限反映了这一点。如果可以通过DNS将其跟踪到不同的地理位置,则您可能有一个被入侵的帐户可以锁定,直到真实用户确认其身份为止。
无论如何,用新数据对其进行切片,除非您可以通过可靠的测试来重现它,否则我认为它实际上不是您的软件。花时间考虑一下,它可能不是错误,而是其他。软件开发人员习惯于认为一切都是错误及其错误,而这可能是以前可能没有考虑过的良性或恶意攻击。
祝你好运,希望我给了你一些思考!
答案 1 :(得分:2)
非常不寻常的解决方案,但它应该可以工作(如果不能,我认为用js无法解决问题。)
已编辑:在ajax请求中检查已发送的ID! (因此,请在服务器端检查!)这肯定是唯一的ID,因此,您可以使用此@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", "");
}
});
答案 2 :(得分:2)
感谢所有回复的人。最终,我能够通过两种不同的解决方案来解决此问题:
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授予了赏金,因为他的帖子促使我思考最初尝试失败的与外部/网络相关的原因。
答案 3 :(得分:-1)
首先,我将使用以下语法作为个人偏好
$('.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 +客户端,否则您可能会混淆来自不同地方的并行请求,从而重复这样的请求
答案 4 :(得分:-2)
您的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);
},
});
});