RestTemplate:
是 Spring 提供的用于访问Rest服务的客户端, RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
–注意RestTemplate只有初始化配置(也可以不配,有默认的),没有什么连接池
okhttp3:官方文档
OkHttp是一个高效的HTTP客户端:
HTTP / 2支持允许对同一主机的所有请求共享一个套接字。
连接池可减少请求延迟(如果HTTP / 2不可用)。
透明的GZIP缩小了下载大小。
响应缓存可以完全避免网络重复请求。
当网络出现问题时,OkHttp会坚持不懈:它将从常见的连接问题中静默恢复。如果您的服务具有多个IP地址,则在第一次连接失败时,OkHttp将尝试使用备用地址。这对于IPv4 + IPv6和冗余数据中心中托管的服务是必需的。OkHttp支持现代TLS功能(TLS 1.3,ALPN,证书固定)。可以将其配置为回退以获得广泛的连接性。
使用OkHttp很容易。它的请求/响应API具有流畅的构建器和不变性。它支持同步阻塞调用和带有回调的异步调用。添加依赖
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.9.0</version> </dependency>
application.properties设置相关配置参数
http: okhttp3: connect-timeout: 5 read-timeout: 10 write-timeout: 10 # 连接池中整体的空闲连接的最大数量 max-idle-connections: 200 # 连接空闲时间最多为 300 秒 keep-alive-duration: 300
RestTemplateConfig配置类
package cn.friends.config; import cn.friends.config.properties.OkHttpProperties; import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.client.RestTemplate; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.TimeUnit; @Configuration @EnableConfigurationProperties(OkHttpProperties.class) public class RestTemplateConfig { private OkHttpProperties okHttpProperties; /** * 声明 RestTemplate */ @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(httpRequestFactory()); // 中文乱码,主要是 StringHttpMessageConverter的默认编码为ISO导致的 List<HttpMessageConverter<?>> list = restTemplate.getMessageConverters(); for (HttpMessageConverter<?> converter : list) { if (converter instanceof StringHttpMessageConverter) { ((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8); break; } } // 可以增加拦截器 MyRequestInterceptor myRequestInterceptor = new MyRequestInterceptor(); restTemplate.getInterceptors().add(myRequestInterceptor); return restTemplate; } public ClientHttpRequestFactory httpRequestFactory() { return new OkHttp3ClientHttpRequestFactory(okHttpConfigClient()); } public OkHttpClient okHttpConfigClient() { return new OkHttpClient().newBuilder() .connectionPool(pool()) .connectTimeout(okHttpProperties.getConnectTimeout(), TimeUnit.SECONDS) .readTimeout(okHttpProperties.getReadTimeout(), TimeUnit.SECONDS) .writeTimeout(okHttpProperties.getWriteTimeout(), TimeUnit.SECONDS) .hostnameVerifier((hostname, session) -> true) // 设置代理 // .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8888))) // 拦截器 // .addInterceptor() .build(); } public ConnectionPool pool() { return new ConnectionPool(okHttpProperties.getMaxIdleConnections(), okHttpProperties.getKeepAliveDuration(), TimeUnit.SECONDS); } }
自定义拦截器
/** * 自定义的拦截器 * * 主要打印日志 * */ class MyRequestInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { //记录请求开始时间 StopWatch stopWatch = new StopWatch(); stopWatch.start(); //执行请求 ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, body); //执行完毕后,这里要创建备份,要不然会报io提前关闭的异常错误 ClientHttpResponse responseCopy = new BufferingClientHttpResponseWrapper(response); //记录请求结束时间 stopWatch.stop(); //获取响应内容 StringBuilder resBody = new StringBuilder(); try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(responseCopy.getBody(), Charset.forName("UTF-8")))) { String line = bufferedReader.readLine(); while (line != null) { resBody.append(line); line = bufferedReader.readLine(); } } //获取请求内容 String reqBody = ""; //这里可以自定义想打印什么方法或者请求的内容,如下只打印post请求的日志,因为这时候才会带上body if (httpRequest.getHeaders().getContentType() != null && "POST".equals(httpRequest.getMethod().name())) { String requestBody = new String(body, Charset.forName("UTF-8")); // String contentType = httpRequest.getHeaders().getContentType().toString(); //这里可以对contentType做一些判断,针对其格式化请求体的内容, 如"application/x-www-form-urlencoded"格式会附带一些boundary(分割线)的内容 reqBody = requestBody; } //打印日志的格式可以自定义 log.info(JSON.toJSONString(RestLog.builder().costTime(stopWatch.getLastTaskTimeMillis()) .headers(httpRequest.getHeaders()).method(httpRequest.getMethodValue()) .reqUrl(httpRequest.getURI().toString()).reqBody(reqBody).resBody(resBody.toString()) .resStatus(responseCopy.getRawStatusCode()).build())); return responseCopy; } } /** * 响应内容备份 */ final class BufferingClientHttpResponseWrapper implements ClientHttpResponse { private final ClientHttpResponse response; private byte[] body; BufferingClientHttpResponseWrapper(ClientHttpResponse response) { this.response = response; } @Override public HttpStatus getStatusCode() throws IOException { return this.response.getStatusCode(); } @Override public int getRawStatusCode() throws IOException { return this.response.getRawStatusCode(); } @Override public String getStatusText() throws IOException { return this.response.getStatusText(); } @Override public HttpHeaders getHeaders() { return this.response.getHeaders(); } @Override public InputStream getBody() throws IOException { if (this.body == null) { this.body = StreamUtils.copyToByteArray(this.response.getBody()); } return new ByteArrayInputStream(this.body); } @Override public void close() { this.response.close(); } } /** * 定义日志的字段 */ @Data @Builder private static class RestLog { private String reqUrl; private String method; private HttpHeaders headers; private String reqBody; private String resBody; private long costTime; private int resStatus; }
使用效果
@SpringBootTest class SpringbootRestTemplateApplicationTests { @Resource RestTemplate restTemplate; @Test void contextLoads() { String requestUrl = "https://api.uomg.com/api/icp"; //构建json请求参数 JSONObject params = new JSONObject(); params.put("domain", "www.baidu.com"); //请求头声明请求格式为json HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); //创建请求实体 HttpEntity<String> entity = new HttpEntity<>(params.toString(), headers); //执行post请求,并响应返回字符串 String resText = restTemplate.postForObject(requestUrl, entity, String.class); System.out.println(resText); } }
o.l.s.f.config.RestTemplateConfig : {"costTime":1517,"headers":{"Accept":["text/plain, */*"],"Content-Type":["application/json"],"Content-Length":["26"]}, "method":"POST","reqBody":"{\"domain\":\" "resBody":"{\"code\":200901,\"msg\":\"查询域名不能为空\"}","resStatus":200} {"code":200901,"msg":"查询域名不能为空"}