一、问题背景
前段时间运维拦截到了一些 499 网络状态码报警,我对该问题进行了简单排查,本文对该状态码做简单的一个介绍。
二、问题探寻
2.1 问题探寻-过程
[499 - CLIENT CLOSED REQUEST] A non-standard status code introduced by nginx for the case when a client closes the connection while nginx is processing the request.
查阅得知,499 是 nginx 引入的非标准状态代码,用于在 nginx 服务器在处理请求时客户端关闭连接的情况。作为 4xx 开头状态码,表明的是这个请求的过程中是客户端发生了错误。
查阅代码得知,是在某个业务场景,用户点击的服务不同,需要调用接口查询价格,在查询价格的接口下存在这个错误报警。
再切换服务的时候,在上一个请求接口没有返回前,发起新的接口请求并取消上一个请求,这个逻辑是符合预期的。(当然防抖要加上)
尽管在客户端侧只能看到一个被 cancel 的请求,但是这个请求在 nginx 拦截到的状态码是 499。
既然是符合预期的场景,那么其实不该告警,于是联系运维去掉关于该状态码低频状态下的告警。
2.1 问题探寻-取消 ajax 请求
顺便我们看看怎么取消 ajax 请求
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// cancel the request
controller.abort()
复制代码
自 v0.22 后 ajax 换成以上的方法,因此我也换成这种写法进行介绍。
把以上方法进行一些封装。
// cancel-request.js
import qs from 'qs'
export default class CancelRequest {
constructor() {
this.pendingRequest = new Map()
}
// 根据请求信息生成唯一标识 key
geterateReqKey(config) {
const { url, method, params, data } = config
return [url, method, qs.stringify(params), qs.stringify(data)].join('&')
}
// 把当前请求信息添加到pendingRequest对象中
addPendingRequest(config, CancelToken) {
if (!config.cancelDuplicated) return
const requestKey = this.geterateReqKey(config)
if (!config.signal) {
const controller = new AbortController()
if (!this.pendingRequest.has(requestKey)) {
// 把请求取消方法作为 map 值存起来
this.pendingRequest.set(requestKey, controller)
}
config.signal = controller.signal
}
}
// 检查是否存在重复请求,若存在则取消前一次请求
removePendingRequest(config) {
if (!config.cancelDuplicated) return
const requestKey = this.geterateReqKey(config)
if (this.pendingRequest.has(requestKey)) {
const controller = this.pendingRequest.get(requestKey)
// 取消请求
controller.abort()
// 删除map中对应的属性
this.removeRequestKey(config)
}
}
// 从pendingRequest中删除对应的key
removeRequestKey(config) {
const requestKey = this.geterateReqKey(config)
this.pendingRequest.delete(requestKey)
}
}
复制代码
// request.js
import axios from 'axios';
import CancelRequest from './cancel-request.js'
// 实例化
let cancelRequest = new CancelRequest()
const instance = axios.create({
// ...
});
// 请求拦截器
instance.interceptors.request.use(config => {
// 在请求开始之前检查先前的请求,如果是重复请求,删除之前的
cancelRequest.removePendingRequest(config);
// 如果不存在就将当前请求添加到pendingRequest
cancelRequest.addPendingRequest(config);
return config;
}, err => {
Promise.reject(err);
});
// 响应拦截器
instance.interceptors.response.use(res => {
// 移除成功请求记录
cancelRequest.removeRequestKey(res.config)
return res.data;
}, err => {
// 失败时也需要移除
cancelRequest.removeRequestKey(err.config || {} )
Promise.reject(err);
});
export default instance;
复制代码
我们将 cancelDuplicated 作为是否开启取消重复请求的开关,于是,在业务接口处,我们采用
import { request } from '@/utils/request'
export const exampleRequest = (params) => {
return request('apicode', params,{
cancelDuplicated: true
})
}
复制代码
就可以开启该配置。
版权归原作者 码云笔记 所有, 如有侵权,请联系我们删除。