hc-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ol...@apache.org
Subject svn commit: r1546855 - in /httpcomponents/httpclient/trunk: ./ httpclient-cache/ httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ httpclient-cache/src/test/java/org/apache/http/impl/client/cache/
Date Sun, 01 Dec 2013 20:33:37 GMT
Author: olegk
Date: Sun Dec  1 20:33:37 2013
New Revision: 1546855

URL: http://svn.apache.org/r1546855
Log:
HTTPCLIENT-1441: Caching AsynchronousValidationRequest leaks connections.
Contributed by Dominic Tootell <dominic.tootell at gmail.com>

Added:
    httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestStaleWhileRevalidationReleasesConnection.java
  (with props)
Modified:
    httpcomponents/httpclient/trunk/RELEASE_NOTES.txt
    httpcomponents/httpclient/trunk/httpclient-cache/pom.xml
    httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java

Modified: httpcomponents/httpclient/trunk/RELEASE_NOTES.txt
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/RELEASE_NOTES.txt?rev=1546855&r1=1546854&r2=1546855&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/RELEASE_NOTES.txt (original)
+++ httpcomponents/httpclient/trunk/RELEASE_NOTES.txt Sun Dec  1 20:33:37 2013
@@ -1,6 +1,9 @@
 Changes since 4.3.1
 -------------------
 
+* [HTTPCLIENT-1441] Caching AsynchronousValidationRequest leaks connections.
+  Contributed by Dominic Tootell <dominic.tootell at gmail.com>
+
 * [HTTPCLIENT-1440] 'file' scheme in redirect location URI causes NPE.
   Contributed by James Leigh <james at 3roundstones dot com>
 

Modified: httpcomponents/httpclient/trunk/httpclient-cache/pom.xml
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/pom.xml?rev=1546855&r1=1546854&r2=1546855&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/pom.xml (original)
+++ httpcomponents/httpclient/trunk/httpclient-cache/pom.xml Sun Dec  1 20:33:37 2013
@@ -84,6 +84,13 @@
       <artifactId>easymockclassextension</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>

Modified: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java?rev=1546855&r1=1546854&r2=1546855&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java
(original)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java
Sun Dec  1 20:33:37 2013
@@ -37,6 +37,7 @@ import org.apache.http.client.cache.Head
 import org.apache.http.client.cache.HttpCacheEntry;
 import org.apache.http.client.methods.HttpExecutionAware;
 import org.apache.http.client.methods.HttpRequestWrapper;
+import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.protocol.HttpClientContext;
 import org.apache.http.conn.routing.HttpRoute;
 
