一、Http请求与响应格式
请求格式:
- 请求行(包含请求方法(Get、Post等)、URI、Http版本)
- 请求头
- 请求体
响应格式:
- 状态行(包含HTTP版本、状态码、描述信息)
- 响应头
- 响应体
二、HttpClient
1.基本组件
HttpClient是apache提供的用于传输HTTP消息的工具包。HttpClient中一些常用的组件:
-
HttpClientBuilder:用于创建Http客户端实例。
-
CloseableHttpClient:表示用于发送请求和接收响应的Http客户端。
-
HttpRequest接口:表示一个Http请求,对于不同的请求方法有不同的实现类:
- HttpGet:对应get请求方法
- HttpPost:对应post请求方法
-
HttpResponse接口:表示一个Http响应。
-
HttpEntity接口:表示请求/响应实体。用于发送请求体和获取响应体。(它封装了状态行、响应头、响应体)
-
HttpStatus:封装了响应的状态码
-
URIBuilder:一个辅助组件,用于动态创建URI的工具类
-
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¶m2=value2¶m3=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。默认的策略大致如下:
- 判断“Connection”请求头的值若为"Close",则返回false。
- 判断服务器响应的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);
}