【问题标题】:Ajax GET Request is Sent TwiceAjax GET 请求被发送两次
【发布时间】:2019-12-11 13:50:35
【问题描述】:

在我的 Flask 应用程序中运行 Ajax 请求时,我遇到了令人费解的行为。我编写了一个处理程序,接收div 点击,然后将带有某些数据的 Ajax 请求发送到我的app.py 中指定的特定路由。然后将数据插入数据库。虽然这种方法在我自己的机器上运行我的 Flask 应用程序时效果很好,但在将我的应用程序移动到另一个托管服务 (Pythonanywhere) 时,每次我单击 div 时,都会发送两次请求,数据证明了这一点被插入数据库两次。

之前有人问过这个问题的类似变体(例如herehere),但这些问题都处理POST 请求,而我使用的是GET。此外,这些问题通常涉及与 POST 请求一起提交的 HTML form,因此是附加请求。但是,我的代码没有任何形式。

我的代码示例(经过简化,但本质上与我当前的工作相同):

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。我为解决此问题所做的一些事情包括:

  1. 在处理程序中添加event.stopPropagation()

  2. 使用布尔标志系统,其中变量在单击后立即设置为true,并在.ajaxsuccess 处理程序中重置为false。我在条件语句中用这个布尔值包裹了$.ajax

这些补丁都不起作用。然而,让我感到困惑的是为什么$.ajax 在我的机器上运行时被调用一次,但在托管服务上运行时被调用两次。跟缓存有关系吗?我该如何解决这个问题?非常感谢!

编辑:

奇怪的是,重复请求很少发生。有时,只发出一个请求,而其他时候,请求是重复的。但是,我在 Chrome 中检查了 Network XHR 输出,它只显示单个请求标头。

访问日志输出(移除 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

【问题讨论】:

  • 也许你的frontend.js 被加载了两次(或其他地方),注册了两个点击处理程序。尝试在代码之前执行$('.wrapper').off('click')(尽管这将删除所有处理程序)
  • 您在第一次请求后收到任何控制台错误?或者尝试从控制台运行它进行调试
  • @RV 我在整个过程中没有收到任何错误。我在success 处理程序中添加了一个console.log,但是它只打印了一次。你知道这意味着什么吗?
  • 你确定。 ajax 调用了两次。你在网络上见过吗??
  • 在服务器日志中,您是否看到请求来自同一 IP 两次?

标签: javascript jquery html ajax flask


【解决方案1】:

对于您的最新更新,我不得不说这不是重复请求。您的日志显示一个请求来自基于 Windows 的机器上的 Mozilla,而另一个请求来自 Mac 上的 Chrome,这只是来自两个不同位置的两个不同的请求,它们恰好在时间上彼此靠近。即使是在虚拟机上进行的测试,也不应记录多个操作系统或浏览器,因为 VM 会处理所有翻译,防止出现此类混淆。

您不包括 IP 地址,但如果它们是公共地址(例如 127.xxx、10.xxx 或 192.xxx 以外的其他地址),那么他们肯定是碰巧使用您的软件的两个不同用户同时。

如果您跟踪的是同一用户,则可能只是他们在 2 种不同的设备(例如台式机与手机)上使用您的软件。如果不允许这样做,请确保他们的访问反映了这一点。如果可以通过 DNS 将其跟踪到不同的地理位置,那么您可能有一个被盗用的帐户需要锁定,直到真实用户可以确认他们的身份。

不管你用新数据如何分割它,我不认为它实际上是你的软件,除非你可以通过测试来重现它,甚至有点可靠。花点时间考虑一下,它可能只是不是是一个错误,而是别的什么。软件开发人员习惯于认为一切都是错误和他们的错,而这可能是以前可能没有考虑过的良性或恶意攻击。

祝你好运,希望我能给你一些思考!

【讨论】:

  • 非常感谢您的帖子。事实上,我已经在我的服务器上设置了较小的代码示例,并且我无法复制出现在 /submit_time 上的重复问题。不过,我觉得奇怪的是第二个请求的触发速度有多快(2 毫秒后)。是否有某个进程“镜像”了我的原始请求?
  • @Ajax1234,如果有一个“镜像”请求,我不得不说它可能具有相同的 IP 地址以及浏览器和操作系统签名。这可能是用户的无能“中间人”攻击,它允许原始请求以及来自其站点的另一个请求来伪造数据。我只是不明白这种签名更改是如何由您的软件引起的。即使是错误路由的路由器也只会发送一个请求,而且它仍然不会像那样更改签名。
  • @Ajax1234,您提到这种情况很少发生。您可能需要检查是否真的有 2 个或更多用户同时在积极使用您的软件。在测试中同时按键或点击并不常见,但如果您同时有 50-100 多个用户,则可能会发生这种情况。
【解决方案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】:

    很不寻常的解决方案,但应该可以(如果不行,我认为问题不能用js解决。)

    EDITED:检查ajax请求中发送的ID! (所以在服务器端检查!)这肯定是一个唯一的 id,所以,你可以用这个 @computercarguy 测试是否正确。

    let ids = []
    
    function generateId(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)
    });
    
    function ajaxHandler(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+客户端,否则你可能会混淆来自不同地方的并行请求以这样重复

      【讨论】:

      • 感谢您的回答,但是,我仍然收到重复的结果。您能否详细说明并行请求的可能性?
      • 所以你的日志好像是同一个IP?
      • 其实每个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);
           },
      
         });
      });
      

      【讨论】:

      • 谢谢,但是重复的请求仍然存在。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-15
      • 1970-01-01
      • 2013-03-08
      • 1970-01-01
      • 2019-09-13
      • 2019-05-15
      相关资源
      最近更新 更多