Using HttpClient properly to avoid CLOSE_WAIT TCP connections

         Apache的HttpComponent组件,用的人不在少数。但是能用好的人,却微乎其微,为什么?很简单,TCP里面的细节实现不是每个人都能捕获到的(细节是魔鬼),像并发请求控制&资源释放,Nagle算法参数优化,Connection eviction,跟ulimit配对的total connection,重定向策略定制化,两类超时时间的合理设置,流读写等等。

         在最近的项目中,更是破天荒的遇到了close_wait问题,所以利用业余时间索性将之前同学写的HttpClient优化了一遍。下面我将贴出代码,如果大家发现了还有改进的余地,记得千万要留言知会我,共创最棒的代码:

 

/**
 * 史上最棒的HttpClient4封装,details please see
 * http://hc.apache.org/httpcomponents-client-ga/tutorial/html/index.html
 * 
 * @author von gosling 2013-5-7
 */
public class HttpClientManager {

    //Consider ulimit
    private static final int                   DEFAULT_MAX_TOTAL_CONNECTIONS     = 7500;
    //notice IE 6,7,8  
    private static final int                   DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 200;

    private static final int                   DEFAULT_CONN_TIMEOUT_MILLISECONDS = 5 * 1000;

    private static final int                   DEFAULT_READ_TIMEOUT_MILLISECONDS = 60 * 1000;

    private static final int                   INIT_DELAY                        = 5 * 1000;

    private static final int                   CHECK_INTERVAL                    = 5 * 60 * 1000;

    private static String                      HTTP_REQUEST_ENCODING             = "UTF-8";
    private static String                      LINE_SEPARATOR                    = "\r\n";

    private static final Logger                LOG                               = LoggerFactory
                                                                                         .getLogger(HttpClientManager.class);

