1 概述
Http
请求在服务端开发中必不可少,本文使用RestTemplate
做门面,HttpClient
做实现,演示基础的Http
请求例子。
2 源码分析
2.1 添加pom.xml
依赖
RestTemplate
在Spring-Web
模块中内置,SpringBoot
自动引入
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
<!-- 如果不配异步(AsyncRestTemplate),则不需要这个依赖 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.5.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
2.2 配置文件application.yml
(可选)
# yml配置的优先级高于java配置;如果yml配置和java配置同时存在,则yml配置会覆盖java配置
####restTemplate的yml配置开始####
---
spring:
restTemplate:
maxTotalConnect: 1000 #连接池的最大连接数,0代表不限;如果取0,需要考虑连接泄露导致系统崩溃的后果
maxConnectPerRoute: 200
connectTimeout: 3000
readTimeout: 5000
charset: UTF-8
####restTemplate的 yml配置开始####
2.3 编写RestTemplate
配置(必备)
// 必备
@Configuration
@ConfigurationProperties(prefix = "spring.restTemplate")
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
public class RestTemplateConfig {
// java配置的优先级低于yml配置;如果yml配置不存在,会采用java配置
// ####restTemplate的 java配置开始####
private int maxTotalConnection = 500; //连接池的最大连接数
private int maxConnectionPerRoute = 100; //同路由的并发数
private int connectionTimeout = 2 * 1000; //连接超时,默认2s
private int readTimeout = 30 * 1000; //读取超时,默认30s
private String charset = "UTF-8";
public void setMaxTotalConnection(int maxTotalConnection) {
this.maxTotalConnection = maxTotalConnection;
}
public void setMaxConnectionPerRoute(int maxConnectionPerRoute) {
this.maxConnectionPerRoute = maxConnectionPerRoute;
}
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public void setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
}
public void setCharset(String charset) {
this.charset = charset;
}
//创建HTTP客户端工厂
@Bean(name = "clientHttpRequestFactory")
public ClientHttpRequestFactory clientHttpRequestFactory() {
return createClientHttpRequestFactory(this.connectionTimeout, this.readTimeout);
}
//初始化RestTemplate,并加入spring的Bean工厂,由spring统一管理
@Bean(name = "restTemplate")
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return createRestTemplate(factory);
}
//初始化支持异步的RestTemplate,并加入spring的Bean工厂,由spring统一管理
//如果你用不到异步,则无须创建该对象
@Bean(name = "asyncRestTemplate")
@ConditionalOnMissingBean(AsyncRestTemplate.class)
public AsyncRestTemplate asyncRestTemplate(RestTemplate restTemplate) {
final Netty4ClientHttpRequestFactory factory = new Netty4ClientHttpRequestFactory();
factory.setConnectTimeout(this.connectionTimeout);
factory.setReadTimeout(this.readTimeout);
return new AsyncRestTemplate(factory, restTemplate);
}
private ClientHttpRequestFactory createClientHttpRequestFactory(int connectionTimeout, int readTimeout) {
//maxTotalConnection 和 maxConnectionPerRoute 必须要配
if (this.maxTotalConnection <= 0) {
throw new IllegalArgumentException("invalid maxTotalConnection: " + maxTotalConnection);
}
if (this.maxConnectionPerRoute <= 0) {
throw new IllegalArgumentException("invalid maxConnectionPerRoute: " + maxTotalConnection);
}
//全局默认的header头配置
List<Header> headers = new LinkedList<>();
headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en;q=0.6"));
//禁用自动重试,需要重试时,请自行控制
HttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler(0, false);
//创建真正处理http请求的httpClient实例
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultHeaders(headers)
.setRetryHandler(retryHandler)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
httpClient);
factory.setConnectTimeout(connectionTimeout);
factory.setReadTimeout(readTimeout);
return factory;
}
private RestTemplate createRestTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
//我们采用RestTemplate内部的MessageConverter
//重新设置StringHttpMessageConverter字符集,解决中文乱码问题
modifyDefaultCharset(restTemplate);
//设置错误处理器
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
return restTemplate;
}
private void modifyDefaultCharset(RestTemplate restTemplate) {
List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
HttpMessageConverter<?> converterTarget = null;
for (HttpMessageConverter<?> item : converterList) {
if (StringHttpMessageConverter.class == item.getClass()) {
converterTarget = item;
break;
}
}
if (null != converterTarget) {
converterList.remove(converterTarget);
}
Charset defaultCharset = Charset.forName(charset);
converterList.add(1, new StringHttpMessageConverter(defaultCharset));
}
}
- 做完上述配置,就生成了可用的
RestTemplate
实例
2.4 Get
请求演示
@Slf4j
@RestController
public class GetTestController {
@Resource
private RestTemplate restTemplate;
//最简单的get操作
@GetMapping("/baidu1/{key}")
public String get1(@PathVariable String key) throws UnsupportedEncodingException {
String encodeKey = URLEncoder.encode(key, "UTF-8");
String url = "http://www.baidu.com/s?bdorz_come=1&ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=" + encodeKey;
return restTemplate.getForObject(url, String.class); //返回百度主站html
}
//需要自定义header头的get操作
@GetMapping("/baidu2/{key}")
public String get2(@PathVariable String key) throws UnsupportedEncodingException {
HttpHeaders headers = new HttpHeaders();
headers.set("MyHeaderKey", "MyHeaderValue");
HttpEntity entity = new HttpEntity(headers);
String encodeKey =URLEncoder.encode(key, "UTF-8");
String url = "http://www.baidu.com/s?bdorz_come=1&ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=" + encodeKey;
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
return response.getBody(); //返回百度主站html
}
}
2.5 Post
请求演示
@Slf4j
@RestController
public class PostTestController {
@Resource
private RestTemplate restTemplate;
//post表单演示
@GetMapping("/postForm")
public String testPostForm() {
// 填写url
String url = "";
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
// 填写表单
form.add("name", "**");
form.add("age", "**");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
//headers.add("xx", "yy");//可以加入自定义的header头
HttpEntity<MultiValueMap<String, String>> formEntity = new HttpEntity<>(form, headers);
String json = restTemplate.postForObject(url, formEntity, String.class);
return json;
}
@RequestMapping("/postBody")
public String testPostBody() {
// 填写url
String url = "";
// 填写json串
String jsonBody = "{\n"
+ " \"name\": \"XX\",\n"
+ " \"age\": \"12\",\n"
+ " \"sex\": \"man\"\n"
+ "}\n";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
//headers.add("xx", "yy");//可以加入自定义的header头
HttpEntity<String> bodyEntity = new HttpEntity<>(jsonBody, headers);
//1.直接拿原始json串
String json = restTemplate.postForObject(url, bodyEntity, String.class);
//2.将原始的json传转成java对象,rest template可以自动完成
ResultVo resultVo = restTemplate.postForObject(url, bodyEntity, ResultVo.class);
if (resultVo != null && resultVo.success()) {
Object res = resultVo.getData();
log.info("处理成功,返回数据: {}", resultVo.getData());
} else {
log.info("处理失败,响应结果: {}", resultVo);
}
return json;//返回的是分包api的json
}
}
2.6 文件上传与下载请求演示
@Slf4j
@RestController
public class FileTestController {
@Resource
private RestTemplate restTemplate;
// post文件上传
// 场景说明:只适合小文件(20MB以内)上传
@RequestMapping("/postFile")
public String testPostFileBody() {
String filePath = "D:/config.png";
//通过磁盘文件上传,如果产生了临时文件,一定要记得删除,否则,临时文件越积越多,磁盘会爆
FileSystemResource resource = new FileSystemResource(new File(filePath));
String url = "***";//测试的时候换成自己的配置
String appId = "***";//测试的时候换成自己的配置
String secureKey = "***";//测试的时候换成自己的配置
String time = String.valueOf(System.currentTimeMillis());
String pubStr = "1";
String tempStr = String.format("app_id=%s&is_public=%s&time=%s&vframe=0%s", appId, pubStr, time, secureKey);
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("is_public", pubStr);
form.add("vframe", "0");
form.add("file", resource);
form.add("app_id", appId);
form.add("time", time);
form.add("sign", DigestUtils.md5(tempStr));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
//headers.add("xx", "yy");//可以加入自定义的header头
HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(form, headers);
String json = restTemplate.postForObject(url, formEntity, String.class);
return json;
}
//文件下载
//场景说明:只适合小文件(10MB以内)下载
@RequestMapping("/downloadFile")
public ResponseEntity testDownloadFile() throws Exception {
String url = "http://editor.baidu.com/editor/download/BaiduEditor(Online)_5-9-16.exe";
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM));
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.GET, entity, byte[].class);
byte[] bytes = response.getBody();
long contentLength = bytes != null ? bytes.length : 0;
headers.setContentLength((int) contentLength);
headers.setContentDispositionFormData("baidu.exe", URLEncoder.encode("百度安装包.exe", "UTF-8"));
return new ResponseEntity<>(response.getBody(), headers, HttpStatus.OK);
}
}
3 采坑记录
3.1 只配@ConfigurationProperties
时,不会自动创建bean
正确姿势:
@Configuration
@ConfigurationProperties(prefix = "spring.restTemplate")
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
public class RestTemplateConfig {
}
错误姿势:
@ConfigurationProperties(prefix = "spring.restTemplate")
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
public class RestTemplateConfig {
}
3.2 @ConfigurationProperties
无法注入没有setter
的属性
3.3 RestTemplate
默认配置会乱码
正确姿势:
private RestTemplate createRestTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
//我们采用RestTemplate内部的MessageConverter
//重新设置StringHttpMessageConverter字符集,解决中文乱码问题
modifyDefaultCharset(restTemplate);
//设置错误处理器
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
return restTemplate;
}
private void modifyDefaultCharset(RestTemplate restTemplate) {
List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
HttpMessageConverter<?> converterTarget = null;
for (HttpMessageConverter<?> item : converterList) {
if (StringHttpMessageConverter.class == item.getClass()) {
converterTarget = item;
break;
}
}
if (null != converterTarget) {
converterList.remove(converterTarget);
}
Charset defaultCharset = Charset.forName(charset);
converterList.add(1, new StringHttpMessageConverter(defaultCharset));
}
错误姿势:
@Bean
public RestTemplate getRestTemplate(){
RestTemplate rest = new RestTemplate(this.createFactory);
return rest;
}
4 如何调试RestTemplate
- 可以在
logback
里单独配一个debug
级别的logger
,把org.apache.http
下面的日志定向到控制台:
<logger name="org.apache.http" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
5 工程目录
6 结束语
说点什么呢,有任何建议,欢迎留言探讨,本文源码。
欢迎关注博主公众号,第一时间推送最新文章
版权声明:本文为liu19900205原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。