Index: .classpath =================================================================== --- .classpath (revision 494409) +++ .classpath (working copy) @@ -1,10 +1,11 @@ - - - - + + + + + - + Index: build.xml =================================================================== --- build.xml (revision 525158) +++ build.xml (working copy) @@ -264,6 +264,7 @@ + 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 525158) +++ 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; @@ -74,6 +71,8 @@ private int httpVersion = 1; // Assume HTTP/1.1 + protected HttpConnection connection; + private InputStream is; private InputStream uis; @@ -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 @@ -534,8 +555,8 @@ protected HttpURLConnection(URL url, int port) { super(url); defaultPort = port; - reqHeader = (Header) defaultReqHeader.clone(); + try { uri = url.toURI(); } catch (URISyntaxException e) { @@ -584,8 +605,13 @@ if (getFromCache()) { return; } + try { + uri = url.toURI(); + } catch (URISyntaxException e1) { + // ignore + } // 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 +617,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 +630,7 @@ continue; } try { - socket = getHTTPConnection(selectedProxy); + connection = getHTTPConnection(selectedProxy); proxy = selectedProxy; break; // connected } catch (IOException e) { @@ -614,12 +640,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 +650,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 +666,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 +721,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 != null) { + connection.closeSocketAndStreams(); + } else if (connection != null) { + HttpConnectionManager.getDefault().returnConnectionToPool(connection); + connection = null; } + connection = null; } protected void endRequest() throws IOException { @@ -868,7 +869,6 @@ // connect before sending requests connect(); - doRequest(); /* @@ -1070,7 +1070,7 @@ if (method == HEAD || (responseCode >= 100 && responseCode < 200) || responseCode == HTTP_NO_CONTENT || responseCode == HTTP_NOT_MODIFIED) { - closeSocket(); + disconnect(); uis = new LimitedInputStream(0); } putToCache(); @@ -1163,8 +1163,7 @@ // if we are doing output make sure the appropriate headers are sent if (os != null) { if (reqHeader.get("Content-Type") == null) { //$NON-NLS-1$ - output - .append("Content-Type: application/x-www-form-urlencoded\r\n"); //$NON-NLS-1$ + output.append("Content-Type: application/x-www-form-urlencoded\r\n"); //$NON-NLS-1$ } if (os.isCached()) { if (reqHeader.get("Content-Length") == null) { //$NON-NLS-1$ @@ -1198,8 +1197,7 @@ * if both setFixedLengthStreamingMode and * content-length are set, use fixedContentLength first */ - output - .append((fixedContentLength >= 0) ? String + output.append((fixedContentLength >= 0) ? String .valueOf(fixedContentLength) : reqHeader.get(i)); } else { @@ -1381,7 +1379,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) { @@ -1404,7 +1402,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) { @@ -1450,7 +1448,7 @@ hostPort = -1; } endRequest(); - closeSocket(); + disconnect(); connected = false; continue; } @@ -1467,7 +1465,7 @@ * authorization challenge * * @param challenge - * @return + * @return authorization credentials * @throws IOException */ private String getAuthorizationCredentials(String challenge) @@ -1517,4 +1515,4 @@ } } } -} +} \ No newline at end of file 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,252 @@ +/* + * 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.InterruptedIOException; +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; + } + + /* + * This method has been copied from the Apache Jakarta Commons HttpClient project + * http://svn.apache.org/repos/asf/jakarta/commons/proper/httpclient/trunk/HttpClient/src/java/org/apache/commons/httpclient/HttpConnection.java r480424 + */ + protected boolean isStale() throws IOException { + boolean isStale = true; + if (!socket.isClosed()) { + // the socket is open, but could still have been closed from the other end + isStale = false; + try { + if (inputStream.available() <= 0) { + int soTimeout = socket.getSoTimeout(); + try { + socket.setSoTimeout(1); + inputStream.mark(1); + int byteRead = inputStream.read(); + if (byteRead == -1) { + // again - if the socket is reporting all data read, + // probably stale + isStale = true; + } else { + inputStream.reset(); + } + } finally { + socket.setSoTimeout(soTimeout); + } + } + } catch (InterruptedIOException e) { + if (!isSocketTimeoutException(e)) { + throw e; + } + // aha - the connection is NOT stale - continue on! + } catch (IOException e) { + // oops - the connection is stale, the read or soTimeout failed. + isStale = true; + } + } + + return isStale; + } + + /* + * This field has been copied from the Apache Jakarta Commons HttpClient project + * http://svn.apache.org/repos/asf/jakarta/commons/proper/httpclient/trunk/HttpClient/src/java/org/apache/commons/httpclient/HttpConnection.java r480424 + */ + static private final Class SOCKET_TIMEOUT_CLASS = SocketTimeoutExceptionClass(); + + /* + * This method has been copied from the Apache Jakarta Commons HttpClient project + * http://svn.apache.org/repos/asf/jakarta/commons/proper/httpclient/trunk/HttpClient/src/java/org/apache/commons/httpclient/HttpConnection.java r480424 + */ + public static boolean isSocketTimeoutException(final InterruptedIOException e) { + if (SOCKET_TIMEOUT_CLASS != null) { + return SOCKET_TIMEOUT_CLASS.isInstance(e); + } else { + return true; + } + } + + /* + * This method has been copied from the Apache Jakarta Commons HttpClient project + * http://svn.apache.org/repos/asf/jakarta/commons/proper/httpclient/trunk/HttpClient/src/java/org/apache/commons/httpclient/HttpConnection.java r480424 + */ + static private Class SocketTimeoutExceptionClass() { + try { + return Class.forName("java.net.SocketTimeoutException"); + } catch (ClassNotFoundException e) { + return null; + } + } + +} \ No newline at end of file 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,173 @@ +/* + * 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.isStale()) { + 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 525158) +++ src/main/java/org/apache/harmony/luni/internal/net/www/protocol/https/HttpsURLConnection.java (working copy) @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.harmony.luni.internal.net.www.protocol.https; import java.io.IOException; @@ -22,7 +21,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; @@ -387,7 +385,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 +395,8 @@ super.connect(); } if (!makingSSLTunnel) { - setUpTransportIO(wrapConnection(socket)); + sslSocket = connection.getSecureSocket(getSSLSocketFactory(), getHostnameVerifier()); + setUpTransportIO(connection); } } @@ -420,21 +419,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/impl/org/apache/harmony/tests/internal/net/www/protocol/http/PersistenceTest.java =================================================================== --- src/test/impl/org/apache/harmony/tests/internal/net/www/protocol/http/PersistenceTest.java +++ src/test/impl/org/apache/harmony/tests/internal/net/www/protocol/http/PersistenceTest.java @@ -0,0 +1,549 @@ +/* + * 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.tests.internal.net.www.protocol.http; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.net.HttpURLConnection; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +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; + +import tests.support.Support_HttpServer; +import tests.support.Support_HttpServerSocket; +import tests.support.Support_Jetty; +import tests.support.Support_PortManager; +import tests.support.Support_URLConnector; + +/** + * Tests for HttpURLConnection persistence. + * These tests depends on internal implementation. + */ +public class PersistenceTest extends TestCase { + + private static final boolean DEBUG = false; + + private final static Object bound = new Object(); + + private static int port; + + static { + // run-once set up + try { + port = Support_Jetty.startDefaultHttpServer(); + } catch (Exception e) { + fail("Exception during setup jetty : " + e.getMessage()); + } + } + + static class MockServer extends Thread { + ServerSocket serverSocket; + boolean accepted = false; + boolean started = false; + + public MockServer(String name) throws IOException { + super(name); + serverSocket = new ServerSocket(0); + serverSocket.setSoTimeout(5000); + } + + public int port() { + return serverSocket.getLocalPort(); + } + + @Override + public void run() { + try { + synchronized (bound) { + started = true; + bound.notify(); + } + try { + serverSocket.accept().close(); + accepted = true; + } catch (SocketTimeoutException ignore) { + } + serverSocket.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + 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); + } + } + } + + public void setUp() { + if (DEBUG) { + System.out.println("\n=============================="); + System.out.println("===== Execution: "+getName()); + System.out.println("=============================="); + } + } + + /** + * 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 Exception { + 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 + * @throws Exception + */ + public void testIncorrectUsage() throws Exception { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:" + port).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 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 { + 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 Exception + */ + public void testCorrectUsage() throws Exception { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:" + port).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://localhost:" + port).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 + * @throws Exception + */ + public void testMaxConnectionsSystemProperty() throws Exception { + int initialFreeConnections = HttpConnectionManager.getDefault().numFreeConnections(); + System.setProperty("http.maxConnections", "2"); + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:" + port).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is = c.getInputStream(); + c = (HttpURLConnection) + new URL("http://localhost:" + port).openConnection(); + c.setDoOutput(true); + c.setRequestMethod("GET"); + InputStream is2 = c.getInputStream(); + c = (HttpURLConnection) + new URL("http://localhost:" + port).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()); + } + + public void testClosingOutputStream() throws IOException { +// create a serversocket + Support_HttpServerSocket serversocket = new Support_HttpServerSocket(); + int portNumber = Support_PortManager.getNextPort(); + // create a client connector + Support_URLConnector connector = new Support_URLConnector(); + Support_HttpServer server = new Support_HttpServer(serversocket, this); + + server.startServer(portNumber); + + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + InputStream is; + int c; + final String postTestUrl = "http://localhost:" + portNumber + + Support_HttpServer.POSTTEST; + + String toWrite = "abcdef"; + connector.open(postTestUrl); + OutputStream out = connector.getOutputStream(); + System.out.println("Output stream = " + out.hashCode()); + out.write(toWrite.getBytes("ISO8859_1")); + out.close(); + is = connector.getInputStream(); + bout.reset(); + do { + c = is.read(); + if (c != -1) { + bout.write(c); + } + } while (c != -1); + is.close(); + connector.close(); + String result = new String(bout.toByteArray(), "ISO8859_1"); + assertTrue("Error sending data 1: " + result, toWrite + .equals(result)); + + toWrite = "zyxwvuts"; + connector.open(postTestUrl); + connector.setRequestProperty("Transfer-encoding", "chunked"); + out = connector.getOutputStream(); + System.out.println("Output stream = " + out.hashCode()); + out.write(toWrite.getBytes("ISO8859_1")); + out.close(); + is = connector.getInputStream(); + bout.reset(); + do { + c = is.read(); + if (c != -1) { + bout.write(c); + } + } while (c != -1); + is.close(); + connector.close(); + result = new String(bout.toByteArray(), "ISO8859_1"); + assertEquals(toWrite, result); + + } + +} \ No newline at end of file Index: src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/http/HttpURLConnectionTest.java =================================================================== --- src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/http/HttpURLConnectionTest.java (revision 531469) +++ src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/http/HttpURLConnectionTest.java (working copy) @@ -14,13 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.harmony.luni.tests.internal.net.www.protocol.http; import java.io.IOException; +import java.io.InputStream; 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; @@ -30,6 +31,7 @@ import java.net.SocketTimeoutException; import java.net.URI; import java.net.URL; +import java.security.Permission; import java.util.ArrayList; import junit.framework.TestCase; @@ -34,13 +36,13 @@ import junit.framework.TestCase; - /** - * Tests for HTTPURLConnection class constructors and methods. - * + * Tests for HttpURLConnection class constructors and methods. */ public class HttpURLConnectionTest extends TestCase { + private static final boolean DEBUG = false; + private final static Object bound = new Object(); static class MockServer extends Thread { @@ -51,7 +53,7 @@ public MockServer(String name) throws IOException { super(name); serverSocket = new ServerSocket(0); - serverSocket.setSoTimeout(1000); + serverSocket.setSoTimeout(5000); } public int port() { @@ -77,6 +79,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,12 +213,12 @@ 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(( - "HTTP/1.0 407 Proxy authentication required\n" - + "Proxy-authenticate: Basic realm=\"remotehost\"\n\n") + "HTTP/1.0 407 Proxy authentication required\n" + + "Proxy-authenticate: Basic realm=\"remotehost\"\n\n") .getBytes()); num = socket.getInputStream().read(buff); if (num == -1) { @@ -100,7 +224,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 +238,15 @@ } } } - + + public void setUp() { + if (DEBUG) { + System.out.println("\n=============================="); + System.out.println("===== Execution: "+getName()); + System.out.println("=============================="); + } + } + /** * ProxySelector implementation used in the test. */ @@ -256,7 +388,7 @@ server.join(); proxy.join(); - assertTrue("Connection does not use proxy", + assertTrue("Connection does not use proxy", connection.usingProxy()); assertTrue("Proxy server was not used", proxy.accepted); } finally { @@ -274,7 +406,7 @@ "user", "password".toCharArray()); } }); - + try { MockProxyServer proxy = new MockProxyServer("ProxyServer"); @@ -279,12 +411,12 @@ MockProxyServer proxy = new MockProxyServer("ProxyServer"); URL url = new URL("http://remotehost:55555/requested.data"); - HttpURLConnection connection = + HttpURLConnection connection = (HttpURLConnection) url.openConnection( - new Proxy(Proxy.Type.HTTP, + 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 +431,398 @@ Authenticator.setDefault(null); } } - -} + + /** + * 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) + "'"); + } + } + + public void testSecurityManager() throws MalformedURLException, IOException, InterruptedException { + try { + MockHTTPServer httpServer = + new MockHTTPServer("HTTP Server for persistence checking", 2); + httpServer.start(); + synchronized(bound) { + if (!httpServer.started) { + bound.wait(5000); + } + } + MySecurityManager sm = new MySecurityManager(); + System.setSecurityManager(sm); + + // Check that a first connection calls checkConnect + try { + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + if (DEBUG) { + System.out.println("Actual connection class: "+c.getClass()); + } + c.connect(); + fail("Should have thrown a SecurityException upon connection"); + + } catch (SecurityException e) { + } + + // Now create a connection properly + System.setSecurityManager(null); + HttpURLConnection c = (HttpURLConnection) + new URL("http://localhost:"+httpServer.port()).openConnection(); + 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; + } + + // Now check that a second connection also calls checkConnect + System.setSecurityManager(sm); + try { + 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; + } + } + fail("Expected a SecurityException to be thrown"); + } catch (SecurityException e) { + } + } finally { + System.setSecurityManager(null); + } + } + + private static class MySecurityManager extends SecurityManager { + + @Override + public void checkConnect(String host, int port) { + throw new SecurityException(); + } + + @Override + public void checkConnect(String host, int port, Object context) { + throw new SecurityException(); + } + + @Override + public void checkPermission(Permission permission) { + // allows a new security manager to be set + } + + } + +} \ No newline at end of file Index: src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java =================================================================== --- src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java (revision 531470) +++ src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java (working copy) @@ -161,6 +161,48 @@ } /** + * Checks if HTTPS connection performs initial SSL handshake with the + * server working over SSL, sends encrypted HTTP request, + * and receives expected HTTP response. After that it checks that the + * established connection is persistent. + * After HTTPS session if finished + * test checks connection state parameters established by + * HttpsURLConnection. + */ + public void testHttpsPersistentConnection() throws Throwable { + // set up the properties defining the default values needed by SSL stuff + setUpStoreProperties(); + + try { + // create the SSL server socket acting as a server + SSLContext ctx = getContext(); + ServerSocket ss = ctx.getServerSocketFactory() + .createServerSocket(0); + + // create the HostnameVerifier to check hostname verification + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create url connection to be tested + URL url = new URL("https://localhost:" + ss.getLocalPort()); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(); + + // perform the interaction between the peers + SSLSocket peerSocket = (SSLSocket) doPersistentInteraction(connection, ss); + + // check the connection state + checkConnectionStateParameters(connection, peerSocket); + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** * Tests the behaviour of HTTPS connection in case of unavailability * of requested resource. */ @@ -408,6 +450,43 @@ } /** + * Tests the behaviour in case of sending the data to the server + * over persistent connection. + */ + public void testPersistence_doOutput() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + SSLServerSocket ss = (SSLServerSocket) getContext() + .getServerSocketFactory().createServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://localhost:" + ss.getLocalPort()); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(); + connection.setDoOutput(true); + + // perform the interaction between the peers and check the results + SSLSocket peerSocket = (SSLSocket) + doPersistentInteraction(connection, ss); + checkConnectionStateParameters(connection, peerSocket); + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** * Tests HTTPS connection process made through the proxy server. */ public void testProxyConnection() throws Throwable { @@ -444,6 +523,43 @@ /** * Tests HTTPS connection process made through the proxy server. + * Checks that persistent connection to the host exists and can + * be used no in spite of explicit Proxy specifying. + */ + public void testPersistentProxyConnection() throws Throwable { + // setting up the properties pointing to the key/trust stores + setUpStoreProperties(); + + try { + // create the SSLServerSocket which will be used by server side + ServerSocket ss = new ServerSocket(0); + + // create the HostnameVerifier to check that Hostname verification + // is done + TestHostnameVerifier hnv = new TestHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(hnv); + + // create HttpsURLConnection to be tested + URL url = new URL("https://requested.host:55556/requested.data"); + HttpsURLConnection connection = (HttpsURLConnection) url + .openConnection(new Proxy(Proxy.Type.HTTP, + new InetSocketAddress("localhost", ss + .getLocalPort()))); + + // perform the interaction between the peers and check the results + SSLSocket peerSocket = (SSLSocket) doPersistentInteraction(connection, ss); + checkConnectionStateParameters(connection, peerSocket); + + // should silently exit + connection.connect(); + } finally { + // roll the properties back to system values + tearDownStoreProperties(); + } + } + + /** + * Tests HTTPS connection process made through the proxy server. * Proxy server needs authentication. */ public void testProxyAuthConnection() throws Throwable { @@ -568,7 +684,7 @@ // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss, - OK_CODE, true); + OK_CODE); checkConnectionStateParameters(connection, peerSocket); } finally { // roll the properties back to system values @@ -603,8 +719,7 @@ // perform the interaction between the peers and check the results try { - doInteraction(connection, ss, AUTHENTICATION_REQUIRED_CODE, - true); + doInteraction(connection, ss, AUTHENTICATION_REQUIRED_CODE); } catch (IOException e) { // SSL Tunnelling failed if (DO_LOG) { @@ -704,10 +819,10 @@ } /** - * Returns the file name of the key/trust store. The key store file + * Returns the file name of the key/trust store. The key store file * (named as "key_store." + extension equals to the default KeyStore * type installed in the system in lower case) is searched in classpath. - * @throws AssertionFailedError if property was not set + * @throws AssertionFailedError if property was not set * or file does not exist. */ private static String getKeyStoreFileName() throws Exception { @@ -826,7 +941,7 @@ public static Socket doInteraction( final HttpURLConnection clientConnection, final ServerSocket serverSocket) throws Throwable { - return doInteraction(clientConnection, serverSocket, OK_CODE, false); + return doInteraction(clientConnection, serverSocket, OK_CODE, false, false); } /** @@ -839,7 +954,31 @@ final ServerSocket serverSocket, final int responseCode) throws Throwable { return doInteraction(clientConnection, serverSocket, responseCode, - false); + false, false); + } + + /** + * Performs interaction between client's HttpURLConnection and + * servers side (ServerSocket) over persistent connection. + */ + public static Socket doPersistentInteraction( + final HttpURLConnection clientConnection, + final ServerSocket serverSocket) throws Throwable { + return doInteraction(clientConnection, serverSocket, OK_CODE, + false, true); + } + + /** + * Performs interaction between client's HttpURLConnection and + * servers side (ServerSocket) over persistent connection. + * Server will response with specified response code. + */ + public static Socket doPersistentInteraction( + final HttpURLConnection clientConnection, + final ServerSocket serverSocket, final int responseCode) + throws Throwable { + return doInteraction(clientConnection, serverSocket, responseCode, + false, true); } /** @@ -852,7 +991,8 @@ public static Socket doInteraction( final HttpURLConnection clientConnection, final ServerSocket serverSocket, final int responseCode, - final boolean doAuthentication) throws Throwable { + final boolean doAuthentication, + final boolean checkPersistence) throws Throwable { // set up the connection clientConnection.setDoInput(true); @@ -860,7 +1000,7 @@ clientConnection.setReadTimeout(TIMEOUT); ServerWork server = new ServerWork(serverSocket, responseCode, - doAuthentication); + doAuthentication, checkPersistence); ClientConnectionWork client = new ClientConnectionWork(clientConnection); @@ -868,8 +1008,6 @@ client.start(); client.join(); - server.join(); - if (client.thrown != null) { if (responseCode != OK_CODE) { // not OK response expected // it is probably expected exception, keep it as is @@ -883,6 +1021,30 @@ throw new Exception(client.thrown); } } + + if (checkPersistence) { + ClientConnectionWork client2 = + new ClientConnectionWork((HttpURLConnection) + clientConnection.getURL().openConnection()); + client2.start(); + client2.join(); + if (client2.thrown != null) { + if (responseCode != OK_CODE) { // not OK response expected + // it is probably expected exception, keep it as is + throw client2.thrown; + } + if ((client2.thrown instanceof SocketTimeoutException) + && (server.thrown != null)) { + // server's exception is more informative in this case + throw new Exception(server.thrown); + } else { + throw new Exception(client2.thrown); + } + } + } + + server.join(); + if (server.thrown != null) { throw server.thrown; } @@ -998,6 +1160,9 @@ // indicates if the server needs proxy authentication private boolean needProxyAuthentication; + // do we check for connection persistence + private boolean checkPersistence; + // response code to be send to the client peer private int responseCode; @@ -1007,7 +1172,7 @@ public ServerWork(ServerSocket serverSocket) { // the server does not require proxy authentication // and sends OK_CODE (OK) response code - this(serverSocket, OK_CODE, false); + this(serverSocket, OK_CODE, false, false); } /** @@ -1018,10 +1183,11 @@ * indicates if the server needs proxy authentication */ public ServerWork(ServerSocket serverSocket, int responseCode, - boolean needProxyAuthentication) { + boolean needProxyAuthentication, boolean checkPersistence) { this.serverSocket = serverSocket; this.responseCode = responseCode; this.needProxyAuthentication = needProxyAuthentication; + this.checkPersistence = checkPersistence; // will act as a proxy server if the specified server socket // is not a secure server socket if (serverSocket instanceof SSLServerSocket) { @@ -1073,105 +1239,120 @@ InputStream is = peerSocket.getInputStream(); OutputStream os = peerSocket.getOutputStream(); - num = is.read(buff); - String message = new String(buff, 0, num); - log("Got request:\n" + message); - log("------------------"); + // how many times established connection will be used + int number_of_uses = checkPersistence ? 2 : 1; + for (int it=0; it 0); } - // just send the response - os - .write(("HTTP/1.1 " + responseCode + "\n" + httpsResponseTail) - .getBytes()); - // and return - log("Work is DONE"); - return; - } - // Do proxy work - if (needProxyAuthentication) { - log("Authentication required ..."); - // send Authentication Request - os.write(respAuthenticationRequired.getBytes()); - // read response - num = is.read(buff); - if (num == -1) { - // this connection was closed, - // do clean up and create new one: - closeSocket(peerSocket); - peerSocket = serverSocket.accept(); - peerSocket.setSoTimeout(TIMEOUT); - log("New client connection ACCEPTED"); - is = peerSocket.getInputStream(); - os = peerSocket.getOutputStream(); - num = is.read(buff); + if (peerSocket instanceof SSLSocket) { + // it will be so if we are have second iteration + // over persistent connection + os.write(("HTTP/1.1 " + OK_CODE + + "\n" + httpsResponseTail).getBytes()); + log("Sent OK RESPONSE over SSL"); + } else { + // The content of this response will reach proxied + // HTTPUC but will not reach proxied HTTPSUC + // In case of HTTP connection it will be the final + // message, in case of HTTPS connection this message + // will just indicate that connection with remote + // host has been done + // (i.e. SSL tunnel has been established). + os.write(plainResponse.getBytes()); + log("Sent OK RESPONSE"); } - message = new String(buff, 0, num); - log("Got authenticated request:\n" + message); - log("------------------"); - // check provided authorization credentials - assertTrue("Received message does not contain " - + "authorization credentials", message - .toLowerCase().indexOf("proxy-authorization:") > 0); - } - // The content of this response will reach proxied HTTPUC - // but will not reach proxied HTTPSUC - // In case of HTTP connection it will be the final message, - // in case of HTTPS connection this message will just indicate - // that connection with remote host has been done - // (i.e. SSL tunnel has been established). - os.write(plainResponse.getBytes()); - log("Sent OK RESPONSE"); - - if (message.startsWith("CONNECT")) { // request for SSL tunnel - log("Perform SSL Handshake..."); - // create sslSocket acting as a remote server peer - SSLSocket sslSocket = (SSLSocket) getContext() - .getSocketFactory().createSocket(peerSocket, - "localhost", peerSocket.getPort(), true); // do autoclose - sslSocket.setUseClientMode(false); - // demand client authentication - sslSocket.setNeedClientAuth(true); - sslSocket.startHandshake(); - peerSocket = sslSocket; - is = peerSocket.getInputStream(); - os = peerSocket.getOutputStream(); - - // read the HTTP request sent by secure connection - // (HTTPS request) - num = is.read(buff); - message = new String(buff, 0, num); - log("[Remote Server] Request from SSL tunnel:\n" + message); - log("------------------"); + if (message.startsWith("CONNECT")) { // request for SSL tunnel + log("Perform SSL Handshake..."); + // create sslSocket acting as a remote server peer + SSLSocket sslSocket = (SSLSocket) getContext() + .getSocketFactory().createSocket(peerSocket, + "localhost", peerSocket.getPort(), true); // do autoclose + sslSocket.setUseClientMode(false); + // demand client authentication + sslSocket.setNeedClientAuth(true); + sslSocket.startHandshake(); + peerSocket = sslSocket; + is = peerSocket.getInputStream(); + os = peerSocket.getOutputStream(); - if (message.startsWith("POST")) { - // client connection sent some data - log("[Remote Server] try to read client data"); + // read the HTTP request sent by secure connection + // (HTTPS request) num = is.read(buff); message = new String(buff, 0, num); - log("[Remote Server] client's data: '" + message + "'"); - // check the received data - assertEquals(clientsData, message); + log("[Remote Server] Request from SSL tunnel:\n" + message); + log("------------------"); + + if (message.startsWith("POST")) { + // client connection sent some data + log("[Remote Server] try to read client data"); + num = is.read(buff); + message = new String(buff, 0, num); + log("[Remote Server] client's data: '" + message + "'"); + // check the received data + assertEquals(clientsData, message); + } + + log("[Remote Server] Sending the response by SSL tunnel.."); + // send the response with specified response code + os.write(("HTTP/1.1 " + responseCode + + "\n" + httpsResponseTail).getBytes()); } - - log("[Remote Server] Sending the response by SSL tunnel.."); - // send the response with specified response code - os - .write(("HTTP/1.1 " + responseCode + "\n" + httpsResponseTail) - .getBytes()); - } - log("Work is DONE"); + log("Work is DONE"); + }; } catch (Throwable e) { if (DO_LOG) { e.printStackTrace(); @@ -1222,7 +1403,7 @@ } // read the content of HTTP(s) response InputStream is = connection.getInputStream(); - log("Input Stream obtained"); + log("Input Stream obtained: "+is.getClass()); byte[] buff = new byte[2048]; int num = 0; int byt = 0;