后端http请求
需求
最近一直在和其他系统进行对接,发现对接方直接给了一个http请求接口过来,但是咱们自己的业务和对方的http接口耦合太深的话,会有很多问题出现,比如请求超时或者连接超时导致业务接口报错,自己总结了一些封装方法
基本使用
jdk自己封装了http协议的一些基本功能,但是一般都会使用httpclient或者restTemplate
httpclient是Apache Jakarta Common 下的子项目
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.util.*;
public class HttpClientUtils {
private static PoolingHttpClientConnectionManager connectionManager = null;
private static HttpClientBuilder httpBuilder = null;
private static RequestConfig requestConfig = null;
private static int MAXCONNECTION = 10;
private static int DEFAULTMAXCONNECTION = 5;
private static String IP = "cnivi.com.cn";
private static int PORT = 80;
static {
//设置http的状态参数
requestConfig = RequestConfig.custom()
.setSocketTimeout(5000)
.setConnectTimeout(5000)
.setConnectionRequestTimeout(5000)
.build();
HttpHost target = new HttpHost(IP, PORT);
connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(MAXCONNECTION);//客户端总并行链接最大数
connectionManager.setDefaultMaxPerRoute(DEFAULTMAXCONNECTION);//每个主机的最大并行链接数
connectionManager.setMaxPerRoute(new HttpRoute(target), 20);
httpBuilder = HttpClients.custom();
httpBuilder.setConnectionManager(connectionManager);
}
public static CloseableHttpClient getConnection() {
CloseableHttpClient httpClient = httpBuilder.build();
return httpClient;
}
public static HttpUriRequest getRequestMethod(Map<String, String> map, String url, String method) {
List<NameValuePair> params = new ArrayList<NameValuePair>();
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String, String> e : entrySet) {
String name = e.getKey();
String value = e.getValue();
NameValuePair pair = new BasicNameValuePair(name, value);
params.add(pair);
}
HttpUriRequest reqMethod = null;
if ("post".equals(method)) {
reqMethod = RequestBuilder.post().setUri(url)
.addParameters(params.toArray(new BasicNameValuePair[params.size()]))
.setConfig(requestConfig).build();
} else if ("get".equals(method)) {
reqMethod = RequestBuilder.get().setUri(url)
.addParameters(params.toArray(new BasicNameValuePair[params.size()]))
.setConfig(requestConfig).build();
}
return reqMethod;
}
public static void main(String args[]) throws IOException {
Map<String, String> map = new HashMap<String, String>();
map.put("account", "");
map.put("password", "");
HttpClient client = getConnection();
HttpUriRequest post = getRequestMethod(map, "http://cnivi.com.cn/login", "post");
HttpResponse response = client.execute(post);
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
String message = EntityUtils.toString(entity, "utf-8");
System.out.println(message);
} else {
System.out.println("请求失败");
}
}
}
restTemplate后面着重使用restTemplate实现功能
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
配置
import org.apache.http.client.HttpClient;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
/**
* http连接管理器
* @return
*/
@Bean
public HttpClientConnectionManager poolingHttpClientConnectionManager() {
/*// 注册http和https请求
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);*/
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
// 最大连接数
poolingHttpClientConnectionManager.setMaxTotal(500);
// 同路由并发数(每个主机的并发)
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100);
return poolingHttpClientConnectionManager;
}
/**
* HttpClient
* @param poolingHttpClientConnectionManager
* @return
*/
@Bean
public HttpClient httpClient(HttpClientConnectionManager poolingHttpClientConnectionManager) {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
// 设置http连接管理器
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
/*// 设置重试次数
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true));*/
// 设置默认请求头
/*List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("Connection", "Keep-Alive"));
httpClientBuilder.setDefaultHeaders(headers);*/
return httpClientBuilder.build();
}
/**
* 请求连接池配置
* @param httpClient
* @return
*/
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
// httpClient创建器
clientHttpRequestFactory.setHttpClient(httpClient);
// 连接超时时间/毫秒(连接上服务器(握手成功)的时间,超出抛出connect timeout)
clientHttpRequestFactory.setConnectTimeout(5 * 1000);
// 数据读取超时时间(socketTimeout)/毫秒(务器返回数据(response)的时间,超过抛出read timeout)
clientHttpRequestFactory.setReadTimeout(10 * 1000);
// 连接池获取请求连接的超时时间,不宜过长,必须设置/毫秒(超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool)
clientHttpRequestFactory.setConnectionRequestTimeout(10 * 1000);
return clientHttpRequestFactory;
}
/**
* rest模板
* @return
*/
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
// boot中可使用RestTemplateBuilder.build创建
RestTemplate restTemplate = new RestTemplate();
// 配置请求工厂
restTemplate.setRequestFactory(clientHttpRequestFactory);
return restTemplate;
}
}
这里的restTemplate配置就配置好了,重要的参数就是从连接池获取连接的时间/连接超时时间/数据读取时间,最大连接数和默认请求头都可以使用时再添加
用一个获取token接口举例
public static TokenResponse token(TokenRequest tokenRequest) {
TokenResponse data = new TokenResponse();
RestTemplate restTemplate = getRestTemplate();
//请求头
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Basic c2FiZXI6c2FiZXJfc2VjcmV0");
headers.set("Tenant-Id", "000000");
headers.set("Content-Type", "application/x-www-form-urlencoded");
//map参数
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("username", tokenRequest.getUsername());
params.add("password", DigestUtils.md5Hex(tokenRequest.getPassword()));
params.add("grant_type", tokenRequest.getGrant_type());
params.add("scope", tokenRequest.getScope());
params.add("tenantId", tokenRequest.getTenantId());
HttpEntity entity = new HttpEntity(params, headers);
data = restTemplate.postForObject(host + token_port + token_url, entity, TokenResponse.class);
return data;
}
带附件的post请求
简单的使用已经是可以满足了,但是客户提了和对接系统传文件的需求,发现过程中出现了很多有意思的错误
对方的接口接收附件是这样的
我们知道后端使用http请求带附件的参数时,要模拟表单发送post请求,这里不清楚的可以看下多文件传参这篇--劝退记录:多文件feign接口传参 - 掘金 (juejin.cn)
一开始直接给参数体对象中传MultipartFile[]发现接收参数为null,然后就去查调用方法,最后实现方法为:
/**
* 发邮件
*
* @param request
* @return
*/
public static R<LinkedHashMap<String, Object>> receiveEmail(ReceiveEmailRequest request) {
R<LinkedHashMap<String, Object>> result = new R<LinkedHashMap<String, Object>>();
RestTemplate restTemplate = getRestTemplate();
//请求头
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Basic c2FiZXI6c2FiZXJfc2VjcmV0");
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
//处理附件
if(request.getFileSystemResources() != null && request.getFileSystemResources().size() > 0){
for (int i = 0; i < request.getFileSystemResources().size(); i++) {
params.add("filesArr",request.getFileSystemResources().get(i));
}
params.add("fileName",request.getFileName());
}else {
params.add("fileName","[]");
}
//map参数
params.add("sendId",request.getSendId());
params.add("sendName",request.getSendName());
params.add("sendAccount",request.getSendAccount());
params.add("srcName",request.getSrcName());
params.add("receiverName",request.getReceiverName());
params.add("isSeparate",request.getIsSeparate());
params.add("title",request.getTitle());
params.add("body",request.getBody());
params.add("ifTimed",request.getIfTimed());
params.add("ownType",request.getOwnType());
params.add("isUrgent",request.getIsUrgent());
params.add("needReceipt",request.getNeedReceipt());
params.add("isEncrypted",request.getIsEncrypted());
// params.add("filesArr",request.getFiles());
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(params, headers);
ResponseEntity<R> r = restTemplate.postForEntity(host + receive_port + receive_url, entity, R.class);
if (r.getStatusCode().is2xxSuccessful()) {
result = r.getBody();
}
return result;
}
注意事项: 1.MultiValueMap<String, Object> 对象同key put值时会加入到现有的对象成为一个集合,所以多文件直接传一个key值就可以 2.请求头里的值一定要改为表单请求 headers.setContentType(MediaType.MULTIPART_FORM_DATA); 3.传文件的对象是FileSystemResources,需要经过本地临时文件处理,方法在下面
public static File getFileByte(String fileURL) {
File file = null;
File tempFile = null;
byte[] fileBytes = null;
try {
// 创建URL对象
URL url = new URL(fileURL);
// 打开连接
URLConnection connection = url.openConnection();
// 获取输入流
InputStream inputStream = connection.getInputStream();
// 使用缓冲流提高读取性能
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
// 将文件流转换为二进制数据
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead);
}
fileBytes = byteArrayOutputStream.toByteArray();
// 关闭流
byteArrayOutputStream.close();
bufferedInputStream.close();
// 假设originalFile是已知的File对象,且其具有正确的文件扩展名
String filePath = url.getPath();
int lastSlashIndex = filePath.lastIndexOf('/');
String originalFileName = filePath.substring(lastSlashIndex + 1);
int extensionIndex = originalFileName.lastIndexOf(".");
String originalExtension = (extensionIndex != -1) ? originalFileName.substring(extensionIndex + 1) : "";
if (!originalExtension.isEmpty()) {
tempFile = File.createTempFile("temp", "." + originalExtension);
} else {
// 如果原始文件没有扩展名,则仍然使用".docx"
tempFile = File.createTempFile("temp", ".docx");
}
FileOutputStream outputStream = new FileOutputStream(tempFile);
outputStream.write(fileBytes);
outputStream.close();
file = tempFile;
} catch (IOException e) {
e.printStackTrace();
}finally {
//删除本地临时文件
tempFile.deleteOnExit();
}
return file;
}