HttpClient发送请求
前言:前段时间接到一个需求,要求系统中实现一个可以上传语音文件的功能,然后将文件和需要的参数发送到电信的接口上;
开始说起来感觉很简单,但是就真的被折磨了好几天,主要还是httpClient发送文件和参数到指定url,还有要从公司内网调中台再调电信接口这个过程花费了不少时间。既然涉及到了文件的上传,就想着把涉及到的东西都系统的学一遍,毕竟之前也没做过这些。
HttpClient请求数据到指定接口
对于通过HttpClien发送文件,就要先了解HttpClient的知识了;
HttpClient
理解:可以说HTTP Client类似与浏览器,它可以代替浏览器发送Http请求到指定的url;
所有的 Http 请求都有一个请求行 (request line),包括方法名、请求的 URI 和 Http 版本号 。
HttpClient 支持 HTTP/1.1 这个版本定义的所有 Http 方法:GET,HEAD,POST,PUT,DELETE,TRACE 和 OPTIONS 。对于每一种 http 方法,HttpClient 都定义了一个相应的类: HttpGet, HttpHead, HttpPost, HttpPut, HttpDelete, HttpTrace 和 HttpOpquertions 。
URI 即统一资源标志符,用来标明 Http 请求中的资源。Http request URIs 包含协议名、主 机名、主机端口(可选)、资源路径、query(可选)和片段信息(可选)。
现在如果直接贴上HttpClient的工具类的话,感觉又看不懂。所以还是先温习一下http请求与响应的过程和格式;
HTTP请求
首先在浏览器对服务器发送请求之前,必须要做的一步就是通过三次握手建立TCP连接(’重点‘后面详细学习);
一个HTTP请求报文由请求行(request line),请求头(headers),空行(blank line)和请求数据(request body)4个部分组成。
-
请求行 分为三个部分:请求方法,请求地址URL和HTTP协议版本;POST /login.htm HTTP/1.1
-
请求方法:常见GET、POST、DELETE、PUT ,这里不介绍了(后面详细学习) -
URL:统一资源定位符 它和URI的区别就是前者是定位(具体地址),后者是标识(身份证号)。 URL组成:协议+主机+端口+路径 -
协议版本:常见的HTTP/1.0 、HTTP/1.1 -
请求头 感觉类似与键值对,就是一些附加信息; host: 接受请求的服务器地址(ip地址端口号) ?user-agent: 发送请求应用程序名称(浏览器的信息) Connection: 与连接相关的属性 Accept-charset:通知服务器可以发送的编码格式 Accept-Encoding:通知服务器可以送的数据压缩格式 Accept-Language:通知服务器可以发送的语言 总结一下accept开头的感觉就是对服务器响应时的要求; 如果是GET请求方法的话,应该到这里就结束了; -
空行 如果后面还是信息,到这里就必须有一个空行,与下面的内容分开,表示请求头部已经结束了; -
请求体(数据) 至于请求数据主要是在POST的请求方法中才会看到的,GET方法的请求数据会直接加在URL的后面直接发送到服务器(例如百度搜索); 发现post方法,除了多个请求数据,请求头好像也还不太一样; post请求头好像多了两个内容长度和内容类型,这好像也就是描述下面多出来的请求数据的对吧; 整个请求也就多了一个空行和请求数据
http响应
请求报文准备好后,接下来就是将请求发送到服务器端了,然后由服务器根据请求头中的accept要求进行响应;
HTTP响应报文由状态行(status line)、响应头部(headers)、空行(blank line)和响应数据(response body)4个部分组成。
-
状态行 也不主要介绍了,反正根据状态行就能知道是哪里出的问题,常见4xx,500; -
响应头 (直接看实际的响应截图吧,很晚很困了~~)
-
响应数据 存放需要返回给客户端的数据信息。 看状态行200应该就知道这个请求-响应是成功了的,并且返回的响应数据是Json格式的; 大家平常浏览网页的话,一般响应数据的content-type应该是text/html;charset=UTF-8这样子的,然后响应数据是一串html代码,经过浏览器解析渲染呈现给用户。(遇到静态资源时,就向服务器端去请求下载) 最后就是关闭TCP连接;
这搞得,就是想记录一下文件的上传和下载,结果还没开始就整出这么多东西来。。。
对于HTTP请求和响应携带的相关参数了解了,下面就可以了解HttpClient了;
HttpClient客户端
首先说一下,对于HttpClient发送请求,网上很多很多封装好的方法,工具类;针对于不同的请求方式,不同的请求参数,封装方法实现大不一样;这里就只介绍一下普通大体的一个封装实现,有特别需求的可以根据自己的要求去做更改或者csdn上搜索;
发送请求步骤:
-
创建HttpClient对象; -
创建请求方法的实例; 常见请求方法对象HttpGet和HttpPost(url+请求方式),个人理解这里应该就相当于对请求行的创建了吧; -
添加请求参数; 调用请求方法对象的setParams(HttpParams params) 添加请求参数,HttpPsot对象也可以使用setEntity(HttpEntity entity) 方法添加请求参数; -
调用httpClient.execute(HttpXXX request) 正式发送请求,返回一个HtppResponse对象; -
调用HttpResponse的getAllHeaders() 、getHeaders(String name) 等方法可获取服务器的响应头,getEntity() 方法可获取HttpEntity对象,该对象包装了服务器的响应内容。 -
释放连接;
具体实现:
-
基于springboot项目,添加httpclient依赖; <dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
-
GET请求 不管怎么说,步骤里肯定有创建HttpClient对象,HttpGet对象,Params请求参数(有参的情况下);尤其是Params的添加,因为大家需求不同,需要传的参数以及类型不同,对于参数的封装方式就有不同; public static String sendGetForm(String url, Map<String, String> params) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()){
String param = "";
if (params != null) {
Set<Entry<String, String>> set = params.entrySet();
for(Entry<String, String> entry : set) {
if(param.isEmpty()) {
param = entry.getKey() + "=" + entry.getValue();
}else {
param = param + "&" + entry.getKey() + "=" + entry.getValue();
}
}
}
url = param.isEmpty() ? url : url + "?" + param;
HttpGet httpGet = new HttpGet(url);
try(CloseableHttpResponse response = httpClient.execute(httpGet)){
return EntityUtils.toString(response.getEntity(), "utf-8");
}
} catch (Exception e) {
log.error("发送get请求失败", e);
throw new RuntimeException("发送get请求失败", e);
}
}
简单介绍一下吧:
-
创建httpClient,至于为什么声明的CloseableHttpClient 类,这个类可以在我们请求完自动释放连接; CloseableHttpClient httpClient = HttpClients.createDefault() -
下面定义param参数配置到url后 这样请求参数可有可无,并且参数放到url后需要的格式; -
创建httpGet请求对象 HttpGet httpGet = new HttpGet(url); -
发送请求,接收响应(配置响应体) CloseableHttpResponse response = httpClient.execute(httpGet) -
POST请求
POST请求又不一样啦,因为它有请求体可以传各种类型的数据(content-type)
看了一下公司封装好的post请求,真的好多种,根据你要传的参数不同,需要配置的请求头、请求体就有所不同;所以在我们使用发送方法前要提前明白自己需要传什么样的数据,这样也就知道需要如何封装请求参数;、
对于返回的请求结果response后面的,一般就是将获取到的响应体进行一个编码,防止中文乱码等;
public static String sendPostForm(String url, Map<String, String> params) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()){
HttpPost httpPost = new HttpPost(url);
if (params != null) {
List<NameValuePair> nameValuePairs = new ArrayList<>();
for (String key : params.keySet()) {
nameValuePairs.add(new BasicNameValuePair(key, params.get(key)));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(nameValuePairs);
httpPost.setEntity(entity);
}
try(CloseableHttpResponse response = httpClient.execute(httpPost)){
return EntityUtils.toString(response.getEntity(), "utf-8");
}
} catch (Exception e) {
log.error("发送post请求失败", e);
throw new RuntimeException("发送post请求失败", e);
}
}
public static String sendPostJson(String url, String json) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()){
HttpPost httpPost = new HttpPost(url);
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
try(CloseableHttpResponse response = httpClient.execute(httpPost)){
return EntityUtils.toString(response.getEntity(), "utf-8");
}
} catch (Exception e) {
log.error("发送post请求失败", e);
throw new RuntimeException("发送post请求失败", e);
}
}
艰难介绍一下:
- 介于
NameValuePair 类型,叫做简单名称值对节点类型,就是请求数据中要求的参数类型吧; - 介于UrlEncodedFormEntity(URL实体转换工具)而且它直接在请求数据上指定了content-type的值;也可以用httpPost.addHeader(“contetntType”,“xxx”)来指定请求数据类型;
- 发送json格式数据和表单数据,对应的请求
content-type 也就不同。 ? 还有一个就是通过post请求发送文件: private String sendFile( String url,MultipartFile file,HashMap<String,String> map) throws IOException {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
String rspMsg =null;
MultipartEntityBuilder params = MultipartEntityBuilder.create();
ContentType strContent = ContentType.create(ContentType.MULTIPART_FORM_DATA.getMimeType(),Consts.UTF_8);
params.addBinaryBody("file", file.getInputStream(), ContentType.DEFAULT_BINARY, file.getOriginalFilename());
params.addTextBody("waterno",map.get("waterno"),strContent);
params.addTextBody("cutwatertone",map.get("cutwatertone"),strContent);
HttpEntity httpEntity = params.build();
httpPost.setEntity(httpEntity);
try {
StringBuilder sb = new StringBuilder();
String line;
HttpResponse httpResponse = httpclient.execute(httpPost);
InputStream inputStream = httpResponse.getEntity().getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
while ((line = br.readLine()) != null) {
sb.append(line);
}
rspMsg = URLDecoder.decode(sb.toString(),"UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return rspMsg;
}
对于这个发送文件的封装方法,网上真的很多,但有些就是封装了file的参数,没有其他普通参数的封装,还有要主要的就是发送文件必须要把文件转换成流才行。当时做发送文件和参数到电信接口时,就在网上找了好多好多方法,都不行…;其实这也是再网上找的,不同的是在创建请求体时,使用的是一个创建者设计模式,有点绕。但是大致步骤还是一样的。
对方接收请求参数
通过HttpClient发送了请求,但服务端如何接收请求参数呢,其实很简单的。大家只要知道客户端发送的是什么请求,参数是什么类型就可以了;
对于get请求,对应的controller方法接收参数使用@RequestParam,
对于post请求,对应的controller方法接收参数使用@RequestBody;
总结:
其实理解了HTTP请求,对于HttpClient发送请求,最难搞的就是对于请求体的封装,尤其POST请求中content-type,它决定请求数据是一个什么类型,例如表单类型,json类型,文件类型等等;然后你在httpPost.addEntity(xxxx)这个方法的参数中就要传递不同的请求体参数类型,网上实现又很多,不一定每个都适合自己;所以还是主要理解HttpClient请求创建的大致步骤吧;而且现在springboot中有个现成的RestTemplate类,也是来在客户端发送请求的,只需要直接调用方法就可以,但是不知道为什么当时直接用它发送文件就是不行。RestTemplate有时间可以去了解一下:RestTemplate用法
最后,本文的HTTP请求和HttpClient由其他文章理解写成,相关链接:
https://blog.csdn.net/w372426096/article/details/82713315
https://blog.csdn.net/ailunlee/article/details/90600174
下一篇文件的下载和上传(本来上次说的是这篇是文件的上传和下载的,结果变成了HttpClient发送请求)
|