2023年11月25日发(作者:)
如何处理@PathVariable中的特殊字符问题
上代码:
@GetMapping(value="/user/{useraccount}")
public void getUserAccount(@PathVariable("useraccount") String userAccount) {
("useraccount :" + userAccount);
}
正常访问:
/user/zhangsan
打印:useraccount : zhangsan
看似⼀切正常
but:
访问:/user/zhangsan/lisi
打印:useraccount : zhangsan
咦,为啥不是useraccount :zhangsan/lisi ?
@PathVariable并没有我们想象的聪明,对于参数中的/并不能跟实际路径/分开
事实上,有. ; -等都不能正确切分。
怎么办呢?
两种⽅案:
1,简单点,直接使⽤@RequestParam代替
@GetMapping(value="/user")
public void getUserAccount(@RequestParam("useraccount") String userAccount) {
("useraccount :" + userAccount);
}
⽤/user?useraccount=zhangsan 访问
2,使⽤正则过滤
@GetMapping(value="/user/{useraccount:[a-zA-Z0-9.-_;]+}")
public void getUserAccount(@PathVariable("useraccount") String userAccount) {
("useraccount :" + userAccount);
}
正常访问:
/user/zhangsan
打印:useraccount : zhangsan
当然,这个就有点不灵活了,第⼀种简单⼜⽅便
补充:记⼀次@PathVariable特殊参数会丢失的排查问题
如果需要解决上⾯这个问题,则可以将代码更改如下(该解决⽅式从⽹上搜寻)
@RequestMapping(value = "hello/{name:.*}")
public Map
Map
("msg", "hello " + name);
return rtnMap;
}
如果使⽤@PathVariable以.sh或.bat等特殊字符结尾,会影响实际返回数据
报错如下:
{
"timestamp": 19,
"status": 406,
"error": "Not Acceptable",
"exception": "diaTypeNotAcceptableException",
"message": "Could not find acceptable representation",
"path": "/HDOrg/user/hello/"
}
还是上⾯的代码
以下代码,省略@RestController控制层类代码
@RequestMapping(value = "hello/{name:.*}")
public Map
Map
("msg", "hello " + name);
return rtnMap;
}
如果这时候请求地址为hello/或hello/,只要是以.sh结尾,这时候业务逻辑代码不会受到影响,但⾛到Spring⾃⼰的代码去处理
返回数据的时候,有⼀个功能会根据扩展名来决定返回的类型,⽽以.sh结尾扩展名为sh,会被解析成对应的Content-Type: application/x-
sh。
解决办法如下,第⼀种⽅法是从⽹上找到的,可以直接禁⽤该功能,但有可能会影响到静态资源的访问,不能确定,也没有进⾏尝试
@Configuration
public class Config extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(
ContentNegotiationConfigurer configurer) {
athExtension(false);
}
}
然后以下就是闲着没事很想换个思路尝试去看看这到底是怎么回事,由于个⼈能⼒有限,不保证以下内容的重要性;
第⼆种⽅式解决思路是,既然扩展名以.sh等结尾会有问题,那么能不能不要让程序将扩展名识别为.sh,或者⼲脆就跳过处理,⽐如我是否可
以加个.sh/这样就会影响到实际的扩展名,但是⼜不会影响到已有的代码,其实这⾥有个偷懒的写法,可以直接在@RequestMapping⾥的
value最后直接加⼀个/,但是这要求客户端必须在原有的条件上最终拼⼀个/,否则会找不到对应的映射,直接404,我这⾥碰到这个问题的时
候,因为该⽅法已经上线并且被其它⼏个系统调⽤,因此更改起来会有些繁琐,所以寻求了⼀种⿇烦的⽅式,先将解决⽅式放在下⾯,不确
定是否会影响其它问题
这种⽅式解决⽅式如下:注释中的两⾏代码⼆选⼀都可,推荐前⾯的写法,直接已经跳过
@RequestMapping(value = "hello/{name:.*}")
public String sayHello(@PathVariable("name") String name) {
// 该⽅法跳过通过上⾯描述的那种⽅式来确定MediaType
ribute(e() + ".SKIP", true);
}
}
出现这个问题,⼀般的查找思路就是是否是请求或响应的Content-Type是否出现了问题,那么在上⾯这个⽅法上⽆论是inputMessage还是
outputMessage都是正常的,重点来看⼀下writeWithMessageConverters()⽅法,该⽅法,部分代码如下
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
implements HandlerMethodReturnValueHandler {
@SuppressWarnings("unchecked")
protected
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object outputValue;
Class> valueType;
Type declaredType;
if (value instanceof CharSequence) {
outputValue = ng();
valueType = ;
declaredType = ;
}
else {
outputValue = value;
valueType = getReturnValueType(outputValue, returnType);
declaredType = getGenericType(returnType);
}
HttpServletRequest request = vletRequest();
List
List
// 后⾯处理MediaType的部分在这⾥全部省略
}
/**
* Returns the media types that can be produced:
*
*
*
*
*
* @since 4.2
public class MediaType extends MimeType implements Serializable {
public static final MediaType ALL;
/**
* A String equivalent of {@link MediaType#ALL}.
*/
public static final String ALL_VALUE = "*/*";
// 静态初始化的值省略
}
该⽅法的结果可以看到如果调⽤的⽅法返回了⼀个空的列表,则该⽅法返回的列表,通过代码可以看到它的值为*/*,该⽅法
往下调⽤部分代码如下:
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
@Override
public List
for (ContentNegotiationStrategy strategy : gies) {
List
if (y() || (MEDIA_TYPE_ALL)) {
continue;
}
return mediaTypes;
}
return ist();
}
}
调⽤如下:
public class WebMvcAutoConfiguration {
@Override
public List
throws HttpMediaTypeNotAcceptableException {
private static final String SKIP_ATTRIBUTE =
.getName() + ".SKIP";
Object skip = ribute(SKIP_ATTRIBUTE,
_REQUEST);
if (skip != null && oolean(ng())) {
return ist();
}
return eMediaTypes(webRequest);
}
}
在这⾥可以看到有⼀个属性为skip,如果它的属性为PathExtensionContentNegotiationStrategy的类全名+".SKP"并且它的值为true,那么这
⾥则不继续往下处理直接返回空的集合,⽽在前⾯也已经看到如果返回的空的集合,实际上最终返回给调⽤⽅的是*/*,结合前⾯看到的
ctMessageConverterMethodProcessor#writeWithMessageConverters(T,
Parameter, tServerHttpRequest,
tServerHttpResponse)
这个⽅法,*/*是可以匹配任何⽣成的producibleMediaTypes,所以最终结果能够按照原先应该返回的类型正确返回,⽽不会被.sh等后缀影响
发布评论