Index: src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnection.java =================================================================== --- src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnection.java (revision 504056) +++ src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnection.java (working copy) @@ -17,7 +17,6 @@ package org.apache.harmony.luni.internal.net.www.protocol.http; -import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -33,8 +32,6 @@ import java.net.Proxy; import java.net.ProxySelector; import java.net.ResponseCache; -import java.net.Socket; -import java.net.SocketAddress; import java.net.SocketPermission; import java.net.URI; import java.net.URISyntaxException; @@ -73,6 +70,8 @@ private final int defaultPort; private int httpVersion = 1; // Assume HTTP/1.1 + + protected HttpConnection connection; private InputStream is; @@ -78,8 +77,6 @@ private InputStream uis; - protected Socket socket; - private OutputStream socketOut; private OutputStream cacheOut; @@ -130,8 +127,12 @@ @Override public void close() throws IOException { - bytesRemaining = 0; - closeSocket(); + if(bytesRemaining > 0) { + bytesRemaining = 0; + disconnect(true); // Should close the socket if client hasn't read all the data + } else { + disconnect(false); + } /* * if user has set useCache to true and cache exists, aborts it when * closing @@ -220,7 +221,11 @@ @Override public void close() throws IOException { atEnd = true; - closeSocket(); + if(available() > 0) { + disconnect(true); + } else { + disconnect(false); + } // if user has set useCache to true and cache exists, abort if (useCaches && null != cacheRequest) { cacheRequest.abort(); @@ -428,6 +433,7 @@ } sendCache(closed); } + disconnect(false); } @Override @@ -585,7 +591,7 @@ return; } // socket to be used for connection - Socket socket = null; + connection = null; // try to determine: to use the proxy or not if (proxy != null) { // try to make the connection to the proxy @@ -591,7 +597,7 @@ // try to make the connection to the proxy // specified in constructor. // IOException will be thrown in the case of failure - socket = getHTTPConnection(proxy); + connection = getHTTPConnection(proxy); } else { // Use system-wide ProxySelect to select proxy list, // then try to connect via elements in the proxy list. @@ -604,7 +610,7 @@ continue; } try { - socket = getHTTPConnection(selectedProxy); + connection = getHTTPConnection(selectedProxy); proxy = selectedProxy; break; // connected } catch (IOException e) { @@ -614,12 +620,12 @@ } } } - if (socket == null) { + if (connection == null) { // make direct connection - socket = getHTTPConnection(null); + connection = getHTTPConnection(null); } - socket.setSoTimeout(getReadTimeout()); - setUpTransportIO(socket); + connection.setSoTimeout(getReadTimeout()); + setUpTransportIO(connection); connected = true; } @@ -624,42 +630,22 @@ } /** - * Returns connected socket to be used for this HTTP connection. TODO: - * implement persistent connections. + * Returns connected socket to be used for this HTTP connection. */ - protected Socket getHTTPConnection(Proxy proxy) throws IOException { - Socket socket; + protected HttpConnection getHTTPConnection(Proxy proxy) throws IOException { + HttpConnection connection; if (proxy == null || proxy.type() == Proxy.Type.DIRECT) { - this.proxy = null; // not using proxy - socket = new Socket(); - socket.connect(new InetSocketAddress(getHostName(), getHostPort()), + this.proxy = null; // not using proxy + connection = HttpConnectionManager.getDefault().getConnection(getHostName(), getHostPort(), getConnectTimeout()); } else if (proxy.type() == Proxy.Type.HTTP) { - socket = new Socket(); - - SocketAddress proxyAddr = proxy.address(); - - if (!(proxyAddr instanceof InetSocketAddress)) { - throw new IllegalArgumentException(Msg.getString( - "K0316", proxyAddr.getClass())); //$NON-NLS-1$ - } - - InetSocketAddress iProxyAddr = (InetSocketAddress) proxyAddr; - - if( iProxyAddr.getAddress() == null ) { - // Resolve proxy, see HARMONY-3113 - socket.connect(new InetSocketAddress((iProxyAddr.getHostName()), - iProxyAddr.getPort()), getConnectTimeout()); - } else { - socket.connect(iProxyAddr, getConnectTimeout()); - } + connection = HttpConnectionManager.getDefault().getConnection(proxy, getConnectTimeout()); } else { // using SOCKS proxy - socket = new Socket(proxy); - socket.connect(new InetSocketAddress(getHostName(), getHostPort()), - getConnectTimeout()); + connection = HttpConnectionManager.getDefault().getConnection(proxy, getHostName(), getHostPort(), + getConnectTimeout()); } - return socket; + return connection; } /** @@ -665,13 +651,12 @@ /** * Sets up the data streams used to send request[s] and read response[s]. * - * @param socket - * socket to be used for connection + * @param connection + * HttpConnection to be used */ - protected void setUpTransportIO(Socket socket) throws IOException { - this.socket = socket; - socketOut = socket.getOutputStream(); - is = new BufferedInputStream(socket.getInputStream()); + protected void setUpTransportIO(HttpConnection connection) throws IOException { + socketOut = connection.getOutputStream(); + is = connection.getInputStream(); } // Tries to get head and body from cache, return true if has got this time @@ -721,16 +706,18 @@ */ @Override public void disconnect() { - try { - closeSocket(); - } catch (IOException e) { - } + disconnect(true); } - - void closeSocket() throws IOException { - if (is != null) { - is.close(); - } + + private void disconnect(boolean closeSocket) { + if(closeSocket) { + connection.closeSocketAndStreams(); + } else { + HttpConnectionManager.getDefault().returnConnectionToPool(connection); + } + connection = null; + socketOut = null; + is = null; } protected void endRequest() throws IOException { @@ -862,7 +849,7 @@ @Override public InputStream getInputStream() throws IOException { - if (!doInput) { + if (!doInput) { throw new ProtocolException(Msg.getString("K008d")); //$NON-NLS-1$ } @@ -1067,7 +1054,7 @@ if (method == HEAD || (responseCode >= 100 && responseCode < 200) || responseCode == HTTP_NO_CONTENT || responseCode == HTTP_NOT_MODIFIED) { - closeSocket(); + disconnect(); uis = new LimitedInputStream(0); } putToCache(); @@ -1378,7 +1365,7 @@ // drop everything and reconnect, might not be required for // HTTP/1.1 endRequest(); - closeSocket(); + disconnect(); connected = false; String credentials = getAuthorizationCredentials(challenge); if (credentials == null) { @@ -1401,7 +1388,7 @@ // drop everything and reconnect, might not be required for // HTTP/1.1 endRequest(); - closeSocket(); + disconnect(); connected = false; String credentials = getAuthorizationCredentials(challenge); if (credentials == null) { @@ -1447,7 +1434,7 @@ hostPort = -1; } endRequest(); - closeSocket(); + disconnect(); connected = false; continue; } @@ -1464,7 +1451,7 @@ * authorization challenge * * @param challenge - * @return + * @return authorization credentials * @throws IOException */ private String getAuthorizationCredentials(String challenge) Index: src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConfiguration.java =================================================================== --- src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConfiguration.java +++ src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConfiguration.java @@ -0,0 +1,78 @@ +/* + * 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. + */ +package org.apache.harmony.luni.internal.net.www.protocol.http; + +import java.net.Proxy; + +public class HttpConfiguration { + + private Proxy proxy; + private int hostPort; + private String hostName; + + public HttpConfiguration(String hostName, int hostPort) { + this.hostName = hostName; + this.hostPort = hostPort; + } + + public HttpConfiguration(Proxy proxy) { + this.proxy = proxy; + } + + public HttpConfiguration(Proxy proxy, String hostName, int hostPort) { + this.proxy = proxy; + this.hostName = hostName; + this.hostPort = hostPort; + } + + public boolean usesProxy() { + return proxy != null; + } + + public Proxy getProxy() { + return proxy; + } + + public String getHostName() { + return hostName; + } + + public int getHostPort() { + return hostPort; + } + + @Override + public boolean equals(Object arg0) { + if(!(arg0 instanceof HttpConfiguration)) { + return false; + } else { + HttpConfiguration config = (HttpConfiguration)arg0; + return config.proxy == null ? proxy == null : config.proxy.equals(proxy) + && config.hostPort == hostPort + && config.hostName == null ? hostName == null : config.hostName.equals(hostName); + } + + } + + @Override + public int hashCode() { + return (((hostName != null) ? hostName.hashCode() : 0) * 37 + + hostPort) * 37 + + ((proxy != null) ? proxy.hashCode() : 0); + } + +} Index: src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnection.java =================================================================== --- src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnection.java +++ src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnection.java @@ -0,0 +1,199 @@ +/* + * 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. + */ +package org.apache.harmony.luni.internal.net.www.protocol.http; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.harmony.luni.internal.nls.Messages; +import org.apache.harmony.luni.util.Msg; + +public class HttpConnection { + + private boolean usingSecureSocket = false; + + private Socket socket; + private SSLSocket sslSocket; + + private InputStream inputStream; + private OutputStream outputStream; + private InputStream sslInputStream; + private OutputStream sslOutputStream; + + private HttpConfiguration config; + + public HttpConnection(HttpConfiguration config, int connectTimeout) throws IOException { + this.config = config; + String hostName = config.getHostName(); + int hostPort = config.getHostPort(); + if(config.usesProxy()) { + Proxy proxy = config.getProxy(); + if(hostName == null) { + socket = new Socket(); + SocketAddress proxyAddr = proxy.address(); + if (!(proxyAddr instanceof InetSocketAddress)) { + throw new IllegalArgumentException(Msg.getString( + "K0316", proxyAddr.getClass())); //$NON-NLS-1$ + } + InetSocketAddress iProxyAddr = (InetSocketAddress) proxyAddr; + + if( iProxyAddr.getAddress() == null ) { + // Resolve proxy, see HARMONY-3113 + socket.connect(new InetSocketAddress((iProxyAddr.getHostName()), + iProxyAddr.getPort()), connectTimeout); + } else { + socket.connect(iProxyAddr, connectTimeout); + } + } else { + socket = new Socket(proxy); + socket.connect(new InetSocketAddress(hostName, hostPort), connectTimeout); + } + } else { + socket = new Socket(); + socket.connect(new InetSocketAddress(hostName, hostPort), connectTimeout); + } + } + + public void closeSocketAndStreams() { + if(usingSecureSocket) { + if (null != sslOutputStream) { + OutputStream temp = sslOutputStream; + sslOutputStream = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + + if (null != sslInputStream) { + InputStream temp = sslInputStream; + sslInputStream = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + + if (null != sslSocket) { + Socket temp = sslSocket; + sslSocket = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + } + if (null != outputStream) { + OutputStream temp = outputStream; + outputStream = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + + if (null != inputStream) { + InputStream temp = inputStream; + inputStream = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + + if (null != socket) { + Socket temp = socket; + socket = null; + try { + temp.close(); + } catch (Exception ex) { + // ignored + } + } + + } + + public void setSoTimeout(int readTimeout) throws SocketException { + socket.setSoTimeout(readTimeout); + } + + public OutputStream getOutputStream() throws IOException { + if(usingSecureSocket) { + if (sslOutputStream == null) { + sslOutputStream = sslSocket.getOutputStream(); + } + return sslOutputStream; + } else if(outputStream == null) { + outputStream = socket.getOutputStream(); + } + return outputStream; + } + + public InputStream getInputStream() throws IOException { + if(usingSecureSocket) { + if (sslInputStream == null) { + sslInputStream = sslSocket.getInputStream(); + } + return sslInputStream; + } else if(inputStream == null) { + inputStream = socket.getInputStream(); + } + return inputStream; + } + + public HttpConfiguration getHttpConfiguration() { + return config; + } + + + public SSLSocket getSecureSocket(SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier) throws IOException { + if(!usingSecureSocket) { + String hostName = config.getHostName(); + int port = config.getHostPort(); + // create the wrapper over connected socket + sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, + hostName, port, true); + sslSocket.setUseClientMode(true); + sslSocket.startHandshake(); + if (!hostnameVerifier.verify(hostName, sslSocket.getSession())) { + throw new IOException(Messages.getString("luni.02", hostName)); //$NON-NLS-1$ + } + usingSecureSocket = true; + } + return sslSocket; + } + + Socket getSocket() { + return socket; + } + +} Index: src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnectionManager.java =================================================================== --- src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnectionManager.java +++ src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnectionManager.java @@ -0,0 +1,150 @@ +/* + * 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. + */ +package org.apache.harmony.luni.internal.net.www.protocol.http; + +import java.io.IOException; +import java.net.Proxy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class HttpConnectionManager { + + // The maximum number of connections to any location + private static int maxConnections = 5; + + // Keeps connections alive if true + private static boolean keepAlive = true; + + private static HttpConnectionManager defaultConnectionManager; + private ConnectionPool pool = new ConnectionPool(); + + public static HttpConnectionManager getDefault() { + if(defaultConnectionManager == null) { + defaultConnectionManager = new HttpConnectionManager(); + } + return defaultConnectionManager; + } + + public HttpConnection getConnection(String hostName, int hostPort, int connectTimeout) throws IOException { + checkSystemProperties(); + HttpConfiguration config = new HttpConfiguration(hostName, hostPort); + return pool.getHttpConnection(config, connectTimeout); + } + + public HttpConnection getConnection(Proxy proxy, int connectTimeout) throws IOException { + checkSystemProperties(); + HttpConfiguration config = new HttpConfiguration(proxy); + return pool.getHttpConnection(config, connectTimeout); + } + + public HttpConnection getConnection(Proxy proxy, String hostName, int hostPort, int connectTimeout) throws IOException { + checkSystemProperties(); + HttpConfiguration config = new HttpConfiguration(proxy, hostName, hostPort); + return pool.getHttpConnection(config, connectTimeout); + } + + public void returnConnectionToPool(HttpConnection connection) { + checkSystemProperties(); + pool.returnConnection(connection); + } + + private static class ConnectionPool { + + private Map> freeConnectionMap = new HashMap>(); // Map of free Sockets + + public synchronized void shutdown() { + for (Iterator> iter = freeConnectionMap.values().iterator(); iter.hasNext();) { + List connections = iter.next(); + for (Iterator iterator = connections.iterator(); iterator.hasNext();) { + HttpConnection connection = iterator.next(); + connection.closeSocketAndStreams(); + } + } + freeConnectionMap.clear(); + } + + public synchronized void returnConnection(HttpConnection connection) { + if(!connection.getSocket().isClosed() && keepAlive) { + HttpConfiguration config = connection.getHttpConfiguration(); + List connections = freeConnectionMap.get(config); + if(connections == null) { + connections = new ArrayList(); + freeConnectionMap.put(config, connections); + } + if(connections.size() < HttpConnectionManager.maxConnections) { + connections.add(connection); + } else { + connection.closeSocketAndStreams(); + } + } else { + // Make sure all streams are closed etc. + connection.closeSocketAndStreams(); + } + } + + public synchronized HttpConnection getHttpConnection(HttpConfiguration config, int connectTimeout) throws IOException { + List connections = freeConnectionMap.get(config); + if(keepAlive && connections == null) { + connections = new ArrayList(); + freeConnectionMap.put(config, connections); + } + if(!keepAlive || connections.isEmpty()) { + HttpConnection connection = new HttpConnection(config, connectTimeout); + return connection; + } else { + HttpConnection connection = connections.get(0); + connections.remove(0); + if(!connection.getSocket().isClosed()) { + return connection; + } else { + return getHttpConnection(config, connectTimeout); + } + } + } + + public int numFreeConnections() { + int numFree = 0; + for (Iterator> iter = freeConnectionMap.values().iterator(); iter.hasNext();) { + List connections = iter.next(); + numFree += connections.size(); + } + return numFree; + } + } + + public int numFreeConnections() { + return pool.numFreeConnections(); + } + + private void checkSystemProperties() { + String httpMaxConnections = System.getProperty("http.maxConnections"); + String httpKeepAlive = System.getProperty("http.keepAlive"); + if(httpMaxConnections != null) { + maxConnections = Integer.parseInt(httpMaxConnections); + } + if(httpKeepAlive != null) { + keepAlive = Boolean.parseBoolean(httpKeepAlive); + if(!keepAlive) { + pool.shutdown(); + } + } + } + +} Index: src/main/java/org/apache/harmony/luni/internal/net/www/protocol/https/HttpsURLConnection.java =================================================================== --- src/main/java/org/apache/harmony/luni/internal/net/www/protocol/https/HttpsURLConnection.java (revision 494409) +++ src/main/java/org/apache/harmony/luni/internal/net/www/protocol/https/HttpsURLConnection.java (working copy) @@ -22,7 +22,6 @@ import java.io.OutputStream; import java.net.ProtocolException; import java.net.Proxy; -import java.net.Socket; import java.net.URL; import java.security.Permission; import java.security.Principal; @@ -363,8 +362,8 @@ @Override public void connect() throws IOException { - if (connected) { - return; + if (connected) { + return; } if (usingProxy() && !makingSSLTunnel) { // SSL Tunnel through the proxy was not established yet, do so @@ -387,7 +386,7 @@ responseMessage, responseCode)); } // if there are some remaining data in the stream - read it out - InputStream is = socket.getInputStream(); + InputStream is = connection.getInputStream(); while (is.available() != 0) { is.read(); } @@ -397,7 +396,8 @@ super.connect(); } if (!makingSSLTunnel) { - setUpTransportIO(wrapConnection(socket)); + sslSocket = connection.getSecureSocket(getSSLSocketFactory(), getHostnameVerifier()); + setUpTransportIO(connection); } } @@ -420,21 +420,5 @@ return super.requestString(); } - /** - * Create the secure socket over the connected socket and verify remote - * hostname. - */ - private Socket wrapConnection(Socket socket) throws IOException { - String hostname = url.getHost(); - // create the wrapper over connected socket - sslSocket = (SSLSocket) getSSLSocketFactory().createSocket(socket, - hostname, url.getPort(), true); - sslSocket.setUseClientMode(true); - sslSocket.startHandshake(); - if (!getHostnameVerifier().verify(hostname, sslSocket.getSession())) { - throw new IOException(Messages.getString("luni.02", hostname)); //$NON-NLS-1$ - } - return sslSocket; - } } } Index: src/test/java/org/apache/harmony/tests/internal/net/www/protocol/http/HttpURLConnectionTest.java =================================================================== --- src/test/java/org/apache/harmony/tests/internal/net/www/protocol/http/HttpURLConnectionTest.java (revision 494409) +++ src/test/java/org/apache/harmony/tests/internal/net/www/protocol/http/HttpURLConnectionTest.java (working copy) @@ -18,9 +18,13 @@ package org.apache.harmony.tests.internal.net.www.protocol.http; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; import java.net.Authenticator; import java.net.HttpURLConnection; import java.net.InetSocketAddress; +import java.net.MalformedURLException; import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.ProxySelector; @@ -34,9 +38,12 @@ import junit.framework.TestCase; +import org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection; +import org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnectionManager; + /** - * Tests for HTTPURLConnection class constructors and methods. + * Tests for HttpURLConnection class constructors and methods. * */ public class HttpURLConnectionTest extends TestCase { @@ -299,5 +306,224 @@ Authenticator.setDefault(null); } } - + + /** + * Test that an HTTP connection persists + */ + public void testConnectionsPersist() throws IOException, InterruptedException { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + MockServer httpServer = + new MockServer("ServerSocket for HttpURLConnectionTest"); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + HttpURLConnection c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + c.getOutputStream().close(); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + OutputStream os = c.getOutputStream(); + assertEquals(initialFreeConnections, HttpConnectionManager.getDefault().numFreeConnections()); + os.close(); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + httpServer.join(); + } + + /** + * Test that multiple HTTP connections persist + */ + public void testMultipleConnectionsPersist() throws IOException, InterruptedException { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + MockServer httpServer = + new MockServer("ServerSocket for HttpURLConnectionTest"); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + MockServer httpServer2 = + new MockServer("ServerSocket for HttpURLConnectionTest"); + httpServer2.start(); + synchronized(bound) { + if (!httpServer2.started) { + bound.wait(5000); + } + } + HttpURLConnection c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + OutputStream os = c.getOutputStream(); + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer2.port()).openConnection(); + c2.setDoOutput(true); + c2.setRequestMethod("POST"); + OutputStream os2 = c2.getOutputStream(); + os.close(); + os2.close(); + assertEquals(initialFreeConnections + 2, HttpConnectionManager.getDefault().numFreeConnections()); + + c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + os = c.getOutputStream(); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + c2 = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer2.port()).openConnection(); + c2.setDoOutput(true); + c2.setRequestMethod("POST"); + os2 = c2.getOutputStream(); + assertEquals(initialFreeConnections, HttpConnectionManager.getDefault().numFreeConnections()); + os.close(); + os2.close(); + assertEquals(initialFreeConnections + 2, HttpConnectionManager.getDefault().numFreeConnections()); + httpServer.join(); + httpServer2.join(); + } + + /** + * Test that a closed HTTP connection is not kept in the pool of live connections + */ + public void testForcedClosure() throws IOException, InterruptedException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + MockServer httpServer = + new MockServer("ServerSocket for HttpURLConnectionTest"); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + HttpConnection connection = HttpConnectionManager.getDefault().getConnection("127.0.0.1", httpServer.port(), 1000); + HttpConnectionManager.getDefault().returnConnectionToPool(connection); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + HttpURLConnection c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + c.getOutputStream(); + assertEquals(initialFreeConnections, HttpConnectionManager.getDefault().numFreeConnections()); + c.disconnect(); + assertEquals(initialFreeConnections, HttpConnectionManager.getDefault().numFreeConnections()); + } + + /** + * Test that a connection is closed if the client does not read all the data + */ + public void testIncorrectUsage() throws MalformedURLException, IOException { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + HttpURLConnection c = (HttpURLConnection) + new URL("http://www.google.com").openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is = c.getInputStream(); // get the input stream but don't finish reading it + is.close(); + assertEquals(initialFreeConnections, HttpConnectionManager.getDefault().numFreeConnections()); + } + + /** + * Test that a connection is not closed if the client does read all the data + * @throws IOException + * @throws MalformedURLException + */ + public void testCorrectUsage() throws MalformedURLException, IOException { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + HttpURLConnection c = (HttpURLConnection) + new URL("http://www.google.com").openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is = c.getInputStream(); + byte[] buffer = new byte[128]; + int totalBytes = 0; + int bytesRead = 0; + while((bytesRead = is.read(buffer)) > 0){ + totalBytes += bytesRead; + } + is.close(); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://www.google.com").openConnection(); + c2.setDoOutput(true); + c2.setRequestMethod("GET"); + InputStream is2 = c2.getInputStream(); + byte[] buffer2 = new byte[128]; + int totalBytes2 = 0; + int bytesRead2 = 0; + while((bytesRead2 = is2.read(buffer2)) > 0){ + totalBytes2 += bytesRead2; + } + is2.close(); + assertEquals(initialFreeConnections + 1, HttpConnectionManager.getDefault().numFreeConnections()); + assertEquals(totalBytes, totalBytes2); + } + + /** + * Test that the http.keepAlive system property has the required effect on persistent connections + */ + public void testKeepAliveSystemProperty() throws IOException, InterruptedException { + System.setProperty("http.keepAlive", "false"); + MockServer httpServer = + new MockServer("ServerSocket for HttpURLConnectionTest"); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + HttpURLConnection c = (HttpURLConnection) + new URL("http://127.0.0.1:" + httpServer.port()).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("POST"); + OutputStream os = c.getOutputStream(); + os.close(); + assertEquals(0, HttpConnectionManager.getDefault().numFreeConnections()); + httpServer.join(); + System.setProperty("http.keepAlive", "true"); + } + + /** + * Test that the http.maxConnections system property has the required effect on persistent connections + */ + public void testMaxConnectionsSystemProperty() throws MalformedURLException, IOException { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + System.setProperty("http.maxConnections", "2"); + HttpURLConnection c = (HttpURLConnection) + new URL("http://www.google.com").openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is = c.getInputStream(); + byte[] buffer = new byte[128]; + while(is.read(buffer) > 0){ + } + c = (HttpURLConnection) + new URL("http://www.google.com").openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is2 = c.getInputStream(); + while(is2.read(buffer) > 0){ + } + c = (HttpURLConnection) + new URL("http://www.google.com").openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is3 = c.getInputStream(); + while(is.read(buffer) > 0){ + } + is.close(); + is2.close(); + is3.close(); + assertEquals(initialFreeConnections + 2, HttpConnectionManager.getDefault().numFreeConnections()); + } + }