目录
一、GET无参,HTTP发送示例
二、GET有参(方式一:拼接URL参数)
三、GET有参(方式二:使用URI方式)
四、POST有参(对象+普通参数)
五、application/x-www-form-urlencoded表单请求(示例)
知识补充:application/x-www-form-urlencoded和multipart/form-data
(1)application/x-www-urlencoded
(3)multipart/form-data
六、发送文件示例
七、发送流示例
HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
HTTP和浏览器有点像,但却不是浏览器。很多人觉得既然HttpClient是一个HTTP客户端编程工具,很多人把他当做浏览器来理解,但是其实HttpClient不是浏览器,它是一个HTTP通信库,因此它只提供一个通用浏览器应用程序所期望的功能子集,最根本的区别是HttpClient中没有用户界面,浏览器需要一个渲染引擎来显示页面,并解释用户输入,例如鼠标点击显示页面上的某处,有一个布局引擎,计算如何显示HTML页面,包括级联样式表和图像。javascript解释器运行嵌入HTML页面或从HTML页面引用的javascript代码。来自用户界面的事件被传递到javascript解释器进行处理。除此之外,还有用于插件的接口,可以处理Applet,嵌入式媒体对象(如pdf文件,Quicktime电影和Flash动画)或ActiveX控件(可以执行任何操作)。HttpClient只能以编程的方式通过其API用于传输和接受HTTP消息。
在使用HttpClient前,首先需要导入其maven依赖,另外还引入了fastjson的依赖,在示例中会用到
org.apache.httpcomponents
httpclient
4.5.6
com.alibaba
fastjson
1.2.58
本文示例使用springboot的运行环境,springboot运行环境的搭建请参照我的这篇文章:快速构建一个springboot项目。
在运行测试案例前需要先启动springboot项目,然后再启动测试类进行测试,如果不知道怎样搭建springboot的单元测试,可以参照我的这篇文章:编写springboot单元测试类
以下所有示例亲测有效。
代码示例:
/**
* GET---无参测试
*/
@Test
public void doGetNoParam() {
// 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
// 创建Get请求
HttpGet httpGet = new HttpGet("http://localhost:8080/http/doGetNoParam");
// 响应模型
CloseableHttpResponse response = null;
try {
// 由客户端执行(发送)Get请求
response = httpClient.execute(httpGet);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
System.out.println("响应状态为:" + response.getStatusLine());
if (responseEntity != null) {
System.out.println("响应内容长度为:" + responseEntity.getContentLength());
System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
对应的接收示例:
@RequestMapping("doGetNoParam")
public String doGetNoParam(){
return "doGetNoParam...";
}
执行结果:
代码示例:
/**
* GET---有参测试 (方式一:手动在url后面加上参数)
*/
@Test
public void doGetAppendUrl() {
// 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
// 参数
StringBuffer params = new StringBuffer();
try {
// 字符数据最好encoding以下;这样一来,某些特殊字符才能传过去(如:某人的名字就是“&”,不encoding的话,传不过去)
params.append("name=" + URLEncoder.encode("&&", "utf-8"));
params.append("&");
params.append("age=24");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
// 创建Get请求
HttpGet httpGet = new HttpGet("http://localhost:8080/http/doGetAppendUrl" + "?" + params);
// 响应模型
CloseableHttpResponse response = null;
try {
// 配置信息
RequestConfig requestConfig = RequestConfig.custom()
// 设置连接超时时间(单位毫秒)
.setConnectTimeout(5000)
// 设置请求超时时间(单位毫秒)
.setConnectionRequestTimeout(5000)
// socket读写超时时间(单位毫秒)
.setSocketTimeout(5000)
// 设置是否允许重定向(默认为true)
.setRedirectsEnabled(true).build();
// 将上面的配置信息 运用到这个Get请求里
httpGet.setConfig(requestConfig);
// 由客户端执行(发送)Get请求
response = httpClient.execute(httpGet);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
System.out.println("响应状态为:" + response.getStatusLine());
if (responseEntity != null) {
System.out.println("响应内容长度为:" + responseEntity.getContentLength());
System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
对应接收示例:
@GetMapping("/doGetAppendUrl")
public String doGetAppendUrl(String name,Integer age){
return name + ",ta的年龄为" + age + "岁了";
}
执行结果:
代码示例:
/**
* GET---有参测试 (方式二:将参数放入键值对类中,再放入URI中,从而通过URI得到HttpGet实例)
*/
@Test
public void doGetByURI() {
// 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
// 参数
URI uri = null;
try {
// 将参数放入键值对类NameValuePair中,再放入集合中
List params = new ArrayList<>();
params.add(new BasicNameValuePair("name", "&&"));
params.add(new BasicNameValuePair("age", "18"));
// 设置uri信息,并将参数集合放入uri;
// 注:这里也支持一个键值对一个键值对地往里面放setParameter(String key, String value)
uri = new URIBuilder().setScheme("http").setHost("localhost")
.setPort(8080).setPath("/http/doGetAppendUrl")
.setParameters(params).build();
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
// 创建Get请求
HttpGet httpGet = new HttpGet(uri);
// 响应模型
CloseableHttpResponse response = null;
try {
// 配置信息
RequestConfig requestConfig = RequestConfig.custom()
// 设置连接超时时间(单位毫秒)
.setConnectTimeout(5000)
// 设置请求超时时间(单位毫秒)
.setConnectionRequestTimeout(5000)
// socket读写超时时间(单位毫秒)
.setSocketTimeout(5000)
// 设置是否允许重定向(默认为true)
.setRedirectsEnabled(true).build();
// 将上面的配置信息 运用到这个Get请求里
httpGet.setConfig(requestConfig);
// 由客户端执行(发送)Get请求
response = httpClient.execute(httpGet);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
System.out.println("响应状态为:" + response.getStatusLine());
if (responseEntity != null) {
System.out.println("响应内容长度为:" + responseEntity.getContentLength());
System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
对应的接收示例跟上边方法一是一样的,在此就不过多描述了。
POST无参基本上跟GET传参差不多,就是new HttpPost()而已。Post传基本参数,可以通过拼接Url的方式,也可以使用URI的方式,此示例把对象传参和基本传参结合在一起,以便囊括常用的传参方式。
使用URI传递普通参数,使用Entity传递对象参数,其中用到的User对象是自己创建的简单对象类型,里边有一些Get和Set方法,在此就不一一列举了。
代码示例:
/**
* POST---有参测试(普通参数 + 对象参数)
*/
@Test
public void doPostUseParam() {
// 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
// 创建Post请求
// 参数
URI uri = null;
try {
// 将参数放入键值对类NameValuePair中,再放入集合中
List params = new ArrayList<>();
params.add(new BasicNameValuePair("flag", "8"));
params.add(new BasicNameValuePair("meaning", "哈哈哈"));
// 设置uri信息,并将参数集合放入uri;
// 注:这里也支持一个键值对一个键值对地往里面放setParameter(String key, String value)
uri = new URIBuilder().setScheme("http").setHost("localhost").setPort(8080)
.setPath("/http/doPostUseParam").setParameters(params).build();
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
HttpPost httpPost = new HttpPost(uri);
// 创建user参数
User user = new User();
user.setUserName("Merry");
user.setUserSex("女");
// 将user对象转换为json字符串,并放入entity中
StringEntity entity = new StringEntity(JSON.toJSONString(user), "UTF-8");
// post请求是将参数放在请求体里面传过去的;这里将entity放入post请求体中
httpPost.setEntity(entity);
httpPost.setHeader("Content-Type", "application/json;charset=utf8");
// 响应模型
CloseableHttpResponse response = null;
try {
// 由客户端执行(发送)Post请求
response = httpClient.execute(httpPost);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
System.out.println("响应状态为:" + response.getStatusLine());
if (responseEntity != null) {
System.out.println("响应内容长度为:" + responseEntity.getContentLength());
System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
防止响应乱码,可以主动设置编码
String response = EntityUtils.toString(responseEntity, UTF_8);
对应接收示例:
@PostMapping("/doPostUseParam")
public String doPostUseParam(@RequestBody User user, String meaning, Integer flag) {
return user.getUserName() + ",ta是" + user.getUserSex() + "生。这意味着" + meaning + flag;
}
执行结果:
代码示例:
/**
* POST---application/x-www-form-urlencoded 表单请求
*/
@Test
public void doPostUseForm() {
// 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpPost httpPost = new HttpPost("http://localhost:8080/http/doPostUseForm");
httpPost.setHeader("Content-Type","application/x-www-form-urlencoded");
// 将参数放入键值对类NameValuePair中,再放入集合中
List params = new ArrayList<>();
params.add(new BasicNameValuePair("bookName", "语文"));
params.add(new BasicNameValuePair("message", "这是一本好书"));
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(params, UTF_8);
httpPost.setEntity(formEntity);
CloseableHttpResponse response = null;
try {
// 由客户端执行(发送)Post请求
response = httpClient.execute(httpPost);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
System.out.println("响应状态为:" + response.getStatusLine());
if (responseEntity != null) {
System.out.println("响应内容长度为:" + responseEntity.getContentLength());
System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
对应接收示例:
@PostMapping("/doPostUseForm")
public String doPostUseForm(String bookName, String message) {
return bookName + ":" + message;
}
一个 HTML 表单中的 enctype 有三种类型
默认情况下是 application/x-www-urlencoded,当表单使用 POST 请求时,数据会被以 x-www-urlencoded 方式编码到 Body 中来传送,而如果 GET 请求,则是附在 url 链接后面来发送。GET 请求只支持 ASCII 字符集,因此,如果我们要发送更大字符集的内容,我们应使用 POST 请求。
如果要发送大量的二进制数据(non-ASCII),application/x-www-form-urlencoded 显然是低效的,因为它需要用 3 个字符来表示一个 non-ASCII 的字符。因此,这种情况下,应该使用 “multipart/form-data” 格式。
application / x-www-form-urlencoded对于发送大量二进制数据或包含非ASCII字符的文本效率低下。
multipart / form-data应该用于提交包含文件,非ASCII数据和二进制数据的表单。
我们在通过 HTTP 向服务器发送 POST 请求提交数据,都是通过 form 表单形式提交的,代码如下:
提交时会向服务器端发出这样的数据(已经去除部分不相关的头信息),数据如下:
POST / HTTP/1.1
Content-Type:application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: w.sohu.com
Content-Length: 21
Connection: Keep-Alive
Cache-Control: no-cache
txt1=hello&txt2=world
对于普通的 HTML Form POST请求,它会在头信息里使用 Content-Length 注明内容长度。请求头信息每行一条,空行之后便是 Body,即“内容”(entity)。内容的格式是在头信息中的 Content-Type 指定的,如上是 application/x-www-form-urlencoded,这意味着消息内容会经过 URL 格式编码,就像在 GET请 求时 URL 里的 QueryString 那样。如:txt1=hello&txt2=world。
multipart/form-data 定义在 rfc2388 中,最早的 HTTP POST 是不支持文件上传的,给编程开发带来很多问题。但是在1995年,ietf 出台了 rfc1867,也就是《RFC 1867 -Form-based File Upload in HTML》,用以支持文件上传。所以 Content-Type 的类型扩充了multipart/form-data 用以支持向服务器发送二进制数据。因此,发送 POST 请求时候,表单 属性 enctype 共有二个值可选,这个属性管理的是表单的 MIME 编码:
注:form 表单中 enctype 的默认值是 enctype="application/x- www-form-urlencoded".
通过 form 表单提交文件操作如下:
浏览器将会发送以下数据:
POST /t2/upload.do HTTP/1.1
User-Agent: SOHUWapRebot
Accept-Language: zh-cn,zh;q=0.5
Accept-Charset: GBK,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Content-Length: 60408
Content-Type:multipart/form-data; boundary=ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Host: w.sohu.com
--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Content-Disposition: form-data; name="city"
Santa colo
--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Content-Disposition: form-data;name="desc"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
...
--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Content-Disposition: form-data;name="pic"; filename="photo.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
... binary data of the jpg ...
--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC--
从上面的 multipart/form-data 格式发送的请求的样式来看,它包含了多个 Parts,每个 Part 都包含头信息部分,
Part 头信息中必须包含一个 Content-Disposition 头,其他的头信息则为可选项, 比如 Content-Type 等。
Content-Disposition 包含了 type 和 一个名字为 name 的 parameter,type 是 form-data,name 参数的值则为表单控件(也即 field)的名字,如果是文件,那么还有一个 filename 参数,值就是文件名。对于可选的 Content-Type(如果没有的话),默认就是 text/plain。
注意:
如果文件内容是通过填充表单来获得,那么上传的时候,Content-Type
会被自动设置(识别)成相应的格式,如果没法识别,那么就会被设置成 “application/octet-stream
”。
如果想要灵活方便的传输文件的话,除了引入org.apache.httpcomponents基本的httpclient依赖外再额外引入org.apache.httpcomponents的httpmime依赖。
P.S.:即便不引入httpmime依赖,也是能传输文件的,不过功能不够强大。
在pom.xml中额外引入:
org.apache.httpcomponents
httpmime
4.5.5
代码示例:
/**
*
* 发送文件
*
* multipart/form-data传递文件(及相关信息)
*
* 注:如果想要灵活方便的传输文件的话,
* 除了引入org.apache.httpcomponents基本的httpclient依赖外
* 再额外引入org.apache.httpcomponents的httpmime依赖。
* 追注:即便不引入httpmime依赖,也是能传输文件的,不过功能不够强大。
*
*/
@Test
public void doPostUseFile() {
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpPost httpPost = new HttpPost("http://localhost:8080/http/doPostUseFile");
CloseableHttpResponse response = null;
try {
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
// 第一个文件
String filesKey = "files";
File file1 = new File("D:\\MyFiles\\poi\\picture.jpg");
multipartEntityBuilder.addBinaryBody(filesKey, file1);
// 第二个文件(多个文件的话,使用同一个key就行,后端用数组或集合进行接收即可)
File file2 = new File("D:\\MyFiles\\poi\\图片.jpg");
// 防止服务端收到的文件名乱码。 我们这里可以先将文件名URLEncode,然后服务端拿到文件名时在URLDecode。就能避免乱码问题。
// 文件名其实是放在请求头的Content-Disposition里面进行传输的,如其值为form-data; name="files"; filename="头像.jpg"
multipartEntityBuilder.addBinaryBody(filesKey, file2, ContentType.DEFAULT_BINARY, URLEncoder.encode(file2.getName(), "utf-8"));
// 其它参数(注:自定义contentType,设置UTF-8是为了防止服务端拿到的参数出现乱码)
ContentType contentType = ContentType.create("text/plain", Charset.forName("UTF-8"));
multipartEntityBuilder.addTextBody("name", "邓沙利文", contentType);
multipartEntityBuilder.addTextBody("age", "25", contentType);
HttpEntity httpEntity = multipartEntityBuilder.build();
httpPost.setEntity(httpEntity);
response = httpClient.execute(httpPost);
HttpEntity responseEntity = response.getEntity();
System.out.println("HTTPS响应状态为:" + response.getStatusLine());
if (responseEntity != null) {
System.out.println("HTTPS响应内容长度为:" + responseEntity.getContentLength());
// 主动设置编码,来防止响应乱码
String responseStr = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
System.out.println("HTTPS响应内容为:" + responseStr);
}
} catch (ParseException | IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
对应的接收示例:
@PostMapping("/doPostUseFile")
public String doPostUseFile(@RequestParam("name") String name,
@RequestParam("age") Integer age,
@RequestParam("files") List multipartFiles) throws UnsupportedEncodingException {
StringBuilder sb = new StringBuilder();
sb.append("\n");
sb.append("name=").append(name)
.append("\tage=").append(age);
String fileName;
for(MultipartFile file : multipartFiles){
sb.append("\n文件信息:\n");
fileName = file.getOriginalFilename();
if(fileName == null){
continue;
}
// 防止中文乱码问题
fileName = URLDecoder.decode(fileName, "utf-8");
sb.append("\t文件名:").append(fileName);
sb.append("\t文件大小:").append(file.getSize() * 1.0 /1024).append("KB");
sb.append("\tContentType:").append(file.getContentType());
sb.append("\n");
}
return sb.toString();
}
执行结果:
代码示例:
/**
* 发送流
*/
@Test
public void doPostUseStream() {
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpPost httpPost = new HttpPost("http://localhost:8080/http/doPostUseStream?name=邓沙利文");
CloseableHttpResponse response = null;
try {
InputStream stream = new ByteArrayInputStream("流啊流~".getBytes());
InputStreamEntity InputStream = new InputStreamEntity(stream);
httpPost.setEntity(InputStream);
response = httpClient.execute(httpPost);
HttpEntity responseEntity = response.getEntity();
System.out.println("HTTPS响应状态为:" + response.getStatusLine());
if (responseEntity != null) {
System.out.println("HTTPS响应内容长度为:" + responseEntity.getContentLength());
// 主动设置编码,来防止响应乱码
String responseStr = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
System.out.println("HTTPS响应内容为:" + responseStr);
}
} catch (ParseException | IOException e) {
e.printStackTrace();
} finally {
try {
// 释放资源
if (httpClient != null) {
httpClient.close();
}
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
接收端示例:
@PostMapping("/doPostUseStream")
public String doPostUseStream(@RequestParam("name") String name,
InputStream stream) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("\nname值为:").append(name);
sb.append("\n输入数据流内容为:");
BufferedReader bf = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
String line;
while((line = bf.readLine()) != null){
sb.append(line);
}
return sb.toString();
}
执行结果:
最后总结一下:使用HttpClient发送HTTP请求基本分为四步: