HttpClient & RestTemplate

一、Http请求与响应格式

请求格式

  1. 请求行(包含请求方法(Get、Post等)、URI、Http版本)
  2. 请求头
  3. 请求体

响应格式

  1. 状态行(包含HTTP版本、状态码、描述信息)
  2. 响应头
  3. 响应体

二、HttpClient

1.基本组件

HttpClient是apache提供的用于传输HTTP消息的工具包。HttpClient中一些常用的组件:

  1. HttpClientBuilder:用于创建Http客户端实例。

  2. CloseableHttpClient:表示用于发送请求和接收响应的Http客户端。

  3. HttpRequest接口:表示一个Http请求,对于不同的请求方法有不同的实现类:

    • HttpGet:对应get请求方法
    • HttpPost:对应post请求方法
  4. HttpResponse接口:表示一个Http响应。

  5. HttpEntity接口:表示请求/响应实体。用于发送请求体和获取响应体。(它封装了状态行、响应头、响应体)

  6. HttpStatus:封装了响应的状态码

  7. URIBuilder:一个辅助组件,用于动态创建URI的工具类

  8. EntityUtils:解析Http消息体的工具类。(由于网络通信只能传输字节序列,该工具就是用于将Http消息体中的字节序列解码为各种格式的字符串)

2.发送GET请求

public static void main(String[] args) throws IOException {
    CloseableHttpClient httpClient = null;
    try {
        //1.获取Http客户端对象
        httpClient = HttpClientBuilder.create().build();
        //2.创建请求的URI
        URI uri = new URIBuilder()
            .setScheme("https")
            .setHost("*****.cn")
            .setPath("api/**")
            .addParameter("param1","value1")      //get请求将参数拼接到uri后
            .addParameter("param2","value2")
            .addParameter("param3","value3")
            .addParameter("limit","10")
            .build();
        //等同与 new URI(https://*****.cn/api/**?param1=value1&param2=value2&param3=value3&limit=10)

        //3.创建GET请求
        HttpGet httpGet = new HttpGet();
        httpGet.setURI(uri);
        //设置请求头
        httpGet.addHeader("cookie","……");
        httpGet.addHeader("user-agent","……");


        //4.发送请求获取服务端响应
        HttpResponse httpResponse = httpClient.execute(httpGet);
        //获取响应体
        HttpEntity responseEntity = httpResponse.getEntity();
        /*
            解析响应体
            服务器通过输出流将响应的字节数据输送到流中,
            我们可以通过responseEntity获取客户端的输入流来读取服务器响应数据并将其解码。
            需要注意的是 我们应该确保客户端输入流的关闭。这里没有显式的关闭是因为EntityUtils工具类在解析完响应的字节流之后会帮我们关闭。
        */
        String content = EntityUtils.toString(responseEntity,"UTF-8");
        System.out.println(content);
    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        //关闭连接池内所有的socket连接
        if(httpClient != null){
            httpClient.close();
        }
    }
}

3.发送Post请求

public static void main(String[] args) throws Exception {
    CloseableHttpClient httpClient = null;
    try {
        //1. 获取Http客户端对象
        httpClient = HttpClientBuilder.create().build();
        //2. 构造uri和请求体
        URI uri = new URI("https://……");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("pageIndex",1);
        jsonObject.put("pageSize",20);
        //post请求将参数设置到请求体中。
        StringEntity entity = new StringEntity(jsonObject.toJSONString(), ContentType.APPLICATION_JSON);
        //3.构造POST请求
        HttpPost httpPost = new HttpPost(uri);
        //添加请求体
        httpPost.setEntity(entity);
        //添加请求头
        httpPost.addHeader("Cache-Control","no-cache");
        httpPost.addHeader("Content-Type","application/json");
        httpPost.addHeader("token","……");
        httpPost.addHeader("user-agent","……");
        //4.执行请求(同步执行)
        HttpResponse httpResponse = httpClient.execute(httpPost);
        HttpEntity responseEntity = httpResponse.getEntity();
        //5.解析响应
        String content = EntityUtils.toString(responseEntity,"UTF-8");
        System.out.println(content);
    } catch (IOException e) {
        e.printStackTrace();
    }finally{
        if(httpClient != null){
            httpClient.close();
        }
    }
}

4.HttpClientBuilder(客户端配置)

HttpClientBuilder用于创建HttpClient实例,在创建HttpClient之前可以通过HttpClientBuilder对其进行一些自定义的设置。(如果不自己定义则框架会采用默认的设置)。

4.1 设置连接管理器(管理Socket连接)

HttpClientConnectionManager接口表示HttpClient的连接管理器,通过该组件可以进行如下设置:

  • SocketConfig:底层Socket设置
  • 设置路由管理等

