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后面的,一般就是将获取到的响应体进行一个编码,防止中文乱码等;
//Post方式表单提交 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); } } //发送json格式数据 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(); //定义post请求的请求数据类型 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发送请求)