HttpClient是不错的http工具,可是每次升级api都变化很大,可能是4.x版本时间太短还不够稳定吧。今天在升级的时候遇到了一个问题。http请求设定的超时时间没有用了。之前获取httpclient实例的工具类如下

import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
public class HttpConnectionManager {
    private static HttpParams httpParams;
    private static ClientConnectionManager connectionManager;
    /**
     * 最大连接数
     */
    public final static int MAX_TOTAL_CONNECTIONS = 800;
    /**
     * 获取连接的最大等待时间
     */
    public final static int WAIT_TIMEOUT = 60000;
    /**
     * 每个路由最大连接数
     */
    public final static int MAX_ROUTE_CONNECTIONS = 400;
    /**
     * 连接超时时间
     */
    public final static int CONNECT_TIMEOUT = 30000;
    /**
     * 读取超时时间
     */
    public final static int READ_TIMEOUT = 30000;
    static {
        httpParams = new BasicHttpParams();
        // 设置最大连接数
        ConnManagerParams.setMaxTotalConnections(httpParams,
                MAX_TOTAL_CONNECTIONS);
        // 设置获取连接的最大等待时间
        ConnManagerParams.setTimeout(httpParams, WAIT_TIMEOUT);
        // 设置每个路由最大连接数
        ConnPerRouteBean connPerRoute = new ConnPerRouteBean(
                MAX_ROUTE_CONNECTIONS);
        ConnManagerParams.setMaxConnectionsPerRoute(httpParams, connPerRoute);
        // 设置连接超时时间
        HttpConnectionParams.setConnectionTimeout(httpParams, CONNECT_TIMEOUT);
        // 设置读取超时时间
        HttpConnectionParams.setSoTimeout(httpParams, READ_TIMEOUT);
        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory
                .getSocketFactory(), 80));
        registry.register(new Scheme("https", SSLSocketFactory
                .getSocketFactory(), 443));
        connectionManager = new ThreadSafeClientConnManager(httpParams,
                registry);
    }
    public static DefaultHttpClient getHttpClient() {
        return new DefaultHttpClient(connectionManager, httpParams);
    }
    public static void shutdown() {
        connectionManager.shutdown();
    }
}

很简单的一个类,初始化一些设置,然后提供一个getHttpClient方法,这里设定httpParam使用的是DefaultHttpClient的构造方法。很简单。

但是到了4.2~~~~。这个构造方法没有了。。xd,不再可以直接传递httpParams进去设定。

所以。。

        HttpConnectionParams.setConnectionTimeout(httpParams, CONNECT_TIMEOUT);
        HttpConnectionParams.setSoTimeout(httpParams, READ_TIMEOUT);

根本就不管用。。。HttpConnectionParams的两个方法如下

    public static void setConnectionTimeout(final HttpParams params, int timeout) {
        if (params == null) {
            throw new IllegalArgumentException("HTTP parameters may not be null");
        }
        params.setIntParameter
            (CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
    }
    public static void setSoTimeout(final HttpParams params, int timeout) {
        if (params == null) {
            throw new IllegalArgumentException("HTTP parameters may not be null");
        }
        params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, timeout);
    }
    

就是给传进来的httpParams设定两个参数而已。那这两个set方法对应的get方法在哪里调用呢。。我们看代码。

