上一篇介紹了 RestTemplate 的基本使用姿勢,在文末提出了一些擴展的高級使用姿勢,本篇將主要集中在如何攜帶自定義的請求頭,如設置 User-Agent,攜帶 Cookie
- Get 攜帶請求頭
- Post 攜帶請求頭
- 攔截器方式設置統一請求頭
I. 項目搭建
1. 配置
借助 SpringBoot 搭建一個 SpringWEB 項目,提供一些用於測試的 REST 服務
- SpringBoot 版本:
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