@CrossOrigin初始化
关于此注解的初始化,在完成mapping
注册的时候就已经完成了,大致步骤如下:
AbstractHandlerMethodMapping: // 注册一个mapping public void registerMapping(T mapping, Object handler, Method method) { this.mappingRegistry.register(mapping, handler, method); } // 内部类 class MappingRegistry { // 记录着没一个HandlerMethod所对应的注解配置 private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>(); ... public void register(T mapping, Object handler, Method method) { ... // initCorsConfiguration这里就是解析handler上面的注解喽~~~ // 此init方法只有RequestMappingHandlerMapping子类重写了~~~ CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { // 若不为null(有注解配置),就缓存起来 this.corsLookup.put(handlerMethod, corsConfig); } ... } }
对于handler上次注解的解析,最终是由RequestMappingHandlerMapping完成的:
它显著的特点是:和Handler强绑定,因此在注册Mapping的时候就完成初始化工作。
综上所述可得出这三种配置方式的区别:
- CorsFilter方式:完全独立的Filter,和其它配置并不冲突和也无关联,最终委托给CorsProcessor来完成的
- addCorsMappings方式:它的配置会作用于所有的内置配置的HandlerMapping上,所以它就是global全局配置
- @CrossOrigin方式:它和某一个具体的handler强绑定,所以它属于局部配置。
说明:方式2和方式3可以形成互补配置,有combine的效果。
为何OPTIONS请求进入不了Controller的Handler方法内?
这个问题是系列文章的第一篇我抛出来的,因为有一个现象是:简单请求我可以在Controller的方法内向response手动添加请求头搞定。但是非简单请求这么做行不通了,原因是OPTIONS请求根本进入不了方法体~
阅读完本文的上半拉,此问题的答案就显而易见了,因此我此处不再废话。倘若对此问题还没想到答案的小伙伴,欢迎你在下面给我留言我会及时解答你的。
为何给response设置响应头写在postHandle()方法内无效?
这个问题倒是困扰了我好一会,直到我直到了Spring MVC对它的处理过程。
问题的现象是:response的响应头都有,但http状态码却是403,跨域失败。结果如下截图:
针对此问题作出如下解释供以参考:
- 上面有说到一句话:匹配上handler后,若是OPTIONS请求的话,它最终的handler不是原handler而是一个全新的PreFlightHandler处理器,并且并且并且chain上的拦截器们都是会生效的。
- 关键就在这里:PreFlightHandler执行handler处理方法最终是委托给CorsProcessor执行的,若config == null并且是 预检请求 ,那它就会执行:rejectRequest(serverResponse),这时状态码就已经设置为了403了,因此等handler方法执行完成之后再执行postHandle()方法体,因为返回状态码已经设置好,已经无力回天了,so就出现了如此怪异现象~
有人说在postHandle()方法里加上这么一句,手动把响应码改成200:response.setStatus(HttpStatus.OK.value());。
效果:能达到想要的跨域效(真实请求能继续发送)。但是我强烈不建议你这么去做,因此这样你需要加很多逻辑判断(什么时候应该设置,什么时候不应该),得不偿失。
DispatcherServlet.doOptions()方法简单分析
说明:dispatchOptionsRequest这个参数虽然默认值是false,但在DispatcherServlet所有的构造器里都有这么一句:setDispatchOptionsRequest(true)。
FrameworkServlet: /** Should we dispatch an HTTP OPTIONS request to {@link #doService}?. */ private boolean dispatchOptionsRequest = false; @Override protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 若dispatchOptionsRequest = true 或者是预检请求OPTIONS请求,都会processRequest // processRequest(request, response);就是复杂的视图渲染逻辑~~~ if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) { processRequest(request, response); // 若你自己设置了allow响应头,那就不处理了。否则交给下面处理 if (response.containsHeader("Allow")) { // Proper OPTIONS response coming from a handler - we're done. return; } } // Use response wrapper in order to always add PATCH to the allowed methods // 开发者自己没有设置Allow这个响应头就会进这里来,最终效果是 // Allow:GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH super.doOptions(request, new HttpServletResponseWrapper(response) { @Override public void setHeader(String name, String value) { if ("Allow".equals(name)) { value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name(); } super.setHeader(name, value); } }); }
若CORS请求的URL不存在,响应码404还是403?
- 无默认的servlet处理器(DefaultServletHandler):404(找不到对应的handler)
- 有默认的servlet处理器:403(能找到handler,因为有默认的处理器兜底嘛)
Spring MVC的这个配置用于开启默认处理器与否:
@Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { //configurer.enable(); //configurer.enable("default"); }
总结
@CrossOrigin关注方法,CorsFilter等其它方式更关注URL。
该CORS系列通过三篇文章层层递进的讲述了CORS跨域请求访问自己如何处理等相关议题,在前后端分离开发模式的今天我觉得后端程序员有必要掌握这块内容,因此特撰文分享给大家,我觉得应该是能对很多人有较大帮助的,所你还有别的想法,欢迎你给我留言~