

【SpringBoot WEB 系列】RestTemplate 之自定义请求头
source link: https://segmentfault.com/a/1190000023051624
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

上一篇介绍了 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
Recommend
-
43
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。 我之前的HTTP开发是用apache的HttpClient开发,代码复杂,还得操心资源
-
52
RestTemplate 是 Spring 提供的用于访问Rest服务的客户端, RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。 我之前的HTTP开发是用apache的HttpCl...
-
54
使用过 Springboot 的对上面这个图案肯定不会陌生,Springboot 启动的同时会打印上面的图案,并带有版本号。查看官方文档可以找到关于 banner...
-
41
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。我之前的HTTP开发是用apache的HttpClient开发,代码复杂,还得操心资源回收等。代码很复杂,冗余代码多,稍微截个图,这是...
-
56
SpringMVC提供了各种姿势的http参数解析支持,从前面的GET/POST参数解析篇也可以看到,加一个 @RequsetParam 注解就可以将方法参数与http参数绑定,看到这时自然就会好奇这是怎么做到的,我们能不能自己定义一种参数解析规则...
-
52
近期使用RestTemplate访问外部资源时,发现一个有意思的问题。因为权限校验失败,对方返回的401的http code,此外返回数据中也会包含一些异常提示信息;然而在使用RestTemplate访问时,却是直接抛了如下提示401的异常,并不能拿到提示信息...
-
53
前面介绍的RestTemplate的所有使用姿势都是不需要鉴权的,然而实际情况可不一定都这么友好;Http Basic Auth属于非常基础的一种鉴权方式了,将用户名和密码以Base64编码之后,携带在请求头,从而实现身份校验; 本文将主要介绍Re...
-
12
技术之前,先读诗书:低声问向谁行宿,城上已三更。 在开发过程中,有时会遇到需要传很大的请求头的情况,即Http Request Header过大。这里可能会报以下错误:
-
11
前面一篇博文介绍了一个@Value的一些知识点,其中提了一个点,@Value对应的配置,除了是配置文件中之外,可以从其他的数据源中获取么,如从redis,db,http中获取配置? 了解过SpringCloud Config的可以给出确切的答案,可以...
-
7
【DB系列】SpringBoot缓存注解@Cacheable之自定义key策略及缓存失效时间指定 ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK