【SpringBoot WEB 系列】RestTemplate 之自定义请求头

栏目: IT技术 · 发布时间: 4年前

内容简介:上一篇介绍了 RestTemplate 的基本使用姿势,在文末提出了一些扩展的高级使用姿势,本篇将主要集中在如何携带自定义的请求头,如设置 User-Agent,携带 Cookie

【SpringBoot WEB 系列】RestTemplate 之自定义请求头

【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. 项目&系列博文

系列博文

源码

1. 一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

【SpringBoot WEB 系列】RestTemplate 之自定义请求头


以上所述就是小编给大家介绍的《【SpringBoot WEB 系列】RestTemplate 之自定义请求头》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming in Haskell

Programming in Haskell

Graham Hutton / Cambridge University Press / 2007-1-18 / GBP 34.99

Haskell is one of the leading languages for teaching functional programming, enabling students to write simpler and cleaner code, and to learn how to structure and reason about programs. This introduc......一起来看看 《Programming in Haskell》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具