    private static ThreadSafeClientConnManager connectionManager;
    static {
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
        //schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));

        connectionManager = new ThreadSafeClientConnManager(schemeRegistry);
        connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);
        connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);

        //Connection eviction
        ScheduledExecutorService scheduledExeService = Executors.newScheduledThreadPool(1,
                new DaemonThreadFactory("Http-client-ConenctionPool-Monitor"));
        scheduledExeService.scheduleAtFixedRate(new IdleConnectionMonitor(connectionManager),
                INIT_DELAY, CHECK_INTERVAL, TimeUnit.MILLISECONDS);
    }

    public static String doPost(String reqURL, Map<String, String> params, String encoding,
                                Boolean enableSSL) {
        HttpClient httpClient = getHttpClient(enableSSL);

        String responseContent = "";
        try {
            HttpPost httpPost = buildHttpPostRequest(reqURL, params, encoding);
            HttpResponse response = httpClient.execute(httpPost);

            //            validateResponse(response, httpPost);

            HttpEntity entity = response.getEntity();
            if (entity != null) {
                // responseLength = entity.getContentLength();
                responseContent = EntityUtils.toString(entity, encoding);
                //Ensure that the entity content has been fully consumed and the underlying stream has been closed.
                EntityUtils.consume(entity);
            } else {
                LOG.warn("Http entity is null! request url is {},response status is {}", reqURL,
                        response.getStatusLine());
            }
        } catch (ConnectTimeoutException e) {
            LOG.warn(e.getMessage());
        } catch (SocketTimeoutException e) {
            LOG.warn("Read time out!");
        } catch (SSLPeerUnverifiedException e) {
            LOG.warn("Peer not authenticated!");
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
        return responseContent;
    }

    public static String doPost(String reqURL, final String entities, String encoding) {
        HttpClient httpClient = getHttpClient(false);

        String responseContent = "";
        try {
            AbstractHttpEntity printWriterEntity = new AbstractHttpEntity() {
                public boolean isRepeatable() {
                    return false;
                }

                public long getContentLength() {
                    return -1;
                }

                public boolean isStreaming() {
                    return false;
                }

                public InputStream getContent() throws IOException {
                    // Should be implemented as well but is irrelevant for this case
                    throw new UnsupportedOperationException();
                }

                public void writeTo(final OutputStream outstream) throws IOException {
                    PrintWriter writer = new PrintWriter(new OutputStreamWriter(outstream,
                            HTTP_REQUEST_ENCODING));
                    writer.print(entities);
                    writer.print(LINE_SEPARATOR);
                    writer.flush();
                }

            };
            HttpPost httpPost = new HttpPost(reqURL);
            //If the data is large enough that you need to stream it,
            //you can write to a temp file and use FileEntity or possibly set up a pipe and use InputStreamEntity
            httpPost.setEntity(printWriterEntity);
            HttpResponse response = httpClient.execute(httpPost);

            validateResponse(response, httpPost);

            HttpEntity entity = response.getEntity();
            if (entity != null) {
                responseContent = EntityUtils.toString(entity, encoding);
                //Ensure that the entity content has been fully consumed and the underlying stream has been closed.
                EntityUtils.consume(entity);
            } else {
                LOG.warn("Http entity is null! request url is {},response status is {}", reqURL,
                        response.getStatusLine());
            }
        } catch (SocketTimeoutException e) {
            LOG.warn("Read time out!");
        } catch (SSLPeerUnverifiedException e) {
            LOG.warn("Peer not authenticated!");
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
        return responseContent;
    }

    private static X509TrustManager customTrustManager(HttpClient httpClient) {
        //Trusting all certificates
        X509TrustManager xtm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            if (null != ctx) {
                ctx.init(null, new TrustManager[] { xtm }, null);
                SSLSocketFactory socketFactory = new SSLSocketFactory(ctx,
                        SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
                httpClient.getConnectionManager().getSchemeRegistry()
                        .register(new Scheme("https", 443, socketFactory));
            }
        } catch (Exception e) {
            LOG.error(e.getMessage());
        }

        return xtm;
    }

    private static HttpClient getHttpClient(Boolean enableSSL) {
        DefaultHttpClient httpClient = new DefaultHttpClient(connectionManager);
        httpClient.setRedirectStrategy(new RedirectStrategy() { //设置重定向处理方式为自行处理
                    @Override
                    public boolean isRedirected(HttpRequest request, HttpResponse response,
                                                HttpContext context) throws ProtocolException {
                        return false;
                    }

                    @Override
                    public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response,
                                                      HttpContext context) throws ProtocolException {
                        return null;
                    }
                });

        httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,
                DEFAULT_READ_TIMEOUT_MILLISECONDS);
        httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
                DEFAULT_CONN_TIMEOUT_MILLISECONDS);
        //According to http use-case to decide to whether to open TCP_NODELAY option,So does SO_LINGER option 
        httpClient.getParams().setParameter(CoreConnectionPNames.TCP_NODELAY, Boolean.TRUE);
        httpClient.getParams().setParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK,
                Boolean.FALSE);

        if (enableSSL) {
            customTrustManager(httpClient);
        }

        return httpClient;
    }

    public static Map.Entry<Integer, String> doGetHttpResponse(String url, String encoding) {
        HttpClient httpClient = getHttpClient(false);
        HttpGet httpget = new HttpGet(url);
        try {
            EncodingResponseHandler responseHandler = new EncodingResponseHandler();

            if (StringUtils.isBlank(encoding)) {
                encoding = HTTP_REQUEST_ENCODING;
            }
            responseHandler.setEncoding(encoding);

            return httpClient.execute(httpget, responseHandler);
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
        return null;
    }

    public static String doGet(String url, String encoding) {
        Map.Entry<Integer, String> ret = doGetHttpResponse(url, encoding);
        if (ret == null) {
            return "";
        }
        if (ret.getKey() != HttpStatus.SC_OK) {
            LOG.error(
                    "Did not receive successful HTTP response: status code = {}, request url = {}",
                    ret.getKey(), url);
        }

        return ret.getValue();
    }

    public static void doPost(String url, Map<String, String> params) {
        HttpClient httpClient = getHttpClient(false);
        try {
            HttpPost httpPost = buildHttpPostRequest(url, params, HTTP.UTF_8);
            ResponseHandler<byte[]> handler = new ResponseHandler<byte[]>() {
                public byte[] handleResponse(HttpResponse response) throws ClientProtocolException,
                        IOException {
                    HttpEntity entity = response.getEntity();
                    if (entity != null) {
                        return EntityUtils.toByteArray(entity);
                    } else {
                        return null;
                    }
                }
            };
            httpClient.execute(httpPost, handler);
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
    }

    private static HttpPost buildHttpPostRequest(String url, Map<String, String> params,
                                                 String encoding)
            throws UnsupportedEncodingException {
        HttpPost httpPost = new HttpPost(url);
        //Encode the form parameters
        if (!CollectionUtils.isEmpty(params)) {
            List<NameValuePair> nvps = Lists.newArrayList();
            Set<Entry<String, String>> paramEntrys = params.entrySet();
            for (Entry<String, String> entry : paramEntrys) {
                nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            httpPost.setEntity(new UrlEncodedFormEntity(nvps, encoding));
        }
        return httpPost;
    }

    //    private static void validateResponse(HttpResponse response, HttpGet get) throws IOException {
    //        StatusLine status = response.getStatusLine();
    //        if (status.getStatusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
    //            LOG.warn(
    //                    "Did not receive successful HTTP response: status code = {}, status message = {}",
    //                    status.getStatusCode(), status.getReasonPhrase());
    //            get.abort();
    //            return;
    //        }
    //    }

    private static void validateResponse(HttpResponse response, HttpPost post) throws IOException {
        StatusLine status = response.getStatusLine();
        if (status.getStatusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
            LOG.warn(
                    "Did not receive successful HTTP response: status code = {}, status message = {}",
                    status.getStatusCode(), status.getReasonPhrase());
            post.abort();
            return;
        }
    }

}


 

 

posted @ 2013-07-29 20:08  javawebsoa  Views(1611)  Comments(0Edit  收藏  举报