解決RestTemplate 請(qǐng)求url中包含百分號(hào) 會(huì)被轉(zhuǎn)義成25的問題
RestTemplate 請(qǐng)求url中包含百分號(hào) 會(huì)被轉(zhuǎn)義成25
最初使用RestTemplate 進(jìn)行遠(yuǎn)程調(diào)用方法如下:
private String getRemoteData(String url) {
logger.info("Request URL :" + url + "|");
String resp = rest.getForObject(url, String.class);
logger.info("Response result : " + resp.toString());
return resp;
}
但發(fā)現(xiàn)請(qǐng)求結(jié)果一直為空。
最后發(fā)現(xiàn)由于我們的業(yè)務(wù)場(chǎng)景中,請(qǐng)求參數(shù)包含中文要求按指定規(guī)則轉(zhuǎn)碼,導(dǎo)致請(qǐng)求url中包含% ,而RestTemplate會(huì)自動(dòng)調(diào)用encode方法進(jìn)行轉(zhuǎn)義,將%轉(zhuǎn)義成了%25 。
解決方法
自建URI 傳入:
private String getRemoteData(String url) {
logger.info("Request URL :" + url + "|");
String resp = null;
try {
URI uri = new URI(url);
resp = rest.getForObject(uri, String.class);
} catch (URISyntaxException e) {
logger.error("Create URI Exception !");
}
logger.info("Response result : " + resp.toString());
return resp;
}
RestTemplate轉(zhuǎn)碼bug
發(fā)現(xiàn)一個(gè)關(guān)于HTTP的Get請(qǐng)求的罕見bug。
轉(zhuǎn)碼問題的背景
需要向tigergraph服務(wù)端發(fā)送一個(gè)復(fù)雜的get請(qǐng)求,參數(shù)只有一個(gè),但是參數(shù)的值是一個(gè)復(fù)雜json
服務(wù)端收到的值始終是不正常的值。觀察發(fā)現(xiàn),不正常地方在于服務(wù)端本應(yīng)解析為空格的地方都變成了加號(hào)(+)。
以為是代碼寫得有問題,然后使用HTTPclient的原生的方式發(fā)起請(qǐng)求:
public static String doGet(String url) throws Exception{
HttpGet get = new HttpGet(url);
return doMethod(get);
}
private static String doMethod(HttpRequestBase method)throws Exception{
CloseableHttpResponse response = null;
CloseableHttpClient client;
HttpClientBuilder hcb = HttpClientBuilder.create();
HttpRequestRetryHandler hrrh = new DefaultHttpRequestRetryHandler();
HttpClientBuilder httpClientBuilder = hcb.setRetryHandler(hrrh);
client = httpClientBuilder.build();
method.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
method.addHeader(HTTP.CONTENT_TYPE, APPLICATION_JSON);
RequestConfig.Builder confBuilder = RequestConfig.custom();
confBuilder.setConnectTimeout(CONNECT_TIMEOUT);
confBuilder.setConnectionRequestTimeout(REQUEST_TIMEOUT);
confBuilder.setSocketTimeout(SOCKET_TIMEOUT);
RequestConfig config = confBuilder.build();
method.setConfig(config);
response = client.execute(method);
int code = response.getStatusLine().getStatusCode();
String result = EntityUtils.toString(response.getEntity());
response.close();
client.close();
return result;
}
得到結(jié)果還是這個(gè)問題,使用Assured測(cè)試工具構(gòu)建http請(qǐng)求也有這問題。
結(jié)論
后來仔細(xì)檢查了URLEncode.encode方法和RestTemplate源碼實(shí)現(xiàn)后,發(fā)現(xiàn)是客戶端的轉(zhuǎn)碼協(xié)議和服務(wù)端的解碼協(xié)議不匹配導(dǎo)致。
經(jīng)反復(fù)測(cè)試和嚴(yán)重,這個(gè)問題只有參數(shù)中帶有空格時(shí)才會(huì)有,其他字符都不有,比如: / * & 這類特殊字符都沒這問題。
最后的解決方案是替換URL串的轉(zhuǎn)碼后的字符串中的空格為%20,然后使用http client原生的請(qǐng)求方式。
第二個(gè)解決方案是使用RestTemplate的UriComponentsBuilder類,使用(builder.build(false).toUri()獲得URL,參數(shù)必須是false才會(huì)把空格轉(zhuǎn)成%20
/** * urlencode轉(zhuǎn)碼不能隨便用,因?yàn)樗龝?huì)把空格轉(zhuǎn)換成+號(hào),而不是標(biāo)準(zhǔn)的%20字符。 * 對(duì)于spring構(gòu)建的服務(wù)端不會(huì)有這個(gè)問題。但我在tiger服務(wù)器上遇到這種問題。 * 所以u(píng)rlencode只適用于服務(wù)端支持的協(xié)議是RFC1738 * 如果服務(wù)端只支持RFC 2396標(biāo)準(zhǔn),那么服務(wù)端解碼時(shí),會(huì)把加號(hào)+當(dāng)成保留字符,而不轉(zhuǎn)碼 * */
@Override
@SuppressWarnings("all")
public <Req, Resp> Resp doGet(String url, Req request, Class<Resp> responseType) throws Exception {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
Map<String, Object> parameters = (Map<String, Object>)request;
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
builder.queryParam(entry.getKey(), Objects.toString(entry.getValue(), ""));
}
return restTemplate.getForObject(builder.build(false).toUri(), responseType);
}
為什么會(huì)有這個(gè)問題?
根源在于Java語(yǔ)言的URLEncode類只能適用于早期的RFC協(xié)議,通常spring開發(fā)的服務(wù)端是兼容這種模式的。
新版的RFC協(xié)議會(huì)把+號(hào)當(dāng)成關(guān)鍵字不再反轉(zhuǎn)成空格,這通常體現(xiàn)在新技術(shù)上,比如目前用的tigergraph圖數(shù)據(jù)庫(kù)就有這情形。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Boot整合Spring Data Jpa代碼實(shí)例
這篇文章主要介紹了Spring Boot整合Spring Data Jpa代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
Spring?Security?過濾器注冊(cè)脈絡(luò)梳理
這篇文章主要介紹了Spring?Security過濾器注冊(cè)脈絡(luò)梳理,Spring?Security在Servlet的過濾鏈中注冊(cè)了一個(gè)過濾器FilterChainProxy,它會(huì)把請(qǐng)求代理到Spring?Security自己維護(hù)的多個(gè)過濾鏈,每個(gè)過濾鏈會(huì)匹配一些URL,如果匹配則執(zhí)行對(duì)應(yīng)的過濾器2022-08-08
如何在Spring?Boot中使用OAuth2認(rèn)證和授權(quán)
這篇文章主要介紹了如何在Spring?Boot中使用OAuth2認(rèn)證和授權(quán)的相關(guān)資料,OAuth2.0是一種開放的授權(quán)協(xié)議,它允許用戶授權(quán)第三方應(yīng)用訪問其賬戶(或資源),而無(wú)需共享其用戶賬戶憑據(jù),需要的朋友可以參考下2023-12-12
springboot自動(dòng)重啟的簡(jiǎn)單方法
Springboot提供了熱部署的方式,當(dāng)發(fā)現(xiàn)任何類發(fā)生了改變,馬上通過JVM類加載的方式,加載最新的類到虛擬機(jī)中。這篇文章主要介紹了springboot自動(dòng)重啟的實(shí)現(xiàn)方法,需要的朋友可以參考下2018-04-04
SpringCloud Config連接git與數(shù)據(jù)庫(kù)流程分析講解
springcloud config是一個(gè)解決分布式系統(tǒng)的配置管理方案。它包含了 client和server兩個(gè)部分,server端提供配置文件的存儲(chǔ)、以接口的形式將配置文件的內(nèi)容提供出去,client端通過接口獲取數(shù)據(jù)、并依據(jù)此數(shù)據(jù)初始化自己的應(yīng)用2022-12-12
java+mysql實(shí)現(xiàn)商品搶購(gòu)功能
這篇文章主要為大家詳細(xì)介紹了java+mysql實(shí)現(xiàn)商品搶購(gòu)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
Spring Security學(xué)習(xí)之rememberMe自動(dòng)登錄的實(shí)現(xiàn)
這篇文章主要給大家介紹了關(guān)于Spring Security學(xué)習(xí)之rememberMe自動(dòng)登錄的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
Java并發(fā)編程示例(二):獲取和設(shè)置線程信息
這篇文章主要介紹了Java并發(fā)編程示例(二):獲取和設(shè)置線程信息,本文是系列文章的第二篇,本文著重講解Thread類的幾個(gè)重要屬性,需要的朋友可以參考下2014-12-12

