Spring的后缀匹配问题
很多同学用Spring boot做微服务,然后就遇到了很奇怪的后缀匹配问题,如果你去百度, 可以看到很多很神奇的解决方案,包括在Controller上用正则表达式去匹配,甚至有人重写了PathMatcher, 简直神奇。所以我说,国内百度和CSDN简直就是害人。要找技术文档还是Google。所以,某些人密谋把Google屏蔽了,那就是阻碍国内技术交流发展,违背时代潮流,倒行逆施的行为,这样的恶行总是要还的。
举个栗子,小张同学用Spring boot做了一个登陆API,该API的URL为http://localhost:8080/login
,
但是如果你用http://localhost:8080/login.json
, http://localhost:8080/login.do
,http://localhost:8080/login.action
,
http://localhost:8080/login.xml
, 甚至http://localhost:8080/login/
去访问,发现竟然都可以正常访问,
返回结果根据具体情况不同分为两种,一种返回200说是正常登录成功,另一种则返回406,抛出HttpMediaTypeNotAcceptableException
例外。
测试同学无意中发现了这个问题,然后汇报给产品经理,傻逼产品经理出于“对用户负责”的借口要求小张同学修复, 于是小张同学抓耳挠腮,百度了许久,加班到深夜,终于在“百度代码合集”的帮助下,历经千辛万苦修复了这个问题。 但其实他自己也不知道是怎么修复的,反正能work了。
在彻底了解这个问题之前,你首先要了解另外一个概念:Content Negotiation。
简单来说,就是Spring会根据用户发过来的accept header所指定的media type来选择能返回相应media type的mapping。
而负责这个选择的,叫做ContentNegotiationManager
。然而,这个聪明的manager不仅仅会解析用户端发过来的accept header,
还会解析url上的后缀所表示的类型,并且url上的后缀有更高的优先级。(官方文档)
Spring框架为了方便大家,就算你不指定url的后缀,它也会自动把url上的.*
后缀统统mapping到你的controller上。
所以,拿小张的例子来说,当一个用户访问http://localhost:8080/login.xml
,框架会去寻找同路径下path=/login, produce=application/xml
的Controller,
小张虽然没有给他的controller指定produce,但框架发现他的controller没法返回xml类型的结果,就会抛出HttpMediaTypeNotAcceptableException
例外,
并且经由DefaultHandlerExceptionResolver
处理为406返回。
另外有趣的是,因为这个例外是在controller之前抛出的,所以controller adivsor虽然可以处理这个例外,但是因为response已经被
DefaultHandlerExceptionResolver
处理为406返回,所以advisor并不能改变返回的结果。
后缀匹配可以通过配置关闭,配置方法非常简单,不再赘述,详情请参考官方文档。
我要说的是,就算你关闭了后缀匹配,Content Negotiation
仍然会去匹配路径上的后缀,因为这是两个完全不同的方面,如果你不想要406的返回,可以通过关闭favorPathExtension
选项实现。
你可能没听懂,我再举个例子:
如果你有一个controller mapping到/person
,Spring会自动把这个mapping暗搓搓地mapping到/person.*
,
类似/person.xml
、/persion.json
、/erson.csv
等都会被转发到这个controller的mapping上。至于这个controller能不能返回xml、csv,这就要看你具体实现了。
如果关闭了后缀匹配,那么类似/person.xml
、/persion.json
、/erson.csv
等就不会被转发到这个controller的mapping上了。理论上你会收到404的返回,除非你另外做了mapping。
但这个时候(关闭了后缀匹配后),如果你有一个controller的mapping是/person.xml
,但你却让他返回了json
类型(譬如通过produce指定),用户虽然没有发送accept的头,
但这个时候你仍然可能就会收到406的返回。
同样的,如果你有一个controller mapping到/person
,并且没有关闭后缀匹配,但是你关闭了favorPathExtension
。那么类似/person.xml
、/persion.json
、/erson.csv
等
仍然会被转发到这个controller的mapping上,并且能够正确返回结果,在用户没有给accept的头的前提下,不考虑返回的media type是啥。
调整这些选项的方法都很简单,就不在此赘述了,详情还是希望同学们花时间仔细阅读官方文档, 如果有兴趣的话,还可以仔细尝试各种配置,或者阅读源代码,而不是去百度或看CSDN的东西,以免被误导。
个人建议,如果你用spring boot做微服务,最好把content negotiation都关了,只用一个FixedContentNegotiationStrategy
,并且把suffixPatternMatch
和trailingSlashMatch
都关闭了。
这样你的API会看起来比较舒爽一点。当然这只是个人建议,具体情况还需要具体讨论。
- 完 -