@@ -108,9 +109,13 @@ class AsynchronousValidationRequest impl
      */
     protected boolean revalidateCacheEntry() {
         try {
-            final HttpResponse httpResponse = cachingExec.revalidateCacheEntry(route, request,
context, execAware, cacheEntry);
-            final int statusCode = httpResponse.getStatusLine().getStatusCode();
-            return isNotServerError(statusCode) && isNotStale(httpResponse);
+            final CloseableHttpResponse httpResponse = cachingExec.revalidateCacheEntry(route,
request, context, execAware, cacheEntry);
+            try {
+                final int statusCode = httpResponse.getStatusLine().getStatusCode();
+                return isNotServerError(statusCode) && isNotStale(httpResponse);
+            } finally {
+                httpResponse.close();
+            }
         } catch (final IOException ioe) {
             log.debug("Asynchronous revalidation failed due to I/O error", ioe);
             return false;

Added: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestStaleWhileRevalidationReleasesConnection.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestStaleWhileRevalidationReleasesConnection.java?rev=1546855&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestStaleWhileRevalidationReleasesConnection.java
(added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestStaleWhileRevalidationReleasesConnection.java
Sun Dec  1 20:33:37 2013
@@ -0,0 +1,307 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.MethodNotSupportedException;
+import org.apache.http.Header;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.cache.CacheResponseStatus;
+import org.apache.http.client.cache.HttpCacheContext;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.apache.http.util.EntityUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Locale;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import org.apache.http.localserver.LocalTestServer;
+
+/**
+ * Test that after background validation that a subsequent request for non cached
+ * conent can be made.  This verifies that the connection has been release back to
+ * the pool by the AsynchronousValidationRequest.
+ */
+public class TestStaleWhileRevalidationReleasesConnection {
+
+    private static final EchoViaHeaderHandler cacheHandler = new EchoViaHeaderHandler();
+
+    protected LocalTestServer localServer;
+    private int port;
+    private CloseableHttpClient client;
+    private final String url = "/static/dom";
+    private final String url2 = "2";
+
+
+    @Before
+    public void start() throws Exception  {
+        this.localServer = new LocalTestServer(null, null);
+        this.localServer.register(url +"*", new EchoViaHeaderHandler());
+        localServer.setTimeout(5000);
+        this.localServer.start();
+
+        port = this.localServer.getServiceAddress().getPort();
+
+        final CacheConfig cacheConfig = CacheConfig.custom()
+                .setMaxCacheEntries(100)
+                .setMaxObjectSize(15) //1574
+                .setAsynchronousWorkerIdleLifetimeSecs(60)
+                .setAsynchronousWorkersMax(1)
+                .setAsynchronousWorkersCore(1)
+                .setRevalidationQueueSize(100)
+                .setSharedCache(true)
+                .build();
+
+        final HttpClientBuilder clientBuilder = CachingHttpClientBuilder.create().setCacheConfig(cacheConfig);
+        clientBuilder.setMaxConnTotal(1);
+        clientBuilder.setMaxConnPerRoute(1);
+
+        final RequestConfig config = RequestConfig.custom()
+                .setSocketTimeout(10000)
+                .setConnectTimeout(10000)
+                .setConnectionRequestTimeout(1000)
+                .build();
+
+        clientBuilder.setDefaultRequestConfig(config);
+
+
+        client = clientBuilder.build();
+    }
+
+    @After
+    public void stop() {
+        if (this.localServer != null) {
+            try {
+                this.localServer.stop();
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+        try {
+            client.close();
+        } catch(IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void testStaleWhileRevalidate() {
+        final String urlToCall = "http://localhost:"+port + url;
+        final HttpContext localContext = new BasicHttpContext();
+        Exception requestException = null;
+
+        // This will fetch from backend.
+        requestException = sendRequest(client, localContext,urlToCall,null);
+        assertNull(requestException);
+
+        CacheResponseStatus responseStatus = (CacheResponseStatus) localContext.getAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS);
+        assertEquals(CacheResponseStatus.CACHE_MISS,responseStatus);
+
+        try {
+            Thread.sleep(1000);
+        } catch (Exception e) {
+
+        }
+        // These will be cached
+        requestException = sendRequest(client, localContext,urlToCall,null);
+        assertNull(requestException);
+
+        responseStatus = (CacheResponseStatus) localContext.getAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS);
+        assertEquals(CacheResponseStatus.CACHE_HIT,responseStatus);
+
+        requestException = sendRequest(client, localContext,urlToCall,null);
+        assertNull(requestException);
+
+        responseStatus = (CacheResponseStatus) localContext.getAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS);
+        assertEquals(CacheResponseStatus.CACHE_HIT,responseStatus);
+
+        // wait, so that max-age is expired
+        try {
+            Thread.sleep(4000);
+        } catch (Exception e) {
+
+        }
+
+        // This will cause a revalidation to occur
+        requestException = sendRequest(client, localContext,urlToCall,"This is new content
that is bigger than cache limit");
+        assertNull(requestException);
+
+        responseStatus = (CacheResponseStatus) localContext.getAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS);
+        assertEquals(CacheResponseStatus.CACHE_HIT,responseStatus);
+
+        try {
+            Thread.sleep(1000);
+        } catch (Exception e) {
+
+        }
+
+        // fetch a different content This will hang due to connection leak in revalidation
+        requestException = sendRequest(client, localContext,urlToCall+url2,null);
+        if(requestException!=null) {
+            requestException.printStackTrace();
+        }
+        assertNull(requestException);
+
+
+    }
+
+    static Exception sendRequest(final HttpClient cachingClient, final HttpContext localContext
, final String url, final String content) {
+        final HttpGet httpget = new HttpGet(url);
+        if(content!=null) {
+            httpget.setHeader(cacheHandler.getUserContentHeader(),content);
+        }
+
+        HttpResponse response = null;
+        try {
+            response = cachingClient.execute(httpget, localContext);
+            return null;
+        } catch (ClientProtocolException e1) {
+            return e1;
+        } catch (IOException e1) {
+            return e1;
+        } finally {
+            if(response!=null) {
+                final HttpEntity entity = response.getEntity();
+                try {
+                    EntityUtils.consume(entity);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    public static class EchoViaHeaderHandler
+            implements HttpRequestHandler {
+
+        private final String CACHE_CONTROL_HEADER = "Cache-Control";
+
+        private final byte[] DEFAULT_CONTENT;
+        private final String DEFAULT_CLIENT_CONTROLLED_CONTENT_HEADER;
+        private final String DEFAULT_RESPONSE_CACHE_HEADER;
+
+        // public default constructor
+        public EchoViaHeaderHandler() {
+            this("ECHO-CONTENT","abc".getBytes(), "public, max-age=3, stale-while-revalidate=5");
+        }
+
+        public EchoViaHeaderHandler(final String contentHeader,final byte[] content,
+                                    final String defaultResponseCacheHeader) {
+            DEFAULT_CLIENT_CONTROLLED_CONTENT_HEADER = contentHeader;
+            DEFAULT_CONTENT = content;
+            DEFAULT_RESPONSE_CACHE_HEADER = defaultResponseCacheHeader;
+        }
+
+
+        /**
+         * Return the header the user can set the content that will be returned by the server
+         * @return
+         */
+        public String getUserContentHeader() {
+            return DEFAULT_CLIENT_CONTROLLED_CONTENT_HEADER;
+        }
+
+        /**
+         * Handles a request by echoing the incoming request entity.
+         * If there is no request entity, an empty document is returned.
+         *
+         * @param request   the request
+         * @param response  the response
+         * @param context   the context
+         *
+         * @throws org.apache.http.HttpException    in case of a problem
+         * @throws java.io.IOException      in case of an IO problem
+         */
+        public void handle(final HttpRequest request,
+                           final HttpResponse response,
+                           final HttpContext context)
+                throws HttpException, IOException {
+
+            final String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH);
+            if (!"GET".equals(method) &&
+                    !"POST".equals(method) &&
+                    !"PUT".equals(method)
+                    ) {
+                throw new MethodNotSupportedException
+                        (method + " not supported by " + getClass().getName());
+            }
+
+            response.setStatusCode(org.apache.http.HttpStatus.SC_OK);
+            response.addHeader("Cache-Control",getCacheContent(request));
+            final byte[] content = getHeaderContent(request);
+            final ByteArrayEntity bae = new ByteArrayEntity(content);
+            response.setHeader("Connection","keep-alive");
+
+            response.setEntity(bae);
+
+        } // handle
+
+
+        public byte[] getHeaderContent(final HttpRequest request) {
+            final Header contentHeader = request.getFirstHeader(DEFAULT_CLIENT_CONTROLLED_CONTENT_HEADER);
+            if(contentHeader!=null) {
+                try {
+                    return contentHeader.getValue().getBytes("UTF-8");
+                } catch(UnsupportedEncodingException e) {
+                    return contentHeader.getValue().getBytes();
+                }
+            } else {
+                return DEFAULT_CONTENT;
+            }
+        }
+
+        public String getCacheContent(final HttpRequest request) {
+            final Header contentHeader = request.getFirstHeader(CACHE_CONTROL_HEADER);
+            if(contentHeader!=null) {
+                return contentHeader.getValue();
+            } else {
+                return DEFAULT_RESPONSE_CACHE_HEADER;
+            }
+        }
+
+    } // class EchoHandler
+
+  }
+

Propchange: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestStaleWhileRevalidationReleasesConnection.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestStaleWhileRevalidationReleasesConnection.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestStaleWhileRevalidationReleasesConnection.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Mime
View raw message