HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。现在HttpClient最新版本为 HttpClient 4.5 (GA) (2015-09-11)
以下列出的是 HttpClient 提供的主要的功能,要知道更多详细的功能可以参见 HttpClient 的主页。
(1)实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
(2)支持自动转向
(3)支持 HTTPS 协议
(4)支持代理服务器
(1) GET方法
使用 HttpClient 需要以下 6 个步骤:
1. 创建 HttpClient 的实例
2. 创建某种连接方法的实例,在这里是GetMethod。在 GetMethod 的构造函数中传入待连接的地址
3. 调用第一步中创建好的实例的 execute 方法来执行第二步中创建好的 method 实例
4. 读 response
5. 释放连接。无论执行方法是否成功,都必须释放连接
6. 对得到后的内容进行处理
根据以上步骤,我们来编写用GET方法来取得某网页内容的代码。
大部分情况下 HttpClient 默认的构造函数已经足够使用。 HttpClient httpClient = new DefaultHttpClient();
创建GET方法的实例。在GET方法的构造函数中传入待连接的地址即可。用GetMethod将会自动处理转发过程,如果想要把自动处理转发过程去掉的话,可以调用方法setFollowRedirects(false)。 GetMethod getMethod = new GetMethod(".....");
调用实例httpClient的executeMethod方法来执行getMethod。由于是执行在网络上的程序,在运行executeMethod方法的时候,需要处理两个异常,分别是HttpException和IOException。引起第一种异常的原因主要可能是在构造getMethod的时候传入的协议不对,比如不小心将"http"写成"htp",或者服务器端返回的内容不正常等,并且该异常发生是不可恢复的;第二种异常一般是由于网络原因引起的异常,对于这种异常(IOException),HttpClient会根据你指定的恢复策略自动试着重新执行executeMethod方法。HttpClient的恢复策略可以自定义(通过实现接口HttpMethodRetryHandler来实现)。通过httpClient的方法setParameter设置你实现的恢复策略,本文中使用的是系统提供的默认恢复策略,该策略在碰到第二类异常的时候将自动重试3次。executeMethod返回值是一个整数,表示了执行该方法后服务器返回的状态码,该状态码能表示出该方法执行是否成功、需要认证或者页面发生了跳转(默认状态下GetMethod的实例是自动处理跳转的)等。 //设置成了默认的恢复策略,在发生异常时候将自动重试3次,在这里你也可以设置成自定义的恢复策略。
(2)POST方法
根据RFC2616,对POST的解释如下:POST方法用来向目的服务器发出请求,要求它接受被附在请求后的实体,并把它当作请求队列(Request-Line)中请求URI所指定资源的附加新子项。POST被设计成用统一的方法实现下列功能:
对现有资源的注释(Annotation of existing resources)
向电子公告栏、新闻组,邮件列表或类似讨论组发送消息
提交数据块,如将表单的结果提交给数据处理过程
通过附加操作来扩展数据库
调用HttpClient中的PostMethod与GetMethod类似,除了设置PostMethod的实例与GetMethod有些不同之外,剩下的步骤都差不多。在下面的例子中,省去了与GetMethod相同的步骤,只说明与上面不同的地方,并以登录清华大学BBS为例子进行说明。
构造PostMethod之前的步骤都相同,与GetMethod一样,构造PostMethod也需要一个URI参数。在创建了PostMethod的实例之后,需要给method实例填充表单的值,在BBS的登录表单中需要有两个域,第一个是用户名(域名叫id),第二个是密码(域名叫passwd)。表单中的域用类NameValuePair来表示,该类的构造函数第一个参数是域名,第二参数是该域的值;将表单所有的值设置到PostMethod中用方法setRequestBody。另外由于BBS登录成功后会转向另外一个页面,但是HttpClient对于要求接受后继服务的请求,比如POST和PUT,不支持自动转发,因此需要自己对页面转向做处理。具体的页面转向处理请参见下面的"自动转向"部分。
说明:
一定要手动设置httpclient线程池大小,如下,仅作参考,具体情况,可以依据自己项目环境而定,只要是避免两个极端:
1)线程池已满,请求不到新的线程,导致大量请求超时。
2)线程池太大,内存过载或线程阻塞等不良情况产生。
httpClient = HttpClients.custom().setMaxConnPerRoute(500).setMaxConnTotal(800).build();
3)三个超时时间设定
//传输超时时间,默认120秒即2分钟
private static int socketTimeout = 120000;
//建立连接超时时间,默认60秒
private static int connectTimeout = 60000;
//获取连接超时时间,30秒
private static int connectRequest = 30000;
4)代码实现如下:
package com.liuxd.http;
import com.google.gson.Gson;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.SocketTimeoutException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.util.*;
public class HttpRequestUtil {
private static Logger log = LoggerFactory.getLogger(HttpRequestUtil.class);
//表示请求器是否已经做了初始化工作
private static volatile boolean hasInit = false;
//传输超时时间,默认120秒即2分钟
private static int socketTimeout = 120000;
//建立连接超时时间,默认60秒
private static int connectTimeout = 60000;
//获取连接超时时间,30秒
private static int connectRequest = 30000;
//请求器的配置
private static RequestConfig requestConfig;
//HTTP请求器
private static CloseableHttpClient httpClient;
public HttpRequestUtil() throws UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException, KeyStoreException, IOException {
init();
}
/**
*
*/
private static void init() {
httpClient = HttpClients.custom().setMaxConnPerRoute(500).setMaxConnTotal(800).build();
//根据默认超时限制初始化requestConfig
requestConfig = RequestConfig.custom().setConnectionRequestTimeout(connectRequest)
.setSocketTimeout(socketTimeout)
.setConnectTimeout(connectTimeout).build();
// HttpClientParams.setCookiePolicy(httpClient.getParams(), CookiePolicy.BROWSER_COMPATIBILITY);
hasInit = true;
}
/**
* http GET 请求
*
* @param url 请求url
* @return 请求结果
*/
public static String httpGet(String url) {
if (!hasInit) {
init();
}
String result = null;
HttpGet httpGet = new HttpGet(url);
log.info("executing GET request" + httpGet.getRequestLine());
try {
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity, "UTF-8");
} catch (ConnectionPoolTimeoutException e) {
log.error("http get throw ConnectionPoolTimeoutException(wait time out)");
} catch (ConnectTimeoutException e) {
log.error("http get throw ConnectTimeoutException");
} catch (SocketTimeoutException e) {
log.error("http get throw SocketTimeoutException");
} catch (Exception e) {
log.error("http get throw Exception");
e.printStackTrace();
} finally {
httpGet.abort();
}
return result;
}
/**
* 发送json的字符串
*
* @param url 请求url
* @param postData 请求体
* @return 返回字符串
*/
public static String sendPostString(String url, String postData) {
if (!hasInit) {
init();
}
HttpPost httpPost = new HttpPost(url);
log.info("POST过去的数据是:");
log.info(postData);
//得指明使用UTF-8编码,
StringEntity postEntity = new StringEntity(postData, "UTF-8");
httpPost.addHeader("Content-Type", "application/json");
httpPost.setEntity(postEntity);
return httpPost(httpPost);
}
private static String httpPost(HttpPost httpPost) {
//设置请求器的配置
httpPost.setConfig(requestConfig);
String result = null;
log.info("executing request" + httpPost.getRequestLine());
try {
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity, "UTF-8");
} catch (ConnectionPoolTimeoutException e) {
log.error("http post throw ConnectionPoolTimeoutException(wait time out)");
} catch (ConnectTimeoutException e) {
log.error("http post throw ConnectTimeoutException");
} catch (SocketTimeoutException e) {
log.error("http post throw SocketTimeoutException");
} catch (Exception e) {
log.error("http post throw Exception");
e.printStackTrace();
} finally {
httpPost.abort();
}
return result;
}
/**
* 设置连接超时时间
*
* @param socketTimeout 连接时长,默认10秒
*/
public static void setSocketTimeout(int socketTimeout) {
HttpRequestUtil.socketTimeout = socketTimeout;
resetRequestConfig();
}
/**
* 设置传输超时时间
*
* @param connectTimeout 传输时长,默认30秒
*/
public static void setConnectTimeout(int connectTimeout) {
HttpRequestUtil.connectTimeout = connectTimeout;
resetRequestConfig();
}
private static void resetRequestConfig() {
requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
}
/**
* @param requestConfig 设置HttpsRequest的请求器配置
*/
public static void setRequestConfig(RequestConfig requestConfig) {
HttpRequestUtil.requestConfig = requestConfig;
}
public static String postForm(String url, Map params) throws Exception {
if (!hasInit) {
init();
}
HttpPost httpPost = new HttpPost(url);
if (params != null && params.size() > 0) {
List nvps = new ArrayList();
for (String paramName : params.keySet()) {
Object val = params.get(paramName);
if ((val instanceof Map || val instanceof Collection)) {
nvps.add(new BasicNameValuePair(paramName, new Gson().toJson(val)));
} else {
nvps.add(new BasicNameValuePair(paramName, val.toString()));
}
}
httpPost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
}
return httpPost(httpPost);
}
}