框架提供了两个具体的实现:

  • PoolingHttpClientConnectionManager: 它管理客户端连接池,并能向多个执行中的线程提供连接请求。在每条路由的基础上汇总连接。如果在管理器连接池中有一条可用的持久连接,它将复用该条连接提供给请求的路由,而不会创建一条全新的连接。(默认使用)
  • BasicHttpClientConnectionManager: 一次只维护一个连接。即使这个类是线程安全的,也应该只用于一个执行中的线程上。

4.2 设置连接存活策略

在HTTP 1.0中,每一次请求响应之后,下一次的请求需要断开之前的连接,再重新开始;

在HTTP 1.1中,使用keep-alive在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数,也意味着可以减少TIME_WAIT状态连接,以此提高性能和提高httpd服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()调用)。

因此便出现了Connection: keep-alive的设置,用于建立长连接,即我们所说的Keep-Alive模式。

我们通过在请求头中设置(“Connection”: “Keep-Alive”)来开启Kepp-Alive模式。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间;
一次完成的http请求是否能够保持,同时也要靠服务端是否具备Keep-Alive能力。

ConnectionReuseStrategy接口

//该方法的作用就是在一次请求之后,这个连接能够被维持。
//如果返回false,则表示应该立即关闭连接
//如果返回true,则表示应该保持这个连接从而可以应用于其他请求
public interface ConnectionReuseStrategy {
    boolean keepAlive(HttpResponse var1, HttpContext var2);
}

该接口的默认实现为DefaultClientConnectionReuseStrategy。默认的策略大致如下:

  1. 判断“Connection”请求头的值若为"Close",则返回false。
  2. 判断服务器响应的Http协议版本,若为HTTP 1.0及之前则返回false,否则为true。

ConnectionKeepAliveStrategy接口

public interface ConnectionKeepAliveStrategy {
    long getKeepAliveDuration(HttpResponse var1, HttpContext var2);
}

该接口的默认实现为DefaultConnectionKeepAliveStrategy。默认的实现为:
获取服务器响应头"Keep-Alive"的值。获得timeout的值,得到超时时间。(Keep-Alive: timeout=5, max=100)

4.3 HttpClient配置示例

public static void main(String[] args) {
    //创建Socket配置
    SocketConfig socketConfig = SocketConfig.custom()
        //是否立即发送数据,设置为true会关闭Socket缓冲,默认为false。
        .setTcpNoDelay(false)
        //是否可以在一个进程关闭Socket后,即使它还没有释放端口,其它进程还可以立即重用端口
        .setSoReuseAddress(true)
        //接收数据的等待超时时间,单位ms
        .setSoTimeout(500)
        //关闭Socket时,要么发送完所有数据,要么等待60s后,就关闭连接,此时socket.close()是阻塞的
        .setSoLinger(60)
        //开启监视TCP连接是否有效
        .setSoKeepAlive(true)
        .build();

    PoolingHttpClientConnectionManager pcm = new PoolingHttpClientConnectionManager();
    //设置socket配置
    pcm.setDefaultSocketConfig(socketConfig);
    //设置最大连接数
    pcm.setMaxTotal(600);
    //设置每个路由默认最大连接数
    pcm.setDefaultMaxPerRoute(100);
    HttpHost httpHost = new HttpHost("localhost", 54188);
    // 设置目标主机对应的路由的最大连接数,会覆盖setDefaultMaxPerRoute设置的默认值
    pcm.setMaxPerRoute(new HttpRoute(httpHost), 200);

    HttpClientBuilder.create()
        //设置Http连接管理器
        .setConnectionManager(pcm)  
        //设置连接重用策略
        .setConnectionReuseStrategy(new DefaultConnectionReuseStrategy())
        //设置连接存活策略
        .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
        /*
            开启定时检测功能(IdleConnectionEvictorl类通过线程一直检测):
                - 清理过期的连接
                - 如果指定了最大空闲时间,则清理空闲连接
        */
        .setConnectionManagerShared(false)
        //设置最大空闲时间。默认10s
        .evictIdleConnections(20, TimeUnit.SECONDS)
        //等……
        ;
}

三、RestTemplate

RestTemplate是Spring提供的工具,其本身提供了一层非常高且优雅的抽象,可以支持任意的第三方http客户端工具,并支持无侵入式的替换,非常强大。虽然本身是基于spring的,但仅仅使用了spring的utils,也可以在非spring项目中使用。

RestTemplate 默认依赖 JDK 提供 http 连接的能力(HttpURLConnection),如果有需要的话也可以通过设置HttpRequestFactory的方式集成其他Http客户端。

集成HttpClient

public static RestTemplate restTemplate(){
	HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
	// 连接超时时间设置(单位:毫秒)
	clientHttpRequestFactory.setConnectTimeout(1000);
	// 数据读取超时时间设置(单位:毫秒)
	clientHttpRequestFactory.setReadTimeout(1000);
	// 从连接池获取请求连接的超时时间(单位:毫秒)
	clientHttpRequestFactory.setConnectionRequestTimeout(1000);
	return new RestTemplate(clientHttpRequestFactory);
}
    
