/* * ==================================================================== * 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.ogt.http.conn; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import org.apache.ogt.http.Header; import org.apache.ogt.http.HttpException; import org.apache.ogt.http.HttpHost; import org.apache.ogt.http.HttpResponse; import org.apache.ogt.http.HttpResponseInterceptor; import org.apache.ogt.http.HttpVersion; import org.apache.ogt.http.client.HttpClient; import org.apache.ogt.http.client.methods.HttpGet; import org.apache.ogt.http.conn.scheme.PlainSocketFactory; import org.apache.ogt.http.conn.scheme.Scheme; import org.apache.ogt.http.conn.scheme.SchemeRegistry; import org.apache.ogt.http.conn.scheme.SchemeSocketFactory; import org.apache.ogt.http.impl.client.DefaultHttpClient; import org.apache.ogt.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.ogt.http.localserver.LocalTestServer; import org.apache.ogt.http.localserver.RandomHandler; import org.apache.ogt.http.params.BasicHttpParams; import org.apache.ogt.http.params.HttpConnectionParams; import org.apache.ogt.http.params.HttpParams; import org.apache.ogt.http.params.HttpProtocolParams; import org.apache.ogt.http.protocol.BasicHttpProcessor; import org.apache.ogt.http.protocol.HTTP; import org.apache.ogt.http.protocol.HttpContext; import org.apache.ogt.http.protocol.ResponseConnControl; import org.apache.ogt.http.protocol.ResponseContent; import org.apache.ogt.http.protocol.ResponseDate; import org.apache.ogt.http.protocol.ResponseServer; import org.apache.ogt.http.util.EntityUtils; import org.junit.After; import org.junit.Assert; import org.junit.Test; public class TestConnectionReuse { protected LocalTestServer localServer; @After public void tearDown() throws Exception { if (this.localServer != null) { this.localServer.stop(); } } @Test public void testReuseOfPersistentConnections() throws Exception { BasicHttpProcessor httpproc = new BasicHttpProcessor(); httpproc.addInterceptor(new ResponseDate()); httpproc.addInterceptor(new ResponseServer()); httpproc.addInterceptor(new ResponseContent()); httpproc.addInterceptor(new ResponseConnControl()); this.localServer = new LocalTestServer(httpproc, null); this.localServer.register("/random/*", new RandomHandler()); this.localServer.start(); InetSocketAddress saddress = this.localServer.getServiceAddress(); HttpParams params = new BasicHttpParams(); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, "UTF-8"); HttpProtocolParams.setUserAgent(params, "TestAgent/1.1"); HttpProtocolParams.setUseExpectContinue(params, false); HttpConnectionParams.setStaleCheckingEnabled(params, false); SchemeRegistry supportedSchemes = new SchemeRegistry(); SchemeSocketFactory sf = PlainSocketFactory.getSocketFactory(); supportedSchemes.register(new Scheme("http", 80, sf)); ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(supportedSchemes); mgr.setMaxTotal(5); mgr.setDefaultMaxPerRoute(5); DefaultHttpClient client = new DefaultHttpClient(mgr, params); HttpHost target = new HttpHost(saddress.getHostName(), saddress.getPort(), "http"); WorkerThread[] workers = new WorkerThread[10]; for (int i = 0; i < workers.length; i++) { workers[i] = new WorkerThread( client, target, new URI("/random/2000"), 10, false); } for (int i = 0; i < workers.length; i++) { WorkerThread worker = workers[i]; worker.start(); } for (int i = 0; i < workers.length; i++) { WorkerThread worker = workers[i]; workers[i].join(10000); Exception ex = worker.getException(); if (ex != null) { throw ex; } } // Expect some connection in the pool Assert.assertTrue(mgr.getConnectionsInPool() > 0); mgr.shutdown(); } private static class AlwaysCloseConn implements HttpResponseInterceptor { public void process( final HttpResponse response, final HttpContext context) throws HttpException, IOException { response.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE); } } @Test public void testReuseOfClosedConnections() throws Exception { BasicHttpProcessor httpproc = new BasicHttpProcessor(); httpproc.addInterceptor(new ResponseDate()); httpproc.addInterceptor(new ResponseServer()); httpproc.addInterceptor(new ResponseContent()); httpproc.addInterceptor(new AlwaysCloseConn()); this.localServer = new LocalTestServer(httpproc, null); this.localServer.register("/random/*", new RandomHandler()); this.localServer.start(); InetSocketAddress saddress = this.localServer.getServiceAddress(); HttpParams params = new BasicHttpParams(); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, "UTF-8"); HttpProtocolParams.setUserAgent(params, "TestAgent/1.1"); HttpProtocolParams.setUseExpectContinue(params, false); HttpConnectionParams.setStaleCheckingEnabled(params, false); SchemeRegistry supportedSchemes = new SchemeRegistry(); SchemeSocketFactory sf = PlainSocketFactory.getSocketFactory(); supportedSchemes.register(new Scheme("http", 80, sf)); ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(supportedSchemes); mgr.setMaxTotal(5); mgr.setDefaultMaxPerRoute(5); DefaultHttpClient client = new DefaultHttpClient(mgr, params); HttpHost target = new HttpHost(saddress.getHostName(), saddress.getPort(), "http"); WorkerThread[] workers = new WorkerThread[10]; for (int i = 0; i < workers.length; i++) { workers[i] = new WorkerThread( client, target, new URI("/random/2000"), 10, false); } for (int i = 0; i < workers.length; i++) { WorkerThread worker = workers[i]; worker.start(); } for (int i = 0; i < workers.length; i++) { WorkerThread worker = workers[i]; workers[i].join(10000); Exception ex = worker.getException(); if (ex != null) { throw ex; } } // Expect zero connections in the pool Assert.assertEquals(0, mgr.getConnectionsInPool()); mgr.shutdown(); } @Test public void testReuseOfAbortedConnections() throws Exception { BasicHttpProcessor httpproc = new BasicHttpProcessor(); httpproc.addInterceptor(new ResponseDate()); httpproc.addInterceptor(new ResponseServer()); httpproc.addInterceptor(new ResponseContent()); httpproc.addInterceptor(new ResponseConnControl()); this.localServer = new LocalTestServer(httpproc, null); this.localServer.register("/random/*", new RandomHandler()); this.localServer.start(); InetSocketAddress saddress = this.localServer.getServiceAddress(); HttpParams params = new BasicHttpParams(); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, "UTF-8"); HttpProtocolParams.setUserAgent(params, "TestAgent/1.1"); HttpProtocolParams.setUseExpectContinue(params, false); HttpConnectionParams.setStaleCheckingEnabled(params, false); SchemeRegistry supportedSchemes = new SchemeRegistry(); SchemeSocketFactory sf = PlainSocketFactory.getSocketFactory(); supportedSchemes.register(new Scheme("http", 80, sf)); ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(supportedSchemes); mgr.setMaxTotal(5); mgr.setDefaultMaxPerRoute(5); DefaultHttpClient client = new DefaultHttpClient(mgr, params); HttpHost target = new HttpHost(saddress.getHostName(), saddress.getPort(), "http"); WorkerThread[] workers = new WorkerThread[10]; for (int i = 0; i < workers.length; i++) { workers[i] = new WorkerThread( client, target, new URI("/random/2000"), 10, true); } for (int i = 0; i < workers.length; i++) { WorkerThread worker = workers[i]; worker.start(); } for (int i = 0; i < workers.length; i++) { WorkerThread worker = workers[i]; workers[i].join(10000); Exception ex = worker.getException(); if (ex != null) { throw ex; } } // Expect zero connections in the pool Assert.assertEquals(0, mgr.getConnectionsInPool()); mgr.shutdown(); } @Test public void testKeepAliveHeaderRespected() throws Exception { BasicHttpProcessor httpproc = new BasicHttpProcessor(); httpproc.addInterceptor(new ResponseDate()); httpproc.addInterceptor(new ResponseServer()); httpproc.addInterceptor(new ResponseContent()); httpproc.addInterceptor(new ResponseConnControl()); httpproc.addInterceptor(new ResponseKeepAlive()); this.localServer = new LocalTestServer(httpproc, null); this.localServer.register("/random/*", new RandomHandler()); this.localServer.start(); InetSocketAddress saddress = this.localServer.getServiceAddress(); HttpParams params = new BasicHttpParams(); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, "UTF-8"); HttpProtocolParams.setUserAgent(params, "TestAgent/1.1"); HttpProtocolParams.setUseExpectContinue(params, false); HttpConnectionParams.setStaleCheckingEnabled(params, false); SchemeRegistry supportedSchemes = new SchemeRegistry(); SchemeSocketFactory sf = PlainSocketFactory.getSocketFactory(); supportedSchemes.register(new Scheme("http", 80, sf)); ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(supportedSchemes); mgr.setMaxTotal(1); mgr.setDefaultMaxPerRoute(1); DefaultHttpClient client = new DefaultHttpClient(mgr, params); HttpHost target = new HttpHost(saddress.getHostName(), saddress.getPort(), "http"); HttpResponse response = client.execute(target, new HttpGet("/random/2000")); EntityUtils.consume(response.getEntity()); Assert.assertEquals(1, mgr.getConnectionsInPool()); Assert.assertEquals(1, localServer.getAcceptedConnectionCount()); response = client.execute(target, new HttpGet("/random/2000")); EntityUtils.consume(response.getEntity()); Assert.assertEquals(1, mgr.getConnectionsInPool()); Assert.assertEquals(1, localServer.getAcceptedConnectionCount()); // Now sleep for 1.1 seconds and let the timeout do its work Thread.sleep(1100); response = client.execute(target, new HttpGet("/random/2000")); EntityUtils.consume(response.getEntity()); Assert.assertEquals(1, mgr.getConnectionsInPool()); Assert.assertEquals(2, localServer.getAcceptedConnectionCount()); // Do another request just under the 1 second limit & make // sure we reuse that connection. Thread.sleep(500); response = client.execute(target, new HttpGet("/random/2000")); EntityUtils.consume(response.getEntity()); Assert.assertEquals(1, mgr.getConnectionsInPool()); Assert.assertEquals(2, localServer.getAcceptedConnectionCount()); mgr.shutdown(); } private static class WorkerThread extends Thread { private final URI requestURI; private final HttpHost target; private final HttpClient httpclient; private final int repetitions; private final boolean forceClose; private volatile Exception exception; public WorkerThread( final HttpClient httpclient, final HttpHost target, final URI requestURI, int repetitions, boolean forceClose) { super(); this.httpclient = httpclient; this.requestURI = requestURI; this.target = target; this.repetitions = repetitions; this.forceClose = forceClose; } @Override public void run() { try { for (int i = 0; i < this.repetitions; i++) { HttpGet httpget = new HttpGet(this.requestURI); HttpResponse response = this.httpclient.execute( this.target, httpget); if (this.forceClose) { httpget.abort(); } else { EntityUtils.consume(response.getEntity()); } } } catch (Exception ex) { this.exception = ex; } } public Exception getException() { return exception; } } // A very basic keep-alive header interceptor, to add Keep-Alive: timeout=1 // if there is no Connection: close header. private static class ResponseKeepAlive implements HttpResponseInterceptor { public void process(HttpResponse response, HttpContext context) throws HttpException, IOException { Header connection = response.getFirstHeader(HTTP.CONN_DIRECTIVE); if(connection != null) { if(!connection.getValue().equalsIgnoreCase("Close")) { response.addHeader(HTTP.CONN_KEEP_ALIVE, "timeout=1"); } } } } }