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 @@ -153,6 +154,7 @@ @Override public int read() throws IOException { if (bytesRemaining <= 0) { + disconnect(false); return -1; } int result = is.read(); @@ -162,6 +164,9 @@ cacheOut.write(result); } bytesRemaining--; + if (bytesRemaining <= 0) { + disconnect(false); + } return result; } @@ -176,6 +181,7 @@ throw new ArrayIndexOutOfBoundsException(); } if (bytesRemaining <= 0) { + disconnect(false); return -1; } if (length > bytesRemaining) { @@ -190,6 +196,9 @@ cacheOut.write(buf, offset, result); } } + if (bytesRemaining <= 0) { + disconnect(false); + } return result; } @@ -195,6 +204,7 @@ public long skip(int amount) throws IOException { if (bytesRemaining <= 0) { + disconnect(false); return -1; } if (amount > bytesRemaining) { @@ -204,6 +214,9 @@ if (result > 0) { bytesRemaining -= result; } + if (bytesRemaining <= 0) { + disconnect(false); + } return result; } } @@ -219,8 +232,12 @@ @Override public void close() throws IOException { + if(!atEnd && available() > 0) { + disconnect(true); + } else { + disconnect(false); + } atEnd = true; - closeSocket(); // if user has set useCache to true and cache exists, abort if (useCaches && null != cacheRequest) { cacheRequest.abort(); @@ -261,6 +278,7 @@ readChunkSize(); } if (atEnd) { + disconnect(false); return -1; } bytesRemaining--; @@ -286,6 +304,7 @@ readChunkSize(); } if (atEnd) { + disconnect(false); return -1; } if (length > bytesRemaining) { @@ -305,6 +324,7 @@ public long skip(int amount) throws IOException { if (atEnd) { + disconnect(false); return -1; } if (bytesRemaining <= 0) { @@ -428,6 +448,7 @@ } sendCache(closed); } + disconnect(false); } @Override @@ -585,7 +606,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 +612,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 +625,7 @@ continue; } try { - socket = getHTTPConnection(selectedProxy); + connection = getHTTPConnection(selectedProxy); proxy = selectedProxy; break; // connected } catch (IOException e) { @@ -614,12 +635,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 +645,17 @@ } /** - * 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()), - 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()); - } + this.proxy = null; // not using proxy + connection = HttpConnectionManager.getDefault().getConnection(uri, getConnectTimeout()); } else { - // using SOCKS proxy - socket = new Socket(proxy); - socket.connect(new InetSocketAddress(getHostName(), getHostPort()), - getConnectTimeout()); + connection = HttpConnectionManager.getDefault().getConnection(uri, proxy, getConnectTimeout()); } - return socket; + return connection; } /** @@ -665,13 +661,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 +716,17 @@ */ @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 if (connection != null) { + HttpConnectionManager.getDefault().returnConnectionToPool(connection); + connection = null; } + connection = null; } protected void endRequest() throws IOException { @@ -1067,7 +1063,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 +1374,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 +1397,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 +1443,7 @@ hostPort = -1; } endRequest(); - closeSocket(); + disconnect(); connected = false; continue; } @@ -1464,7 +1460,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,138 @@ +/* + * 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.InetSocketAddress; +import java.net.Proxy; +import java.net.SocketAddress; +import java.net.URI; + +import org.apache.harmony.luni.util.Msg; + +/** + * An HttpConfiguration contains all the details needed to create an http connection + * and to compare whether or not two connections are the same. An HttpConfiguration + * will either consist of a Proxy or a port number (int) + * and host name (String) or all three, depending on whether or not a + * Proxy is used and the type of Proxy it is. + * + * HttpConfiguration is used as a key by HttpConnectionManager + * to retrieve HttpConnections from its connection pool. + */ +public class HttpConfiguration { + + private Proxy proxy; + private int hostPort; + private String hostName; + private URI uri; + + public HttpConfiguration(URI uri) { + this.uri = uri; + this.hostName = uri.getHost(); + this.hostPort = uri.getPort(); + if(hostPort == -1) { + if(uri.getScheme().equals("https")) { //$NON-NLS-1$ + hostPort = 443; + } else { + hostPort = 80; + } + } + } + + public HttpConfiguration(URI uri, Proxy proxy) { + this.uri = uri; + this.proxy = proxy; + if (proxy.type() == Proxy.Type.HTTP) { + SocketAddress proxyAddr = proxy.address(); + if (!(proxyAddr instanceof InetSocketAddress)) { + throw new IllegalArgumentException(Msg.getString( + "K0316", proxyAddr.getClass())); //$NON-NLS-1$ + } + InetSocketAddress iProxyAddr = (InetSocketAddress) proxyAddr; + this.hostName = iProxyAddr.getHostName(); + this.hostPort = iProxyAddr.getPort(); + } else { + // using SOCKS proxy + this.hostName = uri.getHost(); + this.hostPort = uri.getPort(); + if(hostPort == -1) { + if(uri.getScheme().equals("https")) { //$NON-NLS-1$ + hostPort = 443; + } else { + hostPort = 80; + } + } + } + this.uri = uri; + SocketAddress proxyAddr = proxy.address(); + if (!(proxyAddr instanceof InetSocketAddress)) { + throw new IllegalArgumentException(Msg.getString( + "K0316", proxyAddr.getClass())); //$NON-NLS-1$ + } + InetSocketAddress iProxyAddr = (InetSocketAddress) proxyAddr; + this.hostName = iProxyAddr.getHostName(); + this.hostPort = iProxyAddr.getPort(); + } + + /** + * Returns true if this configuration uses a Proxy + */ + public boolean usesProxy() { + return proxy != null; + } + + /** + * Returns the Proxy for this configuration, or null if a proxy + * is not used + */ + public Proxy getProxy() { + return proxy; + } + + /** + * Returns the host name for this configuration, or null if an http Proxy is used + */ + public String getHostName() { + return hostName; + } + + /** + * Returns the port for this configuration, or 0 if an http Proxy is used + */ + public int getHostPort() { + return hostPort; + } + + @Override + public boolean equals(Object arg0) { + if(!(arg0 instanceof HttpConfiguration)) { + return false; + } else { + HttpConfiguration config = (HttpConfiguration)arg0; + if(config.proxy != null && proxy != null) { + return config.proxy.equals(proxy) && uri.equals(config.uri); + } + return uri.equals(config.uri); + } + } + + @Override + public int hashCode() { + return uri.hashCode(); + } + +} \ No newline at end of file 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,180 @@ +/* + * 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.SocketException; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.harmony.luni.internal.nls.Messages; + +/** + * An HttpConnection represents a persistent http or https connection and contains + * various utility methods to access that connection. + */ +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(); + Proxy proxy = config.getProxy(); + if(proxy == null || proxy.type() == Proxy.Type.HTTP) { + socket = new Socket(); + } else { + socket = new Socket(proxy); + } + 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,174 @@ +/* + * 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.net.URI; +import java.security.AccessController; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.harmony.luni.util.PriviAction; + +/** + * HttpConnectionManager manages a pool of HttpConnections + * that are not currently in use and is used to get hold of persistent HttpConnections. + * Clients should return an HttpConnection to the pool after use by calling + * returnConnectionToPool + * + * Two system properties affect the behaviour of this class - http.maxConnections + * and http.keepAlive. http.keepAlive determines whether + * or not connections should be persisted and http.maxConnections + * determines the maximum number of connections to each individual host that + * should be kept in the pool. + */ +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(); + + /** + * Returns the default connection manager + */ + public static HttpConnectionManager getDefault() { + if(defaultConnectionManager == null) { + defaultConnectionManager = new HttpConnectionManager(); + } + return defaultConnectionManager; + } + + public HttpConnection getConnection(URI uri, int connectTimeout) throws IOException { + checkSystemProperties(); + HttpConfiguration config = new HttpConfiguration(uri); + return pool.getHttpConnection(config, connectTimeout); + } + + public HttpConnection getConnection(URI uri, Proxy proxy, int connectTimeout) throws IOException { + checkSystemProperties(); + HttpConfiguration config = new HttpConfiguration(uri, proxy); + return pool.getHttpConnection(config, connectTimeout); + } + + public void returnConnectionToPool(HttpConnection connection) { + checkSystemProperties(); + pool.returnConnection(connection); + } + + public int numFreeConnections() { + return pool.numFreeConnections(); + } + + private void checkSystemProperties() { + String httpMaxConnections = AccessController.doPrivileged(new PriviAction("http.maxConnections")); + String httpKeepAlive = AccessController.doPrivileged(new PriviAction("http.keepAlive")); + if(httpMaxConnections != null) { + maxConnections = Integer.parseInt(httpMaxConnections); + } + if(httpKeepAlive != null) { + keepAlive = Boolean.parseBoolean(httpKeepAlive); + if(!keepAlive) { + pool.clear(); + } + } + } + + private static class ConnectionPool { + + private Map> freeConnectionMap = new HashMap>(); // Map of free Sockets + + public synchronized void clear() { + 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) { + if(!connections.contains(connection)) { + 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()) { + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkConnect(connection.getSocket().getInetAddress().getHostName(), connection.getSocket().getPort()); + } + 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 void reset() { + pool.clear(); + + } + +} \ No newline at end of file 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 506960) +++ 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; @@ -29,6 +33,7 @@ import java.net.SocketAddress; import java.net.SocketTimeoutException; import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; @@ -34,9 +39,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 { @@ -41,6 +49,9 @@ */ public class HttpURLConnectionTest extends TestCase { + // do print debug information + private static final boolean DEBUG = false; + private final static Object bound = new Object(); static class MockServer extends Thread { @@ -51,7 +62,7 @@ public MockServer(String name) throws IOException { super(name); serverSocket = new ServerSocket(0); - serverSocket.setSoTimeout(1000); + serverSocket.setSoTimeout(5000); } public int port() { @@ -77,6 +88,128 @@ } } + static class MockHTTPServer extends MockServer { + // HTTP response codes + static final int OK_CODE = 200; + static final int NOT_FOUND_CODE = 404; + // how many times persistent connection will be used + // by server + int persUses; + // result code to be sent to client + int responseCode; + // response content to be sent to client + String response = ""; + // client's POST message + String clientPost = "Hello from client!"; + + public MockHTTPServer(String name, int persUses) throws IOException { + this(name, persUses, OK_CODE); + } + + public MockHTTPServer(String name, int persUses, + int responseCode) throws IOException { + super(name); + this.persUses = persUses; + this.responseCode = responseCode; + } + + public int port() { + return serverSocket.getLocalPort(); + } + + @Override + public void run() { + try { + synchronized (bound) { + started = true; + bound.notify(); + } + InputStream is = null; + Socket client = null; + try { + client = serverSocket.accept(); + accepted = true; + for (int i=0; i 0)) { + if (bytik == '\r') { + bytik = is.read(); + } + if (wasEOL && (bytik == '\n')) { + break; + } + wasEOL = (bytik == '\n'); + buff[num++] = (byte) bytik; + } + //int num = is.read(buff); + String message = new String(buff, 0, num); + if (DEBUG) { + System.out.println("---- Server got request: ----\n" + + message + "-----------------------------"); + } + + // Act as Server (not Proxy) side + if (message.startsWith("POST")) { + // client connection sent some data + // if the data was not read with header + if (DEBUG) { + System.out.println( + "---- Server read client's data: ----"); + } + num = is.read(buff); + message = new String(buff, 0, num); + if (DEBUG) { + System.out.println("'" + message + "'"); + System.out.println( + "------------------------------------"); + } + // check the received data + assertEquals(clientPost, message); + } + + client.getOutputStream().write(( + "HTTP/1.1 " + responseCode + " OK\n" + + "Content-type: text/html\n" + + "Content-length: " + + response.length() + "\n\n" + + response).getBytes()); + + if (responseCode != OK_CODE) { + // wait while test case check closed connection + // and interrupt this thread + try { + while (!isInterrupted()) { + Thread.sleep(1000); + } + } catch (Exception ignore) { } + } + } + } catch (SocketTimeoutException ignore) { + ignore.printStackTrace(); + } finally { + if (is != null) { + is.close(); + } + if (client != null) { + client.close(); + } + serverSocket.close(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + static class MockProxyServer extends MockServer { boolean acceptedAuthorizedRequest; @@ -89,7 +222,7 @@ public void run() { try { Socket socket = serverSocket.accept(); - socket.setSoTimeout(1000); + socket.setSoTimeout(5000); byte[] buff = new byte[1024]; int num = socket.getInputStream().read(buff); socket.getOutputStream().write(( @@ -94,7 +227,7 @@ int num = socket.getInputStream().read(buff); socket.getOutputStream().write(( "HTTP/1.0 407 Proxy authentication required\n" - + "Proxy-authenticate: Basic realm=\"remotehost\"\n\n") + + "Proxy-authenticate: Basic realm=\"remotehost\"\n\n") .getBytes()); num = socket.getInputStream().read(buff); if (num == -1) { @@ -100,7 +233,7 @@ if (num == -1) { // this connection was closed, create new one: socket = serverSocket.accept(); - socket.setSoTimeout(1000); + socket.setSoTimeout(5000); num = socket.getInputStream().read(buff); } String request = new String(buff, 0, num); @@ -114,7 +247,15 @@ } } } - + + public void setUp() { + if (DEBUG) { + System.out.println("\n=============================="); + System.out.println("===== Execution: "+getName()); + System.out.println("=============================="); + } + } + /** * ProxySelector implementation used in the test. */ @@ -283,8 +424,8 @@ (HttpURLConnection) url.openConnection( new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", proxy.port()))); - connection.setConnectTimeout(1000); - connection.setReadTimeout(1000); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); proxy.start(); @@ -299,5 +440,567 @@ 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 + * @throws URISyntaxException + */ + public void testForcedClosure() throws IOException, InterruptedException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, URISyntaxException { + 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(new URI("http://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 reads all the data + * but not closes input stream. read until -1. + */ + public void testConnectionPersistence() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + InputStream is = c.getInputStream(); + byte[] buffer = new byte[128]; + int totalBytes = 0; + int bytesRead = 0; + while((bytesRead = is.read(buffer)) > 0){ + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer, 0, bytesRead) + "'"); + } + totalBytes += bytesRead; + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + is = c2.getInputStream(); + buffer = new byte[128]; + totalBytes = 0; + bytesRead = 0; + while((bytesRead = is.read(buffer)) > 0){ + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer, 0, bytesRead) + "'"); + totalBytes += bytesRead; + } + } + } + + /** + * Test that a connection is not closed if the client reads all the data + * but not closes input stream. read() not receives -1. + */ + public void testConnectionPersistence2() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + InputStream is = c.getInputStream(); + int bytes2Read = httpServer.response.length(); + byte[] buffer = new byte[httpServer.response.length()]; + while((bytes2Read -= is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + is = c2.getInputStream(); + buffer = new byte[httpServer.response.length()]; + bytes2Read = httpServer.response.length(); + while((bytes2Read -= is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + } + + /** + * Test that a connection is not closed if it firstly does POST, + * and then does GET requests. + */ + public void testConnectionPersistence3() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + + c.setDoInput(true); + c.setDoOutput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + c.getOutputStream().write(httpServer.clientPost.getBytes()); + + InputStream is = c.getInputStream(); + int bytes2Read = httpServer.response.length(); + byte[] buffer = new byte[httpServer.response.length()]; + while((bytes2Read -= is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + is = c2.getInputStream(); + buffer = new byte[httpServer.response.length()]; + bytes2Read = httpServer.response.length(); + while((bytes2Read -= is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + } + + /** + * Test that a connection is not closed if it firstly does GET, + * and then does POST requests. + */ + public void testConnectionPersistence4() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + + InputStream is = c.getInputStream(); + int bytes2Read = httpServer.response.length(); + byte[] buffer = new byte[httpServer.response.length()]; + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c2.setDoOutput(true); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + c2.getOutputStream().write(httpServer.clientPost.getBytes()); + is = c2.getInputStream(); + buffer = new byte[httpServer.response.length()]; + bytes2Read = httpServer.response.length(); + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + } + + /** + * Test that a connection is not closed if it does POST for 2 times. + */ + public void testConnectionPersistence5() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + c.setDoOutput(true); + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + c.getOutputStream().write(httpServer.clientPost.getBytes()); + InputStream is = c.getInputStream(); + int bytes2Read = httpServer.response.length(); + byte[] buffer = new byte[httpServer.response.length()]; + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + c2.setDoOutput(true); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + c2.getOutputStream().write(httpServer.clientPost.getBytes()); + is = c2.getInputStream(); + buffer = new byte[httpServer.response.length()]; + bytes2Read = httpServer.response.length(); + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + } + + /** + * Test that a connection made through proxy will be reused + * for connection establishing without proxy. + */ + public void testProxiedConnectionPersistence() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + HttpURLConnection c = (HttpURLConnection) + new URL("http://some.host:1234") + .openConnection(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", + httpServer.port()))); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + c.setDoOutput(true); + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + c.getOutputStream().write(httpServer.clientPost.getBytes()); + InputStream is = c.getInputStream(); + int bytes2Read = httpServer.response.length(); + byte[] buffer = new byte[httpServer.response.length()]; + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + + HttpURLConnection c2 = (HttpURLConnection) + new URL("http://some.host:1234").openConnection(); + c2.setDoOutput(true); + c2.setDoInput(true); + c2.setConnectTimeout(5000); + c2.setReadTimeout(5000); + c2.getOutputStream().write(httpServer.clientPost.getBytes()); + is = c2.getInputStream(); + buffer = new byte[httpServer.response.length()]; + bytes2Read = httpServer.response.length(); + while((bytes2Read = is.read(buffer)) > 0) { } + if (DEBUG) { + System.out.println("Client got response: '" + + new String(buffer) + "'"); + } + } + + /** + * Test that a connection is closed in case of unsuccessful connection. + * Here client gets NOT_FOUND response. + */ + public void testConnectionNonPersistence() throws Exception { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for NOT FOUND checking", 1, + MockHTTPServer.NOT_FOUND_CODE); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + + int initialFreeConnections + = HttpConnectionManager.getDefault().numFreeConnections(); + + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + + c.setDoInput(true); + c.setConnectTimeout(5000); + c.setReadTimeout(5000); + try { + InputStream is = c.getInputStream(); + fail("Expected IOException was not thrown"); + } catch (IOException expected) { + // expected + } finally { + httpServer.interrupt(); + } + assertEquals("Unsuccessful connection was not closed", + 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.apache.org").openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is = c.getInputStream(); + c = (HttpURLConnection) + new URL("http://www.apache.org").openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is2 = c.getInputStream(); + c = (HttpURLConnection) + new URL("http://www.apache.org").openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is3 = c.getInputStream(); + byte[] buffer = new byte[128]; + while(is.read(buffer) > 0){ + } + while(is2.read(buffer) > 0){ + } + while(is3.read(buffer) > 0){ + } + is.close(); + is2.close(); + is3.close(); + assertEquals(initialFreeConnections + 2, HttpConnectionManager.getDefault().numFreeConnections()); + } + }