axios 发送两次请求原因及解决方法
出现问题
最近 Vue 项目中使用 axios 组件,在页面交互中发现 axios 会发送两次请求,一种请求方式为 OPTIONS,另外一种为自己设置的(如 GET、POST 等等)。
如下图所示,所有请求都发送了两次。
仔细查看发现,其中第一次是 OPTIONS 请求,且没有携带 Authorization 信息:
第二次才是正常的 GET 请求,正常携带了 Authorization 信息:
CORS 通信
CORS 是一个 W3C 标准,全称是 "跨域资源共享"(Cross-origin resource sharing)。 它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。
实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。
CORS 两种请求
浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
简单请求
只要同时满足以下两大条件,就属于简单请求。
- 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
- HTTP 的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
非简单请求
凡是不同时满足上面两个条件,就属于非简单请求。
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是 PUT
或 DELETE
,或者 Content-Type
字段的类型是 application/json
。
非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为 "预检" 请求(preflight)。
浏览器处理
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的 XMLHttpRequest
请求,否则就报错。
浏览器对这两种请求的处理,是不一样的。
两次请求原因
前后端未满足 “同源策略 / SOP”,俗称请求跨域。
浏览器一旦发现请求跨域,就会使用 CORS 通信,自动添加一些附加的头信息,简单请求只会有一次请求,只有非简单请求会附加一次请求。
解决方法
服务期端直接通过 “预检” 请求,服务器新建拦截器,拦截所有请求,筛选所有 Requset Method:OPTIONS 的请求,不做任何处理直接返回即可。
Java JWT
package com.yhzy.zytx.jwt.Interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* @ClassName JwtInterceptor
* @Description JWT拦截器
*/
public class JwtInterceptor implements HandlerInterceptor {
private final static Logger logger = LoggerFactory.getLogger(JwtInterceptor.class);
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
// 从 http 请求头中取出 token
String token = httpServletRequest.getHeader("Authorization");
// 如果不是映射到方法直接通过
if(!(object instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod=(HandlerMethod)object;
Method method=handlerMethod.getMethod();
// OPTIONS请求类型直接返回不处理
if (RequestMethod.OPTIONS.name().equals(httpServletRequest.getMethod())){
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) throws Exception {
}
}