HttpClient 简介与实现

HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

1、简介

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)

2、功能

以下列出的是 HttpClient 提供的主要的功能,要知道更多详细的功能可以参见 HttpClient 的主页。

(1)实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)

(2)支持自动转向

(3)支持 HTTPS 协议

(4)支持代理服务器

3、常用基本功能

(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,不支持自动转发,因此需要自己对页面转向做处理。具体的页面转向处理请参见下面的"自动转向"部分。

4、代码实现

说明:

一定要手动设置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<String, Object> params) throws Exception {
        if (!hasInit) {
            init();
        }
        HttpPost httpPost = new HttpPost(url);
        if (params != null && params.size() > 0) {
            List<NameValuePair> 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);
    }

    
}

猜你喜欢

转载自blog.csdn.net/jiahao1186/article/details/82431314