public static HttpClient httpClient(){
	SocketConfig socketConfig = SocketConfig.custom()
                .setTcpNoDelay(false)
                .setSoReuseAddress(true)
                .setSoTimeout(500)
                .setSoLinger(60)
                .setSoKeepAlive(true)
                .build();

	PoolingHttpClientConnectionManager pcm = new PoolingHttpClientConnectionManager();
        pcm.setDefaultSocketConfig(socketConfig);
        pcm.setMaxTotal(600);
        pcm.setDefaultMaxPerRoute(100);
        HttpHost httpHost = new HttpHost("localhost", 54188);
        pcm.setMaxPerRoute(new HttpRoute(httpHost), 200);

	return HttpClientBuilder.create()
                .setConnectionManager(pcm)
                .setConnectionReuseStrategy(new DefaultConnectionReuseStrategy())
                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
                .setConnectionManagerShared(false)
                .evictIdleConnections(20, TimeUnit.SECONDS)
                .build();
}

1. GET请求

@RequestMapping(value = "/get/person")
public Person getPerson(@RequestParam String name, @RequestParam Integer age){
	return new Person(name, age);
}

//RestTemplate访问
public static void main(String[] args) {
	String url = "http://localhost:8080/get/person?name=yhj&age=20";
	//方式一:getForObject
	Person person = restTemplate.getForObject(url,Person.class);
	//方式二:getForEntity
	ResponseEntity<Person> responseEntity = restTemplate.getForEntity(url,Person.class);
	System.out.println("方式一:" + person);
	System.out.println("方式二:" + responseEntity.getBody());
}

RestTemplate 类是为调用REST服务而设计的,所以它的API设计也是与其紧密相连,如果你的http接口是这样的:

@RequestMapping(value = "/get/person/{name}/{age}")
public Person getPersonById(@PathVariable("name") String name, @PathVariable("age") Integer age){
	return new Person(name,age);
}

public static void main(String[] args) {
	String url = "http://localhost:8080/get/person/{name}/{age}";
	//设置url动态参数
	Map<String, String> uriVariables = new HashMap<>(2);
	uriVariables.put("age", "20");
	uriVariables.put("name", "yhj");
	//方式一
	Person person = restTemplate.getForObject(url,Person.class,uriVariables);
	//方式二
	ResponseEntity<Person> responseEntity = restTemplate.getForEntity(url,Person.class,uriVariables);
}

如果http接口返回的是带泛型类型参数的,则需要使用exchange这个通用的方法,入参ParameterizedTypeReference指定泛型类型,如下:

@RequestMapping(value = "get/person/list")
public List<Person> getPersonList(){
	List personList = new ArrayList();
	personList.add(new Person("yy",10));
	personList.add(new Person("hh",20));
	personList.add(new Person("jj",30));
	return personList;
}

public static void main(String[] args) {
	String url = "http://localhost:8080/get/person/list";
	ResponseEntity<List<Person>> responseEntity = restTemplate.exchange(url, HttpMethod.GET,null, new ParameterizedTypeReference<List<Person>>(){});
	System.out.println(JSON.toJSONString(responseEntity.getBody()));
}

添加请求头

org.springframework.http.HttpEntity中封装了请求头和请求体,get请求没有请求体,不过可以通过HttpEntity添加请求头。如下:

public static void main(String[] args) {
	String url = "http://localhost:8080/get/person/list";
	MultiValueMap<String, String> headers = new HttpHeaders();
	headers.add("head_name","value");
	HttpEntity requestEntity = new HttpEntity(headers);
    ResponseEntity<List<Person>> responseEntity = restTemplate.exchange(url, HttpMethod.GET,requestEntity, new ParameterizedTypeReference<List<Person>>(){});
}

2. POST请求

2.1. 普通表单请求

@RequestMapping(value = "/person")
public Person getPerson(@RequestParam String name, @RequestParam Integer age){
	return new Person(name, age);
}

public static void main(String[] args) {
	String url = "http://localhost:8080/person";
	MultiValueMap<String, String> headers = new HttpHeaders();
	headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
	MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
	body.add("name", "yhj");
	body.add("age", "20");
	HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity(body, headers);
	//方式一
	ResponseEntity<Person> responseEntity = restTemplate.postForEntity(url, requestEntity, Person.class);
	//方式二
	Person person = restTemplate.postForObject(url, requestEntity, Person.class);
}

2. 传递Json格式数据

@RequestMapping(value = "/person")
public Person getPerson(@RequestBody Person person){
	return person;
}

public static void main(String[] args) {
	String url = "http://localhost:8080/person";
	Person p = new Person("yhj",22);
	//方式一
	ResponseEntity<Person> responseEntity = restTemplate.postForEntity(url, p, Person.class);
	//方式二
	Person person = restTemplate.postForObject(url, p, Person.class);
}

参考:https://blog.csdn.net/likun557/article/details/121072832

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值