您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)碛嘘P(guān)怎么對(duì)@PathVariable中的特殊字符進(jìn)行處理,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
@GetMapping(value="/user/{useraccount}") public void getUserAccount(@PathVariable("useraccount") String userAccount) { logger.info("useraccount :" + userAccount); }
正常訪問:
/user/zhangsan
打?。簎seraccount : zhangsan
看似一切正常
but:
訪問:/user/zhangsan/lisi
打印:useraccount : zhangsan
咦,為啥不是useraccount :zhangsan/lisi ?
@PathVariable并沒有我們想象的聰明,對(duì)于參數(shù)中的/并不能跟實(shí)際路徑/分開
事實(shí)上,有. ; -等都不能正確切分。
怎么辦呢?
@GetMapping(value="/user") public void getUserAccount(@RequestParam("useraccount") String userAccount) { logger.info("useraccount :" + userAccount); }
用/user?useraccount=zhangsan 訪問
@GetMapping(value="/user/{useraccount:[a-zA-Z0-9\\.\\-\\_\\;\\\]+}") public void getUserAccount(@PathVariable("useraccount") String userAccount) { logger.info("useraccount :" + userAccount); }
正常訪問:
/user/zhangsan
打印:useraccount : zhangsan
當(dāng)然,這個(gè)就有點(diǎn)不靈活了,第一種簡(jiǎn)單又方便
補(bǔ)充:記一次@PathVariable特殊參數(shù)會(huì)丟失的排查問題
請(qǐng)求參數(shù)中如果包含.,會(huì)造成參數(shù)丟失,請(qǐng)看如下代碼
@RequestMapping(value = "hello/{name}") public Map<String, Object> sayHello(@PathVariable("name") String name, HttpServletRequest request) { Map<String, Object> rtnMap = new HashMap<>(); rtnMap.put("msg", "hello " + name); return rtnMap; }
請(qǐng)求地址: hello/ddf,則正常返回{"msg": "hello ddf"}
請(qǐng)求地址: hello/ddf.com,依然還是返回{"msg": "hello ddf"}
@RequestMapping(value = "hello/{name:.*}") public Map<String, Object> sayHello(@PathVariable("name") String name, HttpServletRequest request) { Map<String, Object> rtnMap = new HashMap<>(); rtnMap.put("msg", "hello " + name); return rtnMap; }
如果使用@PathVariable以.sh或.bat等特殊字符結(jié)尾,會(huì)影響實(shí)際返回?cái)?shù)據(jù)
報(bào)錯(cuò)如下:
{ "timestamp": 1541405292119, "status": 406, "error": "Not Acceptable", "exception": "org.springframework.web.HttpMediaTypeNotAcceptableException", "message": "Could not find acceptable representation", "path": "/HDOrg/user/hello/ddf.sh" }
還是上面的代碼
以下代碼,省略@RestController控制層類代碼
@RequestMapping(value = "hello/{name:.*}") public Map<String, Object> sayHello(@PathVariable("name") String name, HttpServletRequest request) { Map<String, Object> rtnMap = new HashMap<>(); rtnMap.put("msg", "hello " + name); return rtnMap; }
如果這時(shí)候請(qǐng)求地址為hello/ddf.sh或hello/ddf.com.sh,只要是以.sh結(jié)尾,這時(shí)候業(yè)務(wù)邏輯代碼不會(huì)受到影響,但走到Spring自己的代碼去處理返回?cái)?shù)據(jù)的時(shí)候,有一個(gè)功能會(huì)根據(jù)擴(kuò)展名來決定返回的類型,而以.sh結(jié)尾擴(kuò)展名為sh,會(huì)被解析成對(duì)應(yīng)的Content-Type: application/x-sh。
解決辦法如下,第一種方法是從網(wǎng)上找到的,可以直接禁用該功能,但有可能會(huì)影響到靜態(tài)資源的訪問,不能確定,也沒有進(jìn)行嘗試
@Configuration public class Config extends WebMvcConfigurerAdapter { @Override public void configureContentNegotiation( ContentNegotiationConfigurer configurer) { configurer.favorPathExtension(false); } }
然后以下就是閑著沒事很想換個(gè)思路嘗試去看看這到底是怎么回事,由于個(gè)人能力有限,不保證以下內(nèi)容的重要性;
第二種方式解決思路是,既然擴(kuò)展名以.sh等結(jié)尾會(huì)有問題,那么能不能不要讓程序?qū)U(kuò)展名識(shí)別為.sh,或者干脆就跳過處理,比如我是否可以加個(gè).sh/這樣就會(huì)影響到實(shí)際的擴(kuò)展名,但是又不會(huì)影響到已有的代碼,其實(shí)這里有個(gè)偷懶的寫法,可以直接在@RequestMapping里的value最后直接加一個(gè)/,但是這要求客戶端必須在原有的條件上最終拼一個(gè)/,否則會(huì)找不到對(duì)應(yīng)的映射,直接404,我這里碰到這個(gè)問題的時(shí)候,因?yàn)樵摲椒ㄒ呀?jīng)上線并且被其它幾個(gè)系統(tǒng)調(diào)用,因此更改起來會(huì)有些繁瑣,所以尋求了一種麻煩的方式,先將解決方式放在下面,不確定是否會(huì)影響其它問題
這種方式解決方式如下:注釋中的兩行代碼二選一都可,推薦前面的寫法,直接已經(jīng)跳過
@RequestMapping(value = "hello/{name:.*}") public String sayHello(@PathVariable("name") String name) { // 該方法跳過通過上面描述的那種方式來確定MediaType request.setAttribute(PathExtensionContentNegotiationStrategy.class.getName() + ".SKIP", true); // 后面參數(shù)的值前半部分必須和該方法的RequestMapping一致,否則無效,不包括ContextPath request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/hello/" + name + "/"); return "hello " + name; }
下面依賴源碼來看一下為什么可以這么去做,先看一下為什么會(huì)造成這個(gè)結(jié)果?以下步驟只關(guān)心與當(dāng)前問題有關(guān)的部分,并只大概關(guān)注其中問題,不作細(xì)節(jié)的深入
經(jīng)過debug可以看到錯(cuò)誤是在處理以下過程報(bào)錯(cuò),首先如下
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor { @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // Try even with null return value. ResponseBodyAdvice could get involved. writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); } }
出現(xiàn)這個(gè)問題,一般的查找思路就是是否是請(qǐng)求或響應(yīng)的Content-Type是否出現(xiàn)了問題,那么在上面這個(gè)方法上無論是inputMessage還是outputMessage都是正常的,重點(diǎn)來看一下writeWithMessageConverters()方法,該方法,部分代碼如下
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler { @SuppressWarnings("unchecked") protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { Object outputValue; Class<?> valueType; Type declaredType; if (value instanceof CharSequence) { outputValue = value.toString(); valueType = String.class; declaredType = String.class; } else { outputValue = value; valueType = getReturnValueType(outputValue, returnType); declaredType = getGenericType(returnType); } HttpServletRequest request = inputMessage.getServletRequest(); List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request); List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType); // 后面處理MediaType的部分在這里全部省略 } /** * Returns the media types that can be produced: * <ul> * <li>The producible media types specified in the request mappings, or * <li>Media types of configured converters that can write the specific return value, or * <li>{@link MediaType#ALL} * </ul> * @since 4.2 */ protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type declaredType) { Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); if (!CollectionUtils.isEmpty(mediaTypes)) { return new ArrayList<MediaType>(mediaTypes); } else if (!this.allSupportedMediaTypes.isEmpty()) { List<MediaType> result = new ArrayList<MediaType>(); for (HttpMessageConverter<?> converter : this.messageConverters) { if (converter instanceof GenericHttpMessageConverter && declaredType != null) { if (((GenericHttpMessageConverter<?>) converter).canWrite(declaredType, valueClass, null)) { result.addAll(converter.getSupportedMediaTypes()); } } else if (converter.canWrite(valueClass, null)) { result.addAll(converter.getSupportedMediaTypes()); } } return result; } else { return Collections.singletonList(MediaType.ALL); } } }
先看方法getAcceptableMediaTypes(),是根據(jù)請(qǐng)求來決定當(dāng)前的HttpServletRequest到底是要請(qǐng)求什么類型的數(shù)據(jù),該方法調(diào)用鏈在后面說明;
getProducibleMediaTypes()方法返回可以生成的MediaType,能夠生成哪些是看當(dāng)前項(xiàng)目一共有多少可以被支持的MediaType,當(dāng)然也能看到也可以通過HttpServletRequest明確設(shè)置屬性HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE來確定用哪種方式;
拿到這兩個(gè)列表后,需要判斷requestedMediaTypes是否兼容producibleMediaTypes,如*/*則可以兼容所有的可以生成的MediaType,最終將兼容的requestedMediaTypes循環(huán)處理,看是否是一個(gè)具體的MediaType而不是通配符,那么最終生效的MediaType就是這個(gè),當(dāng)然存在多個(gè),則也就存在多個(gè)不是通配也滿足條件的,所以再循環(huán)前也做了一次排序,保證優(yōu)先級(jí)最高的一定會(huì)生效。
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler { private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException { List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request)); return (mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes); } }
MediaType.java
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 = "*/*"; // 靜態(tài)初始化MediaType.ALL的值省略 }
該方法的結(jié)果可以看到如果調(diào)用的方法返回了一個(gè)空的列表,則該方法返回MediaType.ALL的列表,通過代碼可以看到它的值為*/*,該方法往下調(diào)用部分代碼如下:
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver { @Override public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { for (ContentNegotiationStrategy strategy : this.strategies) { List<MediaType> mediaTypes = strategy.resolveMediaTypes(request); if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) { continue; } return mediaTypes; } return Collections.emptyList(); } }
調(diào)用如下:
public class WebMvcAutoConfiguration { @Override public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException { private static final String SKIP_ATTRIBUTE = PathExtensionContentNegotiationStrategy.class .getName() + ".SKIP"; Object skip = webRequest.getAttribute(SKIP_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); if (skip != null && Boolean.parseBoolean(skip.toString())) { return Collections.emptyList(); } return this.delegate.resolveMediaTypes(webRequest); } }
在這里可以看到有一個(gè)屬性為skip,如果它的屬性為PathExtensionContentNegotiationStrategy的類全名+".SKP"并且它的值為true,那么這里則不繼續(xù)往下處理直接返回空的集合,而在前面也已經(jīng)看到如果返回的空的集合,實(shí)際上最終返回給調(diào)用方的是*/*,結(jié)合前面看到的
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)
這個(gè)方法,*/*是可以匹配任何生成的producibleMediaTypes,所以最終結(jié)果能夠按照原先應(yīng)該返回的類型正確返回,而不會(huì)被.sh等后綴影響到;
其實(shí)最初沒有看到skip的時(shí)候,看到了一些后面的代碼,最終也解決了這個(gè)問題,不論正確與否,先把整個(gè)過程記錄下來,假如在上面的步驟中沒有設(shè)置skip=true,那么程序繼續(xù)下去的部分走向如下
如果uid以.sh結(jié)尾的話,在邏輯處理完成之后框架處理return數(shù)據(jù)的時(shí)候,會(huì)根據(jù)擴(kuò)展名來決定返回的content-type,sh結(jié)尾
會(huì)影響返回的content-type為application/x-sh,這會(huì)影響該方法的實(shí)際功能,解決辦法是:
要么禁用該功能,要么修改該方法的@RequestMapping,禁用不能確定是否會(huì)對(duì)直接訪問的靜態(tài)資源有影響,
而且該方法調(diào)用方項(xiàng)目已上線,不宜輕易修改,只能這里改變這個(gè)屬性的地址,影響框架
后面獲取請(qǐng)求的后綴為null,而避免這個(gè)問題,但尚不能確認(rèn)requestUrl和mappingUrl不一致是否會(huì)有別的問題
request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/user/" + uid + "/");
上述就是小編為大家分享的怎么對(duì)@PathVariable中的特殊字符進(jìn)行處理了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。