后端异常设计与特殊情况下的浏览器HTTP:POST请求重发

298 阅读4分钟

由于在HTTP协议中,GET请求通常是获取服务器端的资源,不会服务器数据形成反作用,②而POST请求多用于增,删,改服务器上的资源,会对服务器资源产生副作用。所以浏览器按理应该不会因为网络原有自动重发POST请求?那么真实情况是这样的吗?

原由:

最近在开发中遇到一个特殊的情况,前端向后端发送一个CSV文件,后端将CSV中的数据解析出来,然后再去调第三方接口一条一条导入库中,但第三方接口未提供批量接口,而且需要等前一条数据入库后一条数据才能导入,CSV文件里有多少条数据,就会访问多少次第三方接口,如果一个客户需要导入上千条数据,甚至更多数据,那么后端这样串行地一条一条去导入,很轻易就花了几分钟,就会议发再这几分钟的时间,后端又会收到一条同样的请求,数据会导入两次。

分析:

1.第一怀疑对象:Nginx重试机制 先关闭Nginx的重试机制,对所有请求不进行重试,可依然无法解决问题,那么排除第一种原因

2.前端设置了请求超时重发机制,前端请求使用的axios,进行排查,

  • 首先查看浏览器控制台但只记录了一次请求,于是使用chrome://net-internals 查看日志,然而也没什用,没法先问题,现在陷入了深深地怀疑无法自拔
  • 再查看axios请求和响应拦截器中是否有重发机制(垂死挣扎),也没有,我开始怀疑是后端对请求进行了重置,于是去找后端同学的麻烦,然而也没什用,后端根本没有问题

3.于是后端同学用抓包给我从头到尾抓了一遍包,发现确实是浏览器发了两次请求(每两分钟发一次,每次都没有相应内容)

最后就是百度找原因,从浏览器设计到HTTP规范

If an HTTP/1.1 client sends a request which includes a request body, but which does not include an Expect request-header field with the "100-continue" expectation, and if the client is not directly connected to an HTTP/1.1 origin server, and if the client sees the connection close before receiving any status from the server, the client SHOULD retry the request.

若是发送一个请求到服务器端,该请求有请求体,可是请求头里面不包含“ 100-continue ”这种东西,而且客户端没有直接链接到原始的 HTTP/1.1 服务器(这里是链接到Nginx做了反向代理),此时,若是客户端在接收到服务器发送的 HTTP 状态以前发现服务器主动关掉链接,那么客户端应该重试请求。

项目中遇到的这种状态就像是服务器主动关掉了链接,致使浏览器重新发送请求了

解决方案

找到原因就很容易解决问题了,既然你浏览器要重新发请求那么你重发的请求还是之前的请求,并不是新的请求,那么肯定使用的是浏览器缓存的请求,这样我不让你重发就是清除缓存,那么好的接下来就是很简单的解决办法了,只需在这个请求的url后加上时间戳就可以拒绝缓存了 在axios中加时间戳的方法就很简单了

request.interceptors.request.use(
  config => {
    if (config.method == 'post') {
      config.data = {
        ...config.data,
        _t: Date.parse(new Date()) / 1000
      }
    } else if (config.method == 'get') {
      config.params = {
        _t: Date.parse(new Date()) / 1000,
        ...config.params
      }
    }
    return config
  }, function (error) {
    return Promise.reject(error)
  }
)

但是就在你以为万事大吉的时候,随之新的问题会产生,就是请求会超时,链接会断掉,由于我们在项目中使用了代理,那么代理服务器的超时时间 150S,那么超过这个时间会让本次请求的返回为空 奉上 nginx 超时设置(blog.csdn.net/john1337/ar…) 本地调试的话使用proxy代理也会出现同样的问题 需要设置超时时间 timeout ,当然axios也需要针对当前请求设置超时时间(根据实际情况设置)

完美解决方法

1.后端调用第三方接口进行修改,新增批量请求接口,

2.后端不必等待所有接口运行完成再返回,可以使用异步返回,先返回正在导入标识,然后后续返回接口使用websockt方式通知前端(前端根据websockt修改视图)