Spring之RestTemplate中級使用篇
前面一篇介紹瞭如何使用RestTemplate
發起post和get請求,然而也只能滿足一些基本的場景,對於一些特殊的如需要設定請求頭,新增認證資訊等場景,卻沒有提及可以怎麼做,這一篇則相當於進階版,將主要介紹
- get/post請求如何攜帶 header
- post傳檔案可以怎麼玩, post提交json串怎麼處理
exchange
方法的使用姿勢
I. 請求頭設定
首先一個問題就是為什麼要設定請求頭?
我們通過瀏覽器正常訪問的介面,可能通過程式碼直接訪問時,就會提示403
而這樣的原因,較多的一個可能就是後端的請求做了限制,比如根據請求的agent,判斷是否為爬蟲;根據referer判斷是否要返回資料等等;而後端進行校驗的條件中,往往會拿請求頭的資料,因此這也就要求我們在使用時,主動的塞入一些請求頭資訊
1. Get請求
直接看RestTemplate
提供的幾個Get請求介面,並沒有發現有設定請求頭的地方,是不是就表明沒法設定請求頭了?
答案檔案是能設定了,具體的使用思路有點類似mvc中的攔截器,自定義一個攔截器,然後在你實際發起請求時,攔截並設定request的請求頭
注意到 RestTemplate
的父類InterceptingHttpAccessor
提供了一個接收Interceptor的介面org.springframework.http.client.support.InterceptingHttpAccessor#setInterceptors
,這個就是我們所需要的關鍵點(講道理,除非事先就知道有這麼個玩意,不然真讓你去找,還不一定能找到)
所以第一步就是寫一個ClientHttpRequestInterceptor
類,新增請求頭
public class UserAgentInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
HttpHeaders headers = request.getHeaders();
headers.add(HttpHeaders.USER_AGENT,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36");
return execution.execute(request, body);
}
}
複製程式碼
下一步就是在建立RestTemplate物件之後,宣告直譯器並測試使用了
@Test
public void testGetHeader() {
String url = "http://localhost:8080/agent?name=一灰灰Blog";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(new UserAgentInterceptor()));
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
System.out.println(response.getStatusCode() + " | " + response.getBody());
}
複製程式碼
首先在測試之前,先搭一個服務,簡單判斷agent,不滿足條件的直接403, 後端mock程式碼如下
@ResponseBody
@RequestMapping(path = "agent")
public String agent(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "name", required = false) String name) throws IOException {
String agent = request.getHeader(HttpHeaders.USER_AGENT);
if (StringUtils.isEmpty(agent) || !agent.contains("WebKit")) {
response.sendError(403, " illegal agent ");
}
return "welcome " + name;
}
複製程式碼
上面執行後輸出如下,新增請求頭後正常返回
當然也需要對比下不設定agent的情況了,直接拋了一個異常出來了(說明,不顯示覆蓋User-Agent時,後端接收到的agent為: Java/1.8.0_171
上面雖然只給了設定User-Agent的例子,但是其他的請求頭,都是可以放在自定義的Interceptor
中新增進去的
2. Post請求
當然get請求使用的這種姿勢,對於post而言或者對於其他的http請求方法而言,都是通用的,而對於post請求來說,還有另外一種方式,就是requset引數,可以攜帶request headers
首先mock一個後端介面
@ResponseBody
@RequestMapping(path = "post", method = {RequestMethod.GET, RequestMethod.OPTIONS, RequestMethod.POST},
produces = "charset/utf8")
public String post(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "email", required = false) String email,
@RequestParam(value = "nick", required = false) String nick) throws IOException {
String agent = request.getHeader(HttpHeaders.USER_AGENT);
if (StringUtils.isEmpty(agent) || !agent.contains("WebKit")) {
response.sendError(403, " illegal agent ");
return null;
}
return "success email=" + email + "&nick=" + URLEncoder.encode(nick, "UTF-8") + "&status=success";
}
複製程式碼
簡單的使用姿勢如下
@Test
public void testPostHeader() {
String url = "http://localhost:8080/post";
String email = "test@hhui.top";
String nick = "一灰灰Blog";
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("email", email);
params.add("nick", nick);
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
System.out.println(response.getStatusCode() + " | " + response.getBody());
}
複製程式碼
從上面程式碼可以看出,具體的使用姿勢相比於不新增請求頭時,只是多了一個封裝
- 具體的header資訊分裝到
HttpHeaders
物件中 - 請求引數依然封裝到
MultiValueMap
中 - 然後根據請求頭 + 請求引數,構建
HttpEntity
物件,將這個作為post的請求request引數傳入
當然作為對比,當不加入headers時,看下返回什麼鬼, 406異常,但是我們後端定義的是403,為什麼會返回406呢?
3. exchange 方式
另外還會關注到RestTemplate還提供了一個exchange方法,這個相當於一個公共的請求模板,使用姿勢和get/post沒有什麼區別,只是可以由呼叫發自己來選擇具體的請求方法
使用exchange對上面的post請求進行簡單的替換如下, 基本上除了多一個引數之外沒有什麼區別了
@Test
public void testPostHeader() {
String url = "http://localhost:8080/post";
String email = "test@hhui.top";
String nick = "一灰灰Blog";
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("email", email);
params.add("nick", nick);
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
System.out.println(response.getStatusCode() + " | " + response.getBody());
}
複製程式碼
那麼問題來了,為什麼要有這個東西?或者說這個介面的提供可以帶來什麼好處?
- 當你寫一個公共的Rest工具類時,就比較方便了,底層統一,具體的方法由上層業務方選擇即可
- get可以通過這種方式直接新增請求頭(也就是不需要第一種case中的自定義攔截器來塞入header,顯然更加靈活)
II. Post引數提交
前面的post引數提交,其實預設採用的是 application/x-www-form-urlencoded
方式,即是我們最常見的表單提交方式,在瀏覽器中的表現形式如下
[站外圖片上傳中...(image-74df6f-1534334982345)]
此外,還有一種直接提交json串的方式,在前文 《180730-Spring之RequestBody的使用姿勢小結》中有說明,具體瀏覽器中表現形式為
[站外圖片上傳中...(image-92a91d-1534334982346)]
所以接下來的問題就是,RestTemplate要怎麼處理呢?
1. json串提交
建議在看下面的內容之前,先看一下上面的那篇博文,理解下RequestBody
是什麼東西
首先搭建一個後端
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Req {
private String key;
private Integer size;
}
@ResponseBody
@RequestMapping(value = "/body", method = {RequestMethod.POST, RequestMethod.GET, RequestMethod.OPTIONS})
public String body(@RequestBody Req req) {
return req.toString();
}
複製程式碼
然後使用方式,無非就是在請求頭中新增下Content-Type為Application/json
@Test
public void testPostRequestBody() {
String url = "http://localhost:8080/body";
String email = "test@hhui.top";
String nick = "一灰灰Blog";
Map<String, String> params = new HashMap<>();
params.put("email", email);
params.put("nick", nick);
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, "application/json");
HttpEntity<Map<String, String>> request = new HttpEntity<>(params, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
System.out.println(response.getStatusCode() + " | " + response.getBody());
}
複製程式碼
注意下post引數,是放在Map容器中,而不是之前的MultiValueMap
執行時截圖如下
2. 檔案上傳
post除了傳表單資料(json串)之外,還有一個常見的就是上傳檔案了,實際上使用RestTemplate來實現檔案上傳,算是比較簡單的了,和前面的使用基本上也沒有什麼差別,只是將檔案作為params引數而已
首先搭建一個Controller後端服務,簡單的獲取檔案內容,並返回
@ResponseBody
@PostMapping(path = "file")
public String file(MultipartHttpServletRequest request) throws IOException {
MultipartFile file = request.getFile("file");
if (file == null) {
return "no file!";
}
BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()));
StringBuilder builder = new StringBuilder();
String line = reader.readLine();
while (line != null) {
builder.append(line);
line = reader.readLine();
}
reader.close();
return builder.toString();
}
複製程式碼
然後就是實際的測試用例,將檔案包裝在FileSystemResource
物件中,然後塞入MultiValueMap
中,注意下面的使用並沒有顯示新增請求頭,而這種時候,content-type 通過斷點檢視實際為 content-type = multipart/form-data;
@Test
public void testPostFile() {
String url = "http://localhost:8080/file";
FileSystemResource resource = new FileSystemResource(new File("/tmp/test.txt"));
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("file", resource);
params.add("fileName", "test.txt");
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(url, params, String.class);
System.out.println(response);
}
複製程式碼
III. 小結
本篇主要介紹如何給RestTemplate發起的請求,新增請求頭,以及完成某些特定的請求,下面小結一下使用姿勢
1. 設定header
兩種方式
- 一個是設定
Interceptors
,在攔截器中主動新增上對應的請求頭即可,適用於為所有的請求新增統一的請求頭的場景- 這種方式不僅僅能用來設定請求頭,還可以在其中做很多其他的事情
- 另外一種方式針對
postForXXX
和exchange
兩種請求方式而言,同樣自己設定請求頭HttpHeader
,然後將請求頭和params封裝到HttpEntity
,作為request引數提交即可
2. 特殊的請求方式
json串的提交
- 設定請求頭的content-type為
Applicaiton/json
- 將請求的資料封裝到map容器內(或者直接定義一個請求引數的DTO物件也可以)
- 然後將header和引數封裝到
HttpEntity
中,發起請求即可
檔案上傳
- 將資原始檔塞入到
MultiValueMap
中即可,和普通的請求方式沒有什麼區別
3. 其他
初級篇介紹瞭如何使用RestTemplate發起簡單的GET/POST請求;
中級篇則介紹請求的過程中新增設定請求頭,以及某些特殊的請求可以怎麼處理
顯然還會有高階篇,除了上面的東西,我們還需要知道些什麼呢?
- 請求超時的設定比較實用,有必要了解下
- 在訪問某些特殊的網站時,代理的設定也避不開
- 請求有身份鑑權的情況下,如何安全的攜帶自己的身份呢?
- RestTemplate底層使用的是什麼網路庫做的網路訪問?可以用其他的進行替換麼?(答案肯定是可以,不然這個命名就標準的名不副實了)
關於高階篇,坐等更新
IV. 其他
0. 相關博文
1. 一灰灰Blog: liuyueyi.github.io/hexblog
一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
2. 宣告
盡信書則不如,已上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840
3. 掃描關注
小灰灰Blog&公眾號
知識星球