项目作者: lllyyyggg

项目描述 :
axios的跨域请求
高级语言: JavaScript
项目地址: git://github.com/lllyyyggg/axios-cors-demo.git
创建时间: 2018-11-14T12:46:04Z
项目社区:https://github.com/lllyyyggg/axios-cors-demo

开源协议:

下载


CORS详解

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

两种请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)

只要同时满足以下两大条件,就属于简单请求。

  1. 1) 请求方法是以下三种方法之一:
  2. HEAD
  3. GET
  4. POST
  5. (2HTTP的头信息不超出以下几种字段:
  6. Accept
  7. Accept-Language
  8. Content-Language
  9. Last-Event-ID
  10. Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不同时满足上面两个条件,就属于非简单请求。

浏览器对这两种请求的处理,是不一样的。

简单请求

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

  1. Origin: http://localhost:3000

上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequestonerror回调函数捕获。

注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

  1. Access-Control-Allow-Origin: http://localhost:3000
  2. Access-Control-Allow-Credentials: true
  3. Access-Control-Expose-Headers: lanyage
  4. Content-Type:text/html; charset=utf-8

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。

Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。

默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

Access-Control-Expose-Headers

该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma

withCredentials 属性

上面说到,CORS请求默认不发送CookieHTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。

Access-Control-Allow-Credentials:true

另一方面,开发者必须在AJAX请求中打开withCredentials属性。

  1. var xhr = new XMLHttpRequest();
  2. xhr.withCredentials=true;

否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。

但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

下面是一段axios代码。

  1. const host = "http://localhost:8081"
  2. let url=`${host}/delete/1001`;
  3. axios.delete(url)
  4. .then(res => {
  5. console.log(res.data);
  6. })

如果仅仅是这样的话,会报错,那么此时需要在服务端添加Access-Control-Allow-Methods头信息,内容可以是GET,POST,DELETE,PUT

  1. response.setHeader("Access-Control-Allow-Methods","GET,POST,DELETE,PUT"); //表示支持这么多种请求
  2. response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"); //表示支持的请求源

其中Access-Control-Allow-Origin该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
这样,非简单请求就会请求两次。

  1. Request URL: http://localhost:8081/delete/1001
  2. Request Method: OPTIONS
  1. Request URL: http://localhost:8081/delete/1001
  2. Request Method: DELETE
  3. // result: 1001

另外,如果你想携带额外的响应头。

  1. Access-Control-Allow-Headers

服务端的header是不区分大小写的。

服务端要想获取到请求headers,可以通过这样的方法。

首先是定义Interceptor,也就是拦截器。

  1. @Component
  2. public class RequestInterceptor implements HandlerInterceptor {
  3. public static final Logger logger = LoggerFactory.getLogger(RequestInterceptor.class);
  4. @Override
  5. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
  6. logger.info("==> pre handler invoked <==");
  7. response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
  8. response.setHeader("Access-Control-Allow-Methods","GET,POST,DELETE,PUT");
  9. response.setHeader("Access-Control-Allow-Headers", "Content-Type,User-Agent,Authorization,Lanyage");
  10. Enumeration<String> headerNames = request.getHeaderNames();
  11. while (headerNames.hasMoreElements()){
  12. String headerName = headerNames.nextElement();
  13. if(headerName.equalsIgnoreCase("lanyage")) {
  14. logger.info("==> header Lanyage : {} <==", request.getHeader(headerName.toLowerCase()));
  15. }
  16. }
  17. return true;
  18. }
  19. }

然后必须在WebMvcConfigurer的实现类中进行配置。

  1. @Configuration
  2. public class WebAppConfig implements WebMvcConfigurer {
  3. @Autowired
  4. private RequestInterceptor interceptor;
  5. @Override
  6. public void addInterceptors(InterceptorRegistry registry) {
  7. registry.addInterceptor(interceptor);
  8. }
  9. }