观察httpclient.execute(httpget)方法,跳到AbstractHttpClient的execute方法,代码如下

    public final HttpResponse execute(HttpHost target, HttpRequest request,
                                      HttpContext context)
        throws IOException, ClientProtocolException {
        if (request == null) {
            throw new IllegalArgumentException
                ("Request must not be null.");
        }
        // a null target may be acceptable, this depends on the route planner
        // a null context is acceptable, default context created below
        HttpContext execContext = null;
        RequestDirector director = null;
        HttpRoutePlanner routePlanner = null;
        ConnectionBackoffStrategy connectionBackoffStrategy = null;
        BackoffManager backoffManager = null;
        // Initialize the request execution context making copies of
        // all shared objects that are potentially threading unsafe.
        synchronized (this) {
            HttpContext defaultContext = createHttpContext();
            if (context == null) {
                execContext = defaultContext;
            } else {
                execContext = new DefaultedHttpContext(context, defaultContext);
            }
    
            // Create a director for this request
            director = createClientRequestDirector(
                    getRequestExecutor(),
                    getConnectionManager(),
                    getConnectionReuseStrategy(),
                    getConnectionKeepAliveStrategy(),
                    getRoutePlanner(),
                    getProtocolProcessor(),
                    getHttpRequestRetryHandler(),
                    getRedirectStrategy(),
                    getTargetAuthenticationStrategy(),
                    getProxyAuthenticationStrategy(),
                    getUserTokenHandler(),
                    determineParams(request));
            routePlanner = getRoutePlanner();
            connectionBackoffStrategy = getConnectionBackoffStrategy();
            backoffManager = getBackoffManager();
        }

看41行的方法

    protected HttpParams determineParams(HttpRequest req) {
        return new ClientParamsStack
            (null, getParams(), req.getParams(), null);
    }

//还有getParams()
    public synchronized final HttpParams getParams() {
        if (defaultParams == null) {
            defaultParams = createHttpParams();
        }
        return defaultParams;
    }

发现了吧。。httpclient的request参数,是这么初始化的,但是你根本就没给他传递过来httpParams,所以defaultParams是null,自己创建了一个。。我们看timeout的获取代码,在DefaultRequestDirector#execute 方法中,太长了,我就贴关键代码

                   long timeout = HttpClientParams.getConnectionManagerTimeout(params);
                    try {
                        managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
                    } catch(InterruptedException interrupted) {
                        InterruptedIOException iox = new InterruptedIOException();
                        iox.initCause(interrupted);
                        throw iox;
                    }

哦。原来之前在工具类中设定的timeout参数是这么获取的。看看getConnectionManagerTimeout干啥了。相关代码

    public static int getConnectionTimeout(final HttpParams params) {
        if (params == null) {
            throw new IllegalArgumentException("HTTP parameters may not be null");
        }
        return params.getIntParameter
            (CoreConnectionPNames.CONNECTION_TIMEOUT, 0);
    }

坑爹啊。传过来的params是刚才httpclient自己创建的。你去获得参数。当然是为空。。然后设定为0了。。所以timeout根本就不管用。。。

所以说。。在工具类里面设定HttpConnectionParams根本就不管用,因为你给httpParams设定的参数,httpclient根本就没接收到,最后的解决办法很简单,自己设定下httpParams好了。。修改后的代码

import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
public class HttpConnectionManager {
    private static HttpParams httpParams;
    private static PoolingClientConnectionManager cm;
    /**
     * 最大连接数
     */
    public final static int MAX_TOTAL_CONNECTIONS = 200;
    /**
     * 每个路由最大连接数
     */
    public final static int MAX_ROUTE_CONNECTIONS = 20;
    /**
     * 连接超时时间
     */
    public final static int CONNECT_TIMEOUT = 30000;
    /**
     * 读取超时时间
     */
    public final static int READ_TIMEOUT = 30000;
    static {
        httpParams = new BasicHttpParams();
        // 连接请求超时
        httpParams.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
                CONNECT_TIMEOUT);
        // 数据读取超时
        httpParams.setParameter(CoreConnectionPNames.SO_TIMEOUT, READ_TIMEOUT);
        // 编码
        httpParams.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET,
                "UTF-8");
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory
                .getSocketFactory()));
        schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory
                .getSocketFactory()));
        cm = new PoolingClientConnectionManager(schemeRegistry);
        // Increase max total connection to 200
        cm.setMaxTotal(MAX_TOTAL_CONNECTIONS);
        // Increase default max connection per route to 20
        cm.setDefaultMaxPerRoute(MAX_ROUTE_CONNECTIONS);
    }
    public static DefaultHttpClient getHttpClient() {
        DefaultHttpClient httpClient = new DefaultHttpClient(cm);
        httpClient.setParams(httpParams);
        return httpClient;
    }
    public static void shutdown() {
        cm.shutdown();
    }
    public static PoolingClientConnectionManager getConnectionManager() {
        return cm;
    }
}

over了。。真是蛋疼啊。

ps:实际想想,既然没有了直接设定httpParams的地方,那应该提供方法了吧,还是我自己的思路有问题。。对httpclient了解不够深。。

参考:

http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html

http://blog.csdn.net/shootyou/article/details/6415248

http://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html

Comments
Write a Comment