【SpringBoot WEB 系列】RestTemplate 之自定義請求頭
上一篇介紹了 RestTemplate 的基本使用姿勢,在文末提出了一些擴展的高級使用姿勢,本篇將主要集中在如何攜帶自定義的請求頭,如設置 User-Agent,攜帶 Cookie
- Get 攜帶請求頭
- Post 攜帶請求頭
- 攔截器方式設置統一請求頭
<!-- more -->
I. 項目搭建
1. 配置
藉助 SpringBoot 搭建一個 SpringWEB 項目,提供一些用於測試的 REST 服務
2.2.1.RELEASE spring-boot-stater-web
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
爲了後續輸出的日誌更直觀,這裏設置了一下日誌輸出格式,在配置文件 application.yml
中,添加
logging: pattern: console: (%msg%n%n){blue}
2. Rest 服務
添加三個接口,分別提供 GET 請求,POST 表單,POST json 對象,然後返回請求頭、請求參數、cookie,具體實現邏輯相對簡單,也不屬於本篇重點,因此不贅述說明
@RestController public class DemoRest { private String getHeaders(HttpServletRequest request) { Enumeration<String> headerNames = request.getHeaderNames(); String name; JSONObject headers = new JSONObject(); while (headerNames.hasMoreElements()) { name = headerNames.nextElement(); headers.put(name, request.getHeader(name)); } return headers.toJSONString(); } private String getParams(HttpServletRequest request) { return JSONObject.toJSONString(request.getParameterMap()); } private String getCookies(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies == null || cookies.length == 0) { return ""; } JSONObject ck = new JSONObject(); for (Cookie cookie : cookies) { ck.put(cookie.getName(), cookie.getValue()); } return ck.toJSONString(); } private String buildResult(HttpServletRequest request) { return buildResult(request, null); } private String buildResult(HttpServletRequest request, Object obj) { String params = getParams(request); String headers = getHeaders(request); String cookies = getCookies(request); if (obj != null) { params += " | " + obj; } return "params: " + params + "\nheaders: " + headers + "\ncookies: " + cookies; } @GetMapping(path = "get") public String get(HttpServletRequest request) { return buildResult(request); } @PostMapping(path = "post") public String post(HttpServletRequest request) { return buildResult(request); } @Data @NoArgsConstructor public static class ReqBody implements Serializable { private static final long serialVersionUID = -4536744669004135021L; private String name; private Integer age; } @PostMapping(path = "body") public String postBody(@RequestBody ReqBody body) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); return buildResult(request, body); } }
II. 使用姿勢
最常見的攜帶請求頭的需求,無非是 referer 校驗,user-agent 的防爬以及攜帶 cookie,使用 RestTemplate 可以藉助 HttpHeaders
來處理請求頭
1. Get 攜帶請求頭
前一篇博文介紹了 GET 請求的三種方式,但是 getForObject
/ getForEntity
都不滿足我們的場景,這裏需要引入 exchange
方法
public void header() { RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"); headers.set("cookie", "my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;"); // 注意幾個請求參數 HttpEntity<String> res = restTemplate .exchange("http://127.0.0.1:8080/get?name=一灰灰&age=20", HttpMethod.GET, new HttpEntity<>(null, headers), String.class); log.info("get with selfDefine header: {}", res); }
exchange 的使用姿勢和我們前面介紹的 postForEntity
差不多,只是多了一個指定 HttpMethod 的參數而已
重點在於將請求頭塞入 HttpEntity
輸出結果
(get with selfDefine header: <200,params: {"name":["一灰灰"],"age":["20"]} headers: {"cookie":"my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;","host":"127.0.0.1:8080","connection":"keep-alive","accept":"text/plain, application/json, application/*+json, */*","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"} cookies: {"my_user_id":"haha123","UN":"1231923","gr_user_id":"welcome_yhh"},[Content-Type:"text/plain;charset=UTF-8", Content-Length:"447", Date:"Mon, 29 Jun 2020 07:48:49 GMT"]>
2. Post 攜帶請求頭
post 攜帶請求頭,也可以利用上面的方式實現;當然我們一般直接藉助 postForObject/postForEntity
就可以滿足需求了
// httpHeaders 和上面的一致,這裏省略相關代碼 // post 帶請求頭 MultiValueMap<String, Object> params = new LinkedMultiValueMap<>(); params.add("name", "一灰灰Blog"); params.add("age", 20); String response = restTemplate .postForObject("http://127.0.0.1:8080/post", new HttpEntity<>(params, headers), String.class); log.info("post with selfDefine header: {}", response);
輸出結果
(post with selfDefine header: params: {"name":["一灰灰Blog"],"age":["20"]} headers: {"content-length":"338","cookie":"my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;","host":"127.0.0.1:8080","content-type":"multipart/form-data;charset=UTF-8;boundary=2VJHo9r6lYgR_WoSBy1FQC40jvBvGtLk7QUaymGg","connection":"keep-alive","accept":"text/plain, application/json, application/*+json, */*","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"} cookies: {"my_user_id":"haha123","UN":"1231923","gr_user_id":"welcome_yhh"}
3. 攔截器方式
如果我們可以確定每次發起請求時,都要設置一個自定義的 User-Agent
,每次都使用上面的兩種姿勢就有點繁瑣了,因此我們是可以通過攔截器的方式來添加通用的請求頭,這樣使用這個 RestTemplate 時,都會攜帶上請求頭
// 藉助攔截器的方式來實現塞統一的請求頭 ClientHttpRequestInterceptor interceptor = (httpRequest, bytes, execution) -> { httpRequest.getHeaders().set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"); httpRequest.getHeaders().set("cookie", "my_user_id=haha123; UN=1231923;gr_user_id=interceptor;"); return execution.execute(httpRequest, bytes); }; restTemplate.getInterceptors().add(interceptor); response = restTemplate.getForObject("http://127.0.0.1:8080/get?name=一灰灰&age=20", String.class); log.info("get with selfDefine header by Interceptor: {}", response);
上面這個使用姿勢比較適用於通用的場景,測試輸出
(get with selfDefine header by Interceptor: params: {"name":["一灰灰"],"age":["20"]} headers: {"cookie":"my_user_id=haha123; UN=1231923;gr_user_id=interceptor;","host":"127.0.0.1:8080","connection":"keep-alive","accept":"text/plain, application/json, application/*+json, */*","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"} cookies: {"my_user_id":"haha123","UN":"1231923","gr_user_id":"interceptor"}
4. 請求頭錯誤使用姿勢
在我們使用自定義請求頭時,有一個需要特殊重視的地方,HttpHeaders 使用不當,可能導致請求頭爆炸
/** * 錯誤的請求頭使用姿勢 */ public void errorHeader() { RestTemplate restTemplate = new RestTemplate(); int i = 0; // 爲了複用headers,避免每次都創建這個對象,但是在循環中又是通過 add 方式添加請求頭,那麼請求頭會越來越膨脹,最終導致請求超限 // 這種case,要麼將add改爲set;要麼不要在循環中這麼幹 HttpHeaders headers = new HttpHeaders(); while (++i < 5) { headers.add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"); headers.add("cookie", "my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;"); HttpEntity<String> res = restTemplate.exchange("http://127.0.0.1:8080/get?name=一灰灰&age=20", HttpMethod.GET, new HttpEntity<>(null, headers), String.class); log.info("get with selfDefine header: {}", res); } }
上面演示的關鍵點爲
- 希望複用 HttpHeaders
-
headers.add
方式添加請求頭;而不是前面的set
方式
輸出如下,請注意每一次請求過後,請求頭膨脹了一次
(get with selfDefine header: <200,params: {"name":["一灰灰"],"age":["20"]} headers: {"cookie":"my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;","host":"127.0.0.1:8080","connection":"keep-alive","accept":"text/plain, application/json, application/*+json, */*","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"} cookies: {"my_user_id":"haha123","UN":"1231923","gr_user_id":"welcome_yhh"},[Content-Type:"text/plain;charset=UTF-8", Content-Length:"447", Date:"Mon, 29 Jun 2020 07:48:49 GMT"]> (get with selfDefine header: <200,params: {"name":["一灰灰"],"age":["20"]} headers: {"cookie":"my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;; my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;","host":"127.0.0.1:8080","connection":"keep-alive","accept":"text/plain, application/json, application/*+json, */*","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"} cookies: {"my_user_id":"haha123","UN":"1231923","gr_user_id":"welcome_yhh"},[Content-Type:"text/plain;charset=UTF-8", Content-Length:"503", Date:"Mon, 29 Jun 2020 07:48:49 GMT"]> (get with selfDefine header: <200,params: {"name":["一灰灰"],"age":["20"]} headers: {"cookie":"my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;; my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;; my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;","host":"127.0.0.1:8080","connection":"keep-alive","accept":"text/plain, application/json, application/*+json, */*","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"} cookies: {"my_user_id":"haha123","UN":"1231923","gr_user_id":"welcome_yhh"},[Content-Type:"text/plain;charset=UTF-8", Content-Length:"559", Date:"Mon, 29 Jun 2020 07:48:49 GMT"]> (get with selfDefine header: <200,params: {"name":["一灰灰"],"age":["20"]} headers: {"cookie":"my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;; my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;; my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;; my_user_id=haha123; UN=1231923;gr_user_id=welcome_yhh;","host":"127.0.0.1:8080","connection":"keep-alive","accept":"text/plain, application/json, application/*+json, */*","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"} cookies: {"my_user_id":"haha123","UN":"1231923","gr_user_id":"welcome_yhh"},[Content-Type:"text/plain;charset=UTF-8", Content-Length:"615", Date:"Mon, 29 Jun 2020 07:48:49 GMT"]>
II. 其他
0. 項目&系列博文
系列博文
源碼
- 工程: https://github.com/liuyueyi/spring-boot-demo
- 項目: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/221-web-resttemplate
1. 一灰灰 Blog
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛
- 一灰灰 Blog 個人博客 https://blog.hhui.top
- 一灰灰 Blog-Spring 專題博客 http://spring.hhui.top