Using HttpClient properly to avoid CLOSE_WAIT TCP connections

         Apache的HttpComponent(这里是基于 version 4.1)组件,用的人不在少数。但是能用好的人,却微乎其微,为什么?很简单,TCP/IP协议里面的细节太多了(细节是魔鬼),像并发请求控制&资源释放,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;
        }
    }

}
时间: 2025-01-20 07:53:23

Using HttpClient properly to avoid CLOSE_WAIT TCP connections的相关文章

从问题看本质: 研究TCP close_wait的内幕

/* * @author: ahuaxuan * @date: 2010-4-30 */ 最近遇到的一个关于socket.close的问题,在某个应用服务器出现的状况(执行netstat -np | grep tcp): tcp 0 0 10.224.122.16:50158 10.224.112.58:8788 CLOSE_WAIT tcp 0 0 10.224.122.16:37655 10.224.112.58:8788 CLOSE_WAIT tcp 1 0 127.0.0.1:32713

TCP之close_wait

TCP之close_wait 浏览:3697次  出处信息 /*  * @author: ahuaxuan  * @date: 2010-4-30  */  查看各状态连接数: netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 最近遇到的一个关于socket.close的问题,在某个应用服务器出现的状况(执行netstat -np | grep tcp):  tcp        0      0 10.22

使用httpclient抓取时,netstat 发现很多time_wait连接

http://wiki.apache.org/HttpComponents/FrequentlyAskedConnectionManagementQuestions 1. Connections in TIME_WAIT State After running your HTTP application, you use the netstat command and detect a lot of connections in stateTIME_WAIT. Now you wonder wh

TCP洪水攻击(SYN Flood)的诊断和处理

TCP洪水攻击(SYN Flood)的诊断和处理 Posted by  海涛  on 2013 年 7 月 11 日 Tweet1 ​1. SYN Flood介绍 前段时间网站被攻击多次,其中最猛烈的就是TCP洪水攻击,即SYN Flood. SYN Flood是当前最流行的DoS(拒绝服务攻击)与DDoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,常用假冒的IP或IP号段发来海量的请求连接的第一个握手包(SYN包),被攻击服务器回应第二个握手包(

03-PubSubHubbub 和 twisted 的 Persistent connections 能力 | 07.杂项 | Python

03-PubSubHubbub 和 twisted 的 Persistent connections 能力 郑昀 201005 隶属于<07.杂项> 关于上节<02-Twisted 构建 Web Server 的 Socket 长链接问题>,还可以继续探讨为何会保持 Socket 长链接. 该关闭的连接没关闭? 有人在twisted邮件列表中也反映:  『We close the render_POST with a request.write('data') & a re

C#中HttpClient使用注意:预热与长连接

原文:C#中HttpClient使用注意:预热与长连接 最近在测试一个第三方API,准备集成在我们的网站应用中.API的调用使用的是.NET中的HttpClient,由于这个API会在关键业务中用到,对调用API的整体响应速度有严格要求,所以对HttpClient有了格外的关注. 开始测试的时候,只在客户端通过HttpClient用PostAsync发了一个http post请求.测试时发现,从创建HttpClient实例,到发出请求,到读取到服务器的响应数据总耗时在2s左右,而且多次测试都是这

some utility discovered by Linux yum search all tcp, epel.repo

在使用Linux epel repo时,查找tcp发现了很多好玩的工具: # cat /etc/yum.repos.d/epel.repo [epel] name=Extra Packages for Enterprise Linux 6 - $basearch baseurl=http://mirrors.aliyun.com/epel/6/$basearch http://mirrors.aliyuncs.com/epel/6/$basearch #mirrorlist=https://mi

GFS - The Google File System

The Google File System http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.125.789&rep=rep1&type=pdf http://www.dbthink.com/?p=501, 中文翻译   Google牛人云集的地方, 但在设计系统时, 却非常务实, 没有采用什么复杂和时髦的算法和机制  设计大型系统, 最重要的就是, 简单和可靠, 复杂就意味着失控... 在设计GFS, 首先一个选择就是,

【新】netstat命令详解

说明: netstat命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Memberships) 等等. 语法: -a或--all 显示所有连线中的Socket. -A<网络类型>或--<网络类型> 列出该网络类型连线中的相关地址. -c或--continuous 持续列出网络状态. -C或--cache 显示路由器配置的快取信息. -e或--extend 显示网络其