常用 HttpClient API学习
前言
因在项目中看到各式各样的http请求编写代码,例如:
- java JDK自带java.net HttpURLConnection版本,
- org.apache.httpcomponents httpClient的版本,
- commons-httpclient commons-httpclient的版本,
- 项目前辈自行封装的http请求
各式各样的http请求的样例,让学习HttpClient API的我看得眼花缭乱,如果没有参照就有点无从下手,也没办法灵活去运用,故自行查资料系统学习记录一下,以便自己学习、灵活的运用、能看懂别人的代码改问题(虽然本人很不喜欢去填坑)以及规范自己的代码尽量不要给别人造坑。本文比较适合http小白。
一、各HttpClient API的基本介绍
1.java.net 介绍
JDK自带的网络编程包,里面封装了比较基础的网络编程的类,也是写http请求最基础的一种方式。
没有太多东西可介绍,可根据后面写的代码进行分析学习,简介和参考文档链接:
java.net简介
jdk的官网文档
2. commons-httpclient 和org.apache.httpcomponents介绍
org.apache.httpcomponents 官方介绍
commons-httpclient 官方介绍
点开两个链接,查看其介绍你会发现介绍其实大同小异,而且链接的域名也是一致的,并且在commons-httpclient的官方介绍上可以看到该项目已经于2011年,就不再进行维护了,其最高的版本为3.1,所以如果还在用commons-httpclient来写http请求的话,大概率用的包应该是commons-httpclient-3.1.jar,且官网也注明了,它已被HttpClient和HttpCore模块中的Apache HttpComponents项目取代,提供更好的性能和更大的灵活性。
那么回答我, commons-httpclient和org.apache.httpcomponents是啥关系?用的使用最好用哪一个?
是的没错,你可以理解为commons-httpclient已经退休了,org.apache.httpcomponents继承了commons-httpclient的衣钵继续发展了,使用的话也建议使用官方描述的,更强大的org.apache.httpcomponents。(ps:之后就不讲解commons-httpclient的代码详解了)
二、各HttpClient API详解(附代码)
1.java JDK自带java.net HttpURLConnection发送http请求
这个可以当做入门级的学习,通过例子理解一下http请求大致有哪些步骤,但实际开发不建议使用java自带的这一套,已经有前辈造了足够有效率的轮子,我们就没必要造一下一样的轮子,顺便再写点bug进去是吧,想深入了解的话还是要好好学习一下http协议,使用别人封装好的代码也可以去看看框架源码。
jdk的代码啥包都不用引,直接粘贴就可以使用,测试的地址都是postman的样例地址,学习http调用可以参照这postman一起学习,postman也是开发中常用的工具
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Base64;
/**
* jdk java.net包中请求http
*/
public class HttpSendCase {
// 测试get请求的url
public static final String url = "https://postman-echo.com/get";
// 测试post请求的url
public static final String postUrl = "https://postman-echo.com/post";
public static void main(String[] args) {
System.out.println("doGet() = " + doGet(url));
System.out.println("doPost() = " + doPost(postUrl, "{\"method\": \"POST\"}"));
}
/**
* @return
*/
public static String doGet(String url) {
//接口返回结果
StringBuffer result = new StringBuffer();
InputStream inputStream = null;
BufferedReader bufferedReader = null;
HttpURLConnection httpURLConnection = null;
try {
//step1:创建远程连接
URL serverUrl = new URL(url);//使用统一资源定位符定位资源
httpURLConnection = (HttpURLConnection) serverUrl.openConnection();//创建连接对象
/**
* URLConnection 是根据 URL 创建的,是用于访问 URL 所指向资源的通信链接,最主要的几个功能:
* 1、通过URL类,调用openConnection创建连接对象
* 2、设置参数(例如setDoInput setDoOutput)和一般请求属性(setRequestProperty)
* 3、通过connect方法建立到远程对象的实际连接
* 4、设置远程对象变为可用 远程对象的头字段和内容变为可访问
* 该类API见https://docs.oracle.com/javase/8/docs/api/?xd_co_f=47c934d9-e663-4eba-819c-b726fc2d0847
*
* HttpURLConnection:URLConnection的子类,提供一些特定于 HTTP 协议的附加功能
* 所以一般openConnection()后都会强转为该对象
* 内含各种http状态码(例如java.net.HttpURLConnection#HTTP_OK),写代码的时候可以用上,以规范代码
*/
//step2:设置连接方式 HttpURLConnection默认是GET
httpURLConnection.setRequestMethod("GET");
//step3:设置参数
httpURLConnection.setConnectTimeout(10 * 1000);
httpURLConnection.setReadTimeout(10 * 1000);
//step4:发起请求
httpURLConnection.connect();
//step5:获取请求数据
int responseCode = httpURLConnection.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
System.out.println("调用失败,连接状态码:" + responseCode);
}
//获取连接的输入流(字节流,操作的是byte[])
inputStream = httpURLConnection.getInputStream();
//InputStreamReader类是从字节流到字符流的桥接器:它使用指定的字符集读取字节并将它们解码为字符
//https://blog.csdn.net/ai_bao_zi/article/details/81133476
//BufferedReader类从字符输入流中读取文本并缓冲字符,以便有效地读取字符,数组和行
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
//逐行对相应结果进行读取
if (bufferedReader != null) {
String temp = null;
while ((temp = bufferedReader.readLine()) != null) {
result.append(temp);
}
}
} catch (MalformedURLException e) {
System.out.println("远程连接地址错误!");
e.printStackTrace();
} catch (IOException e) {
System.out.println("读取错误!");
e.printStackTrace();
} finally {
//step6:关闭连接,字节流
try {
if (inputStream != null) {
inputStream.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
httpURLConnection.disconnect();
}
return result.toString();
}
public static String doPost(String url, String param) {
StringBuffer result = new StringBuffer();
HttpURLConnection httpURLConnection = null;
InputStream inputStream = null;
BufferedReader bufferedReader = null;
OutputStream outputStream = null;
try {
//step1:建立远程连接
URL serverUrl = new URL(url);
httpURLConnection = (HttpURLConnection) serverUrl.openConnection();
//step2:设置请求方法
httpURLConnection.setRequestMethod("POST");
//step3:设置请求参数
httpURLConnection.setConnectTimeout(10 * 1000);
httpURLConnection.setReadTimeout(10 * 1000);
httpURLConnection.setDoOutput(true);//默认是false,因为POST请求需要传入参数,所以必须手动设置成true;
httpURLConnection.setDoInput(true);//默认是true
//step4:设置一般请求属性
httpURLConnection.setRequestProperty("Content-Type", "application/json");//传送数据以什么样的形式发送
httpURLConnection.setRequestProperty("Accept", "*/*");//浏览器可接收的类型
//还可以设置权限,设置请求头(都使用setRequestProperty来设置)
httpURLConnection.setRequestProperty("timestamp",String.valueOf(System.currentTimeMillis()));//设置请求头
//Basic Auth HTTP授权的授权证书参数(这里简单介绍一下Basic Auth授权,
//这个授权的验证方式是直接在请求头中加入一个请求头参数Authorization,其值格式为 Basic+空格+(用户名+英文分号+密码)的base64字符串,例子如下 )
String info = "Lisa:Lisa123";
httpURLConnection.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString(info.getBytes()));
//step5:设置参数
if (param != null && param.length() > 0) {
outputStream = httpURLConnection.getOutputStream();
outputStream.write(param.getBytes("UTF-8"));
outputStream.flush();
}
//step6:发起请求
httpURLConnection.connect();
int responseCode = httpURLConnection.getResponseCode();
//step7:获取请求返回数据
if (responseCode != HttpURLConnection.HTTP_OK) {
System.out.println("调用失败,连接状态码:" + responseCode);
}
inputStream = httpURLConnection.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
if (bufferedReader != null) {
String temp = bufferedReader.readLine();
while (temp != null) {
result.append(temp);
temp = bufferedReader.readLine();
}
}
} catch (MalformedURLException e) {
System.out.println("远程连接地址错误!");
e.printStackTrace();
} catch (IOException e) {
System.out.println("读取错误!");
e.printStackTrace();
} finally {
//step8:关闭连接
try {
if (inputStream != null) {
inputStream.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
httpURLConnection.disconnect();
}
return result.toString();
}
}
代码运行结果:
2.org.apache.httpcomponents httpClient发送http请求
这里需要注意一下jdk版本与Apache HttpComponents项目的对应
目前Apache HttpComponents的最新HttpClient的版本是到5.1,根据官网描述,版本对应
Apache HttpComponents版本 | jdk版本 |
---|---|
从 4.4 开始 | Java 1.6 或更新版本 |
从 5.0 开始 | Java 1.7 或更新版本 |
从 5.2 开始(5.2暂未发布) | Java 1.8 或更新版本 |
版本释义
版本后缀 | 含义 | 使用建议 |
---|---|---|
alpha | 内部测试版 | α是希腊字母的第一个,表示最早的版本,一般用户不要下载这个版本,这个版本包含很多BUG,功能也不全,主要是给开发人员和 测试人员测试和找BUG用的 |
beta | 公开测试版 | β是希腊字母的第二个,顾名思义,这个版本比alpha版发布得晚一些,主要是给“部落”用户和忠实用户测试用的,该版本任然存 在很多BUG,但是相对alpha版要稳定一些。这个阶段版本的软件还会不断增加新功能。如果你是发烧友,可以下载这个版本 |
rc | Release Candidate(候选版本) | 该版本又较beta版更进一步了,该版本功能不再增加,和最终发布版功能一样。这个版本有点像最终发行版之前的一个类似 预览版,这个的发布就标明离最终发行版不远了。作为普通用户,如果你很急着用这个软件的话,也可以下载这个版本。 |
stable | 稳定版 | 在开源软件中,都有stable版,这个就是开源软件的最终发行版,用户可以放心大胆的用了。 |
一般开发不建议使用最新的版本因为可能会有不稳定的情况,本文使用Apache HttpComponents 4.5.8版本进行开发,
pom引用如下
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.8</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.1</version>
</dependency>
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class ComponectHttpSendCase {
// 测试get请求的url
public static final String url = "https://postman-echo.com/get";
// 测试post请求的url
public static final String postUrl = "https://postman-echo.com/post";
public static Gson gson = new GsonBuilder().setPrettyPrinting().create();
public static void main(String[] args) {
System.out.println("doGet() = " + doGet(url));
System.out.println("doPost() = " + doPost(postUrl, "{\"method\": \"POST\"}"));
}
public static String doGet(String url) {
//接口返回结果
String result = "";
CloseableHttpResponse response = null;
//区别见https://blog.csdn.net/df0128/article/details/83043457
//step1:创建连接
CloseableHttpClient defaultHttpClient = HttpClients.createDefault();
/*
HttpClients.createDefault(); 创建默认的HttpClient
HttpClients.createMinimal(); 创建最小配置的HttpClient
HttpClients.createSystem(); 依据系统配置创建HttpClient
HttpClients.custom(); 自定义配置HttpClientBuilder
即new HttpClientBuilder().build()=CloseableHttpClient
理解为HttpClientBuilder是HttpClient的构建器
HttpClients.createDefault()==new HttpClientBuilder().build()==HttpClients.custom().build()
*/
//step2:创建HttpGet对象
HttpGet httpGet = new HttpGet(url);
//step3:增加请求头、认证等
httpGet.addHeader("Content-Type", "text/plain");
try {
//step4:httpClient发送HttpGet请求,获取相应
response = defaultHttpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
System.out.println("调用失败,连接状态码:" + statusCode);
}
//step5:获取请求数据
result = EntityUtils.toString(response.getEntity());
} catch (IOException e) {
System.out.println("读取错误!");
e.printStackTrace();
} finally {
// step6:关闭连接
httpGet.releaseConnection();
if (response != null) {
try {
response.close();
} catch (IOException e) {
System.out.println("CloseableHttpResponse没有正确close!");
e.printStackTrace();
}
}
/*
CloseableHttpResponse.close();//直接会被关闭,不可在被重用
httpPost.releaseConnection();//这种方式可以让后面的连接重用
httpGet.releaseConnection();//这种方式可以让后面的连接重用
*/
}
return result;
}
public static String doPost(String url, String param) {
//接口返回结果
String result = "";
CloseableHttpResponse response = null;
//step1:创建HttpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
//step2:创建httpPost对象
HttpPost httpPost = new HttpPost(url);
//step3:增加请求头、认证等
httpPost.addHeader("Content-Type", "application/json");
try {
//step4:设置请求体
StringEntity stringEntity = new StringEntity(param);
stringEntity.setContentType("application/json");
stringEntity.setContentEncoding("UTF-8");
httpPost.setEntity(stringEntity);
//以上四行等同于下行
//httpPost.setEntity(new StringEntity(param, ContentType.APPLICATION_JSON));
/**这里需要注意,可能会有疑问,明明请求头中有设置Content-Type,为什么StringEntify里还需要设置
* Content-Type实际上是Entity Header,在本文中虽然通过addHeader设置过Content-Type,但后续httpPost.setEntity()
* 又通过StringEntity重新设置了Content-Type,所以会以StringEntity配置的为准
* 如果StringEntity没有设置Content-Type,那么StringEntity会使用默认的Content-Type,即text/plain,编码默认是ISO_8859_1
* 所以AbstractHttpEntity的实现类,需要设置Content-Type的,都配置好,不然可能会出错
*/
//step5:HttpClient执行发送post请求
response = httpClient.execute(httpPost);
//step6:解析返回数据
int statusCode = response.getStatusLine().getStatusCode();
if (HttpStatus.SC_OK != statusCode) {
System.out.println("调用失败,连接状态码:" + statusCode);
}
result = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (IOException e) {
System.out.println("读取错误!");
e.printStackTrace();
} finally {
// step6:关闭连接
httpPost.releaseConnection();
if (response != null) {
try {
response.close();
} catch (IOException e) {
System.out.println("CloseableHttpResponse没有正确close!");
e.printStackTrace();
}
}
}
return result;
}
}
运行效果也是一样的,如图,这里我们可以发现,比起java.net自带,Http Component的HttpClient使用起来更加的方便,根据API来使用也不需要我们去注意太多的一些细节(比如post请求读取的代码段,java.net需要从流开始读,但使用HttpClient,读取数据写的简单点其实一行代码就搞定了)
三、查漏补缺
Http Header的四种类型
Header类型 | 说明 | Header字段示例 |
---|---|---|
General Header | 请求和响应通用(通信过程中可被各方识别即作为General Header,否则识别为Entity header) | Cache-Control,Connection,Date,Pragma Trailer,Transfer-Encoding,Upgrade,Via,Warning |
Request Header | 请求头字段允许客户端传递额外的信息,将有关请求和客户端本身的信息发送给服务器。这些字段作为请求修饰符,具有语义,相当于编程语言方法调用的参数 | Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Max-Forwards,Proxy-Authorization,Referer,Range,TE,User-Agent |
Response Header | 响应头字段允许服务器传递不能放在状态行中的关于响应的附加信息。这些头字段提供了关于服务器的信息以及关于对Request-URI标识的资源的进一步访问的信息。Response-header字段名只有在与协议版本的更改相结合时才能可靠地进行扩展。但是,如果通信中的所有各方都认为新的或实验性的报头字段是响应报头字段,则可以给出响应报头字段的语义。无法识别的报头字段被视为实体报头字段。 | Accept-Ranges,Age,ETag,Location,Proxy-Authenticate,Retry-After,Server,Vary,WWW-Authenticate |
Entity Header | 实体头字段定义了关于实体主体的元信息,如果没有实体,则定义了关于请求所标识的资源的元信息。其中一些元信息是可选的;有些可能是本规范的某些部分所要求的。扩展报头机制允许在不改变协议的情况下定义额外的实体报头字段,但是这些字段不能被接收者识别。不可识别的报头字段应该被接收方忽略,并且必须通过透明代理转发。 | Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-MD5,Content-Range,Content-Type,Expires,Last-Modified,extension-header |
关于Entity Body:
与HTTP请求或响应一起发送的实体体(如果有的话)的格式和编码由实体头字段定义。只有当消息体存在时,实体体才会出现在消息中。实体体是通过解码消息体中任何可能已应用于确保消息安全和正确传输的transfer-encoding获得的。
message-body = entity-body(entity-body encoded as per Transfer-Encoding)
请求体=实体体(以一种传输编码 编码后的实体体)
URL的初始化
日常使用URL的时候,接口地址可能还是会用String,StringBuilder来生成,但其实URL可以用URIBuilder来构建,这样会更规范一些。
try {
String url = "https://postman-echo.com/get";
URIBuilder uriBuilder = new URIBuilder(url);
uriBuilder.addParameter("param1", "2");
uriBuilder.addParameter("param2" ,"5");
uriBuilder.setParameter("param2", "6");
System.out.println(uriBuilder.getScheme());
System.out.println(uriBuilder.getUserInfo());
System.out.println(uriBuilder.getHost());
System.out.println(uriBuilder.getPort());
System.out.println(uriBuilder.getPath());
System.out.println(uriBuilder.getQueryParams());
System.out.println(uriBuilder.getFragment());
System.out.println(uriBuilder.getCharset());
} catch (URISyntaxException e) {
e.printStackTrace();
}
可以使用URIBuilder.build()来构建URL,更加规范,
运行结果:
参考资料:
Http Header资料
java.net中文说明
Http-Components教程 – 基本原理
alpha、beta、rc各版本区别
JDK8 官方文档