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 515170)
+++ 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
@@ -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 {
@@ -1070,7 +1066,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 +1159,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 +1193,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 +1375,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 +1398,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 +1444,7 @@
hostPort = -1;
}
endRequest();
- closeSocket();
+ disconnect();
connected = false;
continue;
}
@@ -1467,7 +1461,7 @@
* authorization challenge
*
* @param challenge
- * @return
+ * @return authorization credentials
* @throws IOException
*/
private String getAuthorizationCredentials(String challenge)
@@ -1517,4 +1511,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,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;
+ }
+
+}
\ 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.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)
@@ -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/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
+++ src/test/java/org/apache/harmony/tests/internal/net/www/protocol/http/HttpURLConnectionTest.java
@@ -0,0 +1,828 @@
+/*
+ * 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.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;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URL;
+import java.security.Permission;
+import java.util.ArrayList;
+
+import junit.framework.TestCase;
+
+/**
+ * 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 {
+ 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);
+ }
+ }
+ }
+
+ static class MockProxyServer extends MockServer {
+
+ boolean acceptedAuthorizedRequest;
+
+ public MockProxyServer(String name) throws Exception {
+ super(name);
+ }
+
+ @Override
+ public void run() {
+ try {
+ Socket socket = serverSocket.accept();
+ 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")
+ .getBytes());
+ num = socket.getInputStream().read(buff);
+ if (num == -1) {
+ // this connection was closed, create new one:
+ socket = serverSocket.accept();
+ socket.setSoTimeout(5000);
+ num = socket.getInputStream().read(buff);
+ }
+ String request = new String(buff, 0, num);
+ acceptedAuthorizedRequest =
+ request.toLowerCase().indexOf("proxy-authorization:") > 0;
+ if (acceptedAuthorizedRequest) {
+ socket.getOutputStream().write((
+ "HTTP/1.1 200 OK\n\n").getBytes());
+ }
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ public void setUp() {
+ if (DEBUG) {
+ System.out.println("\n==============================");
+ System.out.println("===== Execution: "+getName());
+ System.out.println("==============================");
+ }
+ }
+
+ /**
+ * ProxySelector implementation used in the test.
+ */
+ static class TestProxySelector extends ProxySelector {
+ // proxy port
+ private int proxy_port;
+ // server port
+ private int server_port;
+
+ /**
+ * Creates proxy selector instance.
+ * Selector will return the proxy, only if the connection
+ * is made to localhost:server_port. Otherwise it will
+ * return NO_PROXY.
+ * Address of the returned proxy will be localhost:proxy_port.
+ */
+ public TestProxySelector(int server_port, int proxy_port) {
+ this.server_port = server_port;
+ this.proxy_port = proxy_port;
+ }
+
+ @Override
+ public java.util.List select(URI uri) {
+ Proxy proxy = Proxy.NO_PROXY;
+ if (("localhost".equals(uri.getHost()))
+ && (server_port == uri.getPort())) {
+ proxy = new Proxy(Proxy.Type.HTTP,
+ new InetSocketAddress("localhost", proxy_port));
+ }
+ ArrayList result = new ArrayList();
+ result.add(proxy);
+ return result;
+ }
+
+ @Override
+ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
+ // do nothing
+ }
+ }
+
+ /**
+ * @tests org.apache.harmony.luni.internal.net.www.http.getOutputStream()
+ */
+ public void testGetOutputStream() throws Exception {
+ // Regression for HARMONY-482
+ 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);
+ //use new String("POST") instead of simple "POST" to obtain other
+ //object instances then those that are in HttpURLConnection classes
+ c.setRequestMethod(new String("POST"));
+ c.getOutputStream();
+ httpServer.join();
+ }
+
+
+ /**
+ * Test checks if the proxy specified in openConnection
+ * method will be used for connection to the server
+ */
+ public void testUsingProxy() throws Exception {
+ // Regression for HARMONY-570
+ MockServer server = new MockServer("server");
+ MockServer proxy = new MockServer("proxy");
+
+ URL url = new URL("http://localhost:" + server.port());
+
+ HttpURLConnection connection = (HttpURLConnection) url
+ .openConnection(new Proxy(Proxy.Type.HTTP,
+ new InetSocketAddress("localhost",
+ proxy.port())));
+ connection.setConnectTimeout(2000);
+ connection.setReadTimeout(2000);
+
+ server.start();
+ synchronized(bound) {
+ if (!server.started) bound.wait(5000);
+ }
+ proxy.start();
+ synchronized(bound) {
+ if (!proxy.started) bound.wait(5000);
+ }
+
+ connection.connect();
+
+ // wait while server and proxy run
+ server.join();
+ proxy.join();
+
+ assertTrue("Connection does not use proxy", connection.usingProxy());
+ assertTrue("Proxy server was not used", proxy.accepted);
+
+ HttpURLConnection huc = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
+ assertFalse(huc.usingProxy());
+ }
+
+ /**
+ * Test checks if the proxy provided by proxy selector
+ * will be used for connection to the server
+ */
+ public void testUsingProxySelector() throws Exception {
+ // Regression for HARMONY-570
+ MockServer server = new MockServer("server");
+ MockServer proxy = new MockServer("proxy");
+
+ URL url = new URL("http://localhost:" + server.port());
+
+ // keep default proxy selector
+ ProxySelector defPS = ProxySelector.getDefault();
+ // replace selector
+ ProxySelector.setDefault(
+ new TestProxySelector(server.port(), proxy.port()));
+
+ try {
+ HttpURLConnection connection =
+ (HttpURLConnection) url.openConnection();
+ connection.setConnectTimeout(2000);
+ connection.setReadTimeout(2000);
+
+ server.start();
+ synchronized(bound) {
+ if (!server.started) bound.wait(5000);
+ }
+ proxy.start();
+ synchronized(bound) {
+ if (!proxy.started) bound.wait(5000);
+ }
+ connection.connect();
+
+ // wait while server and proxy run
+ server.join();
+ proxy.join();
+
+ assertTrue("Connection does not use proxy",
+ connection.usingProxy());
+ assertTrue("Proxy server was not used", proxy.accepted);
+ } finally {
+ // restore default proxy selector
+ ProxySelector.setDefault(defPS);
+ }
+ }
+
+ public void testProxyAuthorization() throws Exception {
+ // Set up test Authenticator
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(
+ "user", "password".toCharArray());
+ }
+ });
+
+ try {
+ MockProxyServer proxy = new MockProxyServer("ProxyServer");
+
+ URL url = new URL("http://remotehost:55555/requested.data");
+ HttpURLConnection connection =
+ (HttpURLConnection) url.openConnection(
+ new Proxy(Proxy.Type.HTTP,
+ new InetSocketAddress("localhost", proxy.port())));
+ connection.setConnectTimeout(5000);
+ connection.setReadTimeout(5000);
+
+ proxy.start();
+
+ connection.connect();
+ assertEquals("unexpected response code",
+ 200, connection.getResponseCode());
+ proxy.join();
+ assertTrue("Connection did not send proxy authorization request",
+ proxy.acceptedAuthorizedRequest);
+ } finally {
+ // remove previously set authenticator
+ 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/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java
===================================================================
--- src/test/java/org/apache/harmony/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java
+++ src/test/java/org/apache/harmony/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java
@@ -0,0 +1,1440 @@
+/*
+ * 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.https;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.Authenticator;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.util.Arrays;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Implementation independent test for HttpsURLConnection.
+ * The test needs certstore file placed in system classpath
+ * and named as "key_store." + the type of the
+ * default KeyStore installed in the system in lower case.
+ *
+ * For example: if default KeyStore type in the system is BKS
+ * (i.e. java.security file sets up the property keystore.type=BKS),
+ * thus classpath should point to the directory with "key_store.bks"
+ * file.
+ *
+ * This certstore file should contain self-signed certificate
+ * generated by keytool utility in a usual way.
+ *
+ * The password to the certstore should be "password" (without quotes).
+ */
+public class HttpsURLConnectionTest extends TestCase {
+
+ // the password to the store
+ private static final String KS_PASSWORD = "password";
+
+ // turn on/off logging
+ private static final boolean DO_LOG = false;
+
+ // read/connection timeout value
+ private static final int TIMEOUT = 5000;
+
+ // OK response code
+ private static final int OK_CODE = 200;
+
+ // Not Found response code
+ private static final int NOT_FOUND_CODE = 404;
+
+ // Proxy authentication required response code
+ private static final int AUTHENTICATION_REQUIRED_CODE = 407;
+
+ // fields keeping the system values of corresponding properties
+ private static String systemKeyStoreType;
+
+ private static String systemKeyStore;
+
+ private static String systemKeyStorePassword;
+
+ private static String systemTrustStoreType;
+
+ private static String systemTrustStore;
+
+ private static String systemTrustStorePassword;
+
+ /**
+ * Checks that HttpsURLConnection's default SSLSocketFactory is operable.
+ */
+ public void testGetDefaultSSLSocketFactory() throws Exception {
+ // set up the properties defining the default values needed by SSL stuff
+ setUpStoreProperties();
+
+ try {
+ SSLSocketFactory defaultSSLSF = HttpsURLConnection
+ .getDefaultSSLSocketFactory();
+ ServerSocket ss = new ServerSocket(0);
+ Socket s = defaultSSLSF
+ .createSocket("localhost", ss.getLocalPort());
+ ss.accept();
+ s.close();
+ ss.close();
+ } finally {
+ // roll the properties back to system values
+ tearDownStoreProperties();
+ }
+ }
+
+ /**
+ * Checks if HTTPS connection performs initial SSL handshake with the
+ * server working over SSL, sends encrypted HTTP request,
+ * and receives expected HTTP response. After HTTPS session if finished
+ * test checks connection state parameters established by
+ * HttpsURLConnection.
+ */
+ public void testHttpsConnection() 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) doInteraction(connection, ss);
+
+ // check the connection state
+ checkConnectionStateParameters(connection, peerSocket);
+
+ // should silently exit
+ connection.connect();
+ } finally {
+ // roll the properties back to system values
+ tearDownStoreProperties();
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public void testHttpsConnection_Not_Found_Response() 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();
+
+ try {
+ doInteraction(connection, ss, NOT_FOUND_CODE);
+ fail("Expected exception was not thrown.");
+ } catch (FileNotFoundException e) {
+ if (DO_LOG) {
+ System.out.println("Expected exception was thrown: "
+ + e.getMessage());
+ }
+ }
+
+ // should silently exit
+ connection.connect();
+ } finally {
+ // roll the properties back to system values
+ tearDownStoreProperties();
+ }
+ }
+
+ /**
+ * Tests possibility to set up the default SSLSocketFactory
+ * to be used by HttpsURLConnection.
+ */
+ public void testSetDefaultSSLSocketFactory() throws Throwable {
+ // create the SSLServerSocket which will be used by server side
+ SSLContext ctx = getContext();
+ SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory()
+ .createServerSocket(0);
+
+ SSLSocketFactory socketFactory = (SSLSocketFactory) ctx
+ .getSocketFactory();
+ // set up the factory as default
+ HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory);
+ // check the result
+ assertSame("Default SSLSocketFactory differs from expected",
+ socketFactory, HttpsURLConnection.getDefaultSSLSocketFactory());
+
+ // create the HostnameVerifier to check hostname verification
+ 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();
+
+ TestHostnameVerifier hnv_late = new TestHostnameVerifier();
+ // late initialization: should not be used for created connection
+ HttpsURLConnection.setDefaultHostnameVerifier(hnv_late);
+
+ // perform the interaction between the peers
+ SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
+ // check the connection state
+ checkConnectionStateParameters(connection, peerSocket);
+ // check the verification process
+ assertTrue("Hostname verification was not done", hnv.verified);
+ assertFalse(
+ "Hostname verification should not be done by this verifier",
+ hnv_late.verified);
+ // check the used SSLSocketFactory
+ assertSame("Default SSLSocketFactory should be used",
+ HttpsURLConnection.getDefaultSSLSocketFactory(), connection
+ .getSSLSocketFactory());
+
+ // should silently exit
+ connection.connect();
+ }
+
+ /**
+ * Tests possibility to set up the SSLSocketFactory
+ * to be used by HttpsURLConnection.
+ */
+ public void testSetSSLSocketFactory() throws Throwable {
+ // create the SSLServerSocket which will be used by server side
+ SSLContext ctx = getContext();
+ SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory()
+ .createServerSocket(0);
+
+ // create the HostnameVerifier to check hostname verification
+ 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();
+
+ SSLSocketFactory socketFactory = (SSLSocketFactory) ctx
+ .getSocketFactory();
+ connection.setSSLSocketFactory(socketFactory);
+
+ TestHostnameVerifier hnv_late = new TestHostnameVerifier();
+ // late initialization: should not be used for created connection
+ HttpsURLConnection.setDefaultHostnameVerifier(hnv_late);
+
+ // perform the interaction between the peers
+ SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
+ // check the connection state
+ checkConnectionStateParameters(connection, peerSocket);
+ // check the verification process
+ assertTrue("Hostname verification was not done", hnv.verified);
+ assertFalse(
+ "Hostname verification should not be done by this verifier",
+ hnv_late.verified);
+ // check the used SSLSocketFactory
+ assertNotSame("Default SSLSocketFactory should not be used",
+ HttpsURLConnection.getDefaultSSLSocketFactory(), connection
+ .getSSLSocketFactory());
+ assertSame("Result differs from expected", socketFactory, connection
+ .getSSLSocketFactory());
+
+ // should silently exit
+ connection.connect();
+ }
+
+ /**
+ * Tests the behaviour of HttpsURLConnection in case of retrieving
+ * of the connection state parameters before connection has been made.
+ */
+ public void testUnconnectedStateParameters() throws Throwable {
+ // create HttpsURLConnection to be tested
+ URL url = new URL("https://localhost:55555");
+ HttpsURLConnection connection = (HttpsURLConnection) url
+ .openConnection();
+
+ try {
+ connection.getCipherSuite();
+ fail("Expected IllegalStateException was not thrown");
+ } catch (IllegalStateException e) {}
+ try {
+ connection.getPeerPrincipal();
+ fail("Expected IllegalStateException was not thrown");
+ } catch (IllegalStateException e) {}
+ try {
+ connection.getLocalPrincipal();
+ fail("Expected IllegalStateException was not thrown");
+ } catch (IllegalStateException e) {}
+
+ try {
+ connection.getServerCertificates();
+ fail("Expected IllegalStateException was not thrown");
+ } catch (IllegalStateException e) {}
+ try {
+ connection.getLocalCertificates();
+ fail("Expected IllegalStateException was not thrown");
+ } catch (IllegalStateException e) {}
+ }
+
+ /**
+ * Tests if setHostnameVerifier() method replaces default verifier.
+ */
+ public void testSetHostnameVerifier() 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();
+
+ TestHostnameVerifier hnv_late = new TestHostnameVerifier();
+ // replace default verifier
+ connection.setHostnameVerifier(hnv_late);
+
+ // perform the interaction between the peers and check the results
+ SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
+ assertTrue("Hostname verification was not done", hnv_late.verified);
+ assertFalse(
+ "Hostname verification should not be done by this verifier",
+ hnv.verified);
+ checkConnectionStateParameters(connection, peerSocket);
+
+ // should silently exit
+ connection.connect();
+ } finally {
+ // roll the properties back to system values
+ tearDownStoreProperties();
+ }
+ }
+
+ /**
+ * Tests the behaviour in case of sending the data to the server.
+ */
+ public void test_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) doInteraction(connection, ss);
+ checkConnectionStateParameters(connection, peerSocket);
+
+ // should silently exit
+ connection.connect();
+ } finally {
+ // roll the properties back to system values
+ tearDownStoreProperties();
+ }
+ }
+
+ /**
+ * 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 {
+ // 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) doInteraction(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.
+ * 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 {
+ // 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);
+
+ Authenticator.setDefault(new Authenticator() {
+
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication("user", "password"
+ .toCharArray());
+ }
+ });
+
+ // create HttpsURLConnection to be tested
+ URL url = new URL("https://requested.host:55555/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) doInteraction(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.
+ * 2 HTTPS connections are opened for one URL. For the first time
+ * the connection is opened through one proxy,
+ * for the second time through another.
+ */
+ public void testConsequentProxyConnection() 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:55555/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) doInteraction(connection, ss);
+ checkConnectionStateParameters(connection, peerSocket);
+
+ // create another SSLServerSocket which will be used by server side
+ ss = new ServerSocket(0);
+
+ connection = (HttpsURLConnection) url.openConnection(new Proxy(
+ Proxy.Type.HTTP, new InetSocketAddress("localhost", ss
+ .getLocalPort())));
+
+ // perform the interaction between the peers and check the results
+ peerSocket = (SSLSocket) doInteraction(connection, ss);
+ checkConnectionStateParameters(connection, peerSocket);
+ } finally {
+ // roll the properties back to system values
+ tearDownStoreProperties();
+ }
+ }
+
+ /**
+ * Tests HTTPS connection process made through the proxy server.
+ * Proxy server needs authentication.
+ * Client sends data to the server.
+ */
+ public void testProxyAuthConnection_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
+ ServerSocket ss = new ServerSocket(0);
+
+ // create the HostnameVerifier to check that Hostname verification
+ // is done
+ TestHostnameVerifier hnv = new TestHostnameVerifier();
+ HttpsURLConnection.setDefaultHostnameVerifier(hnv);
+
+ Authenticator.setDefault(new Authenticator() {
+
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication("user", "password"
+ .toCharArray());
+ }
+ });
+
+ // create HttpsURLConnection to be tested
+ URL url = new URL("https://requested.host:55554/requested.data");
+ HttpsURLConnection connection = (HttpsURLConnection) url
+ .openConnection(new Proxy(Proxy.Type.HTTP,
+ new InetSocketAddress("localhost", ss
+ .getLocalPort())));
+ connection.setDoOutput(true);
+
+ // perform the interaction between the peers and check the results
+ SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss,
+ OK_CODE);
+ checkConnectionStateParameters(connection, peerSocket);
+ } finally {
+ // roll the properties back to system values
+ tearDownStoreProperties();
+ }
+ }
+
+ /**
+ * Tests HTTPS connection process made through the proxy server.
+ * Proxy server needs authentication but client fails to authenticate
+ * (Authenticator was not set up in the system).
+ */
+ public void testProxyAuthConnectionFailed() 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:55555/requested.data");
+ HttpURLConnection connection = (HttpURLConnection) url
+ .openConnection(new Proxy(Proxy.Type.HTTP,
+ new InetSocketAddress("localhost", ss
+ .getLocalPort())));
+
+ // perform the interaction between the peers and check the results
+ try {
+ doInteraction(connection, ss, AUTHENTICATION_REQUIRED_CODE);
+ } catch (IOException e) {
+ // SSL Tunnelling failed
+ if (DO_LOG) {
+ System.out.println("Got expected IOException: "
+ + e.getMessage());
+ }
+ }
+ } finally {
+ // roll the properties back to system values
+ tearDownStoreProperties();
+ }
+ }
+
+ /**
+ * Tests the behaviour of HTTPS connection in case of unavailability
+ * of requested resource.
+ */
+ public void testProxyConnection_Not_Found_Response() 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://localhost:" + ss.getLocalPort());
+ HttpURLConnection connection = (HttpURLConnection) url
+ .openConnection(new Proxy(Proxy.Type.HTTP,
+ new InetSocketAddress("localhost", ss
+ .getLocalPort())));
+
+ try {
+ doInteraction(connection, ss, NOT_FOUND_CODE); // NOT FOUND
+ fail("Expected exception was not thrown.");
+ } catch (FileNotFoundException e) {
+ if (DO_LOG) {
+ System.out.println("Expected exception was thrown: "
+ + e.getMessage());
+ }
+ }
+ } finally {
+ // roll the properties back to system values
+ tearDownStoreProperties();
+ }
+ }
+
+ // ---------------------------------------------------------------------
+ // ------------------------ Staff Methods ------------------------------
+ // ---------------------------------------------------------------------
+
+ /**
+ * Log the name of the test case to be executed.
+ */
+ public void setUp() throws Exception {
+ if (DO_LOG) {
+ System.out.println();
+ System.out.println("------------------------");
+ System.out.println("------ " + getName());
+ System.out.println("------------------------");
+ }
+ }
+
+ /**
+ * Checks the HttpsURLConnection getter's values and compares
+ * them with actual corresponding values of remote peer.
+ */
+ public static void checkConnectionStateParameters(
+ HttpsURLConnection clientConnection, SSLSocket serverPeer)
+ throws Exception {
+ SSLSession session = serverPeer.getSession();
+
+ assertEquals(session.getCipherSuite(), clientConnection
+ .getCipherSuite());
+
+ assertEquals(session.getLocalPrincipal(), clientConnection
+ .getPeerPrincipal());
+
+ assertEquals(session.getPeerPrincipal(), clientConnection
+ .getLocalPrincipal());
+
+ Certificate[] serverCertificates = clientConnection
+ .getServerCertificates();
+ Certificate[] localCertificates = session.getLocalCertificates();
+ assertTrue("Server certificates differ from expected", Arrays.equals(
+ serverCertificates, localCertificates));
+
+ localCertificates = clientConnection.getLocalCertificates();
+ serverCertificates = session.getPeerCertificates();
+ assertTrue("Local certificates differ from expected", Arrays.equals(
+ serverCertificates, localCertificates));
+ }
+
+ /**
+ * 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
+ * or file does not exist.
+ */
+ private static String getKeyStoreFileName() throws Exception {
+ String ksFileName = "org/apache/harmony/luni/tests/key_store."
+ + KeyStore.getDefaultType().toLowerCase();
+ URL url = ClassLoader.getSystemClassLoader().getResource(ksFileName);
+ assertNotNull("Expected KeyStore file: '" + ksFileName
+ + "' for default KeyStore of type '"
+ + KeyStore.getDefaultType() + "' does not exist.", url);
+ return new File(url.toURI()).getAbsolutePath();
+ }
+
+ /**
+ * Builds and returns the context used for secure socket creation.
+ */
+ private static SSLContext getContext() throws Exception {
+ String type = KeyStore.getDefaultType();
+ SSLContext ctx;
+
+ String keyStore = getKeyStoreFileName();
+ File keyStoreFile = new File(keyStore);
+
+ FileInputStream fis = new FileInputStream(keyStoreFile);
+
+ KeyStore ks = KeyStore.getInstance(type);
+ ks.load(fis, KS_PASSWORD.toCharArray());
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory
+ .getDefaultAlgorithm());
+ kmf.init(ks, KS_PASSWORD.toCharArray());
+
+ TrustManagerFactory tmf = TrustManagerFactory
+ .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(ks);
+
+ ctx = SSLContext.getInstance("TLSv1");
+ ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+ return ctx;
+ }
+
+ /**
+ * Sets up the properties pointing to the key store and trust store
+ * and used as default values by JSSE staff. This is needed to test
+ * HTTPS behaviour in the case of default SSL Socket Factories.
+ */
+ private static void setUpStoreProperties() throws Exception {
+ String type = KeyStore.getDefaultType();
+
+ systemKeyStoreType = System.getProperty("javax.net.ssl.keyStoreType");
+ systemKeyStore = System.getProperty("javax.net.ssl.keyStore");
+ systemKeyStorePassword = System
+ .getProperty("javax.net.ssl.keyStorePassword");
+
+ systemTrustStoreType = System
+ .getProperty("javax.net.ssl.trustStoreType");
+ systemTrustStore = System.getProperty("javax.net.ssl.trustStore");
+ systemTrustStorePassword = System
+ .getProperty("javax.net.ssl.trustStorePassword");
+
+ System.setProperty("javax.net.ssl.keyStoreType", type);
+ System.setProperty("javax.net.ssl.keyStore", getKeyStoreFileName());
+ System.setProperty("javax.net.ssl.keyStorePassword", KS_PASSWORD);
+
+ System.setProperty("javax.net.ssl.trustStoreType", type);
+ System.setProperty("javax.net.ssl.trustStore", getKeyStoreFileName());
+ System.setProperty("javax.net.ssl.trustStorePassword", KS_PASSWORD);
+ }
+
+ /**
+ * Rolls back the values of system properties.
+ */
+ private static void tearDownStoreProperties() {
+ if (systemKeyStoreType == null) {
+ System.clearProperty("javax.net.ssl.keyStoreType");
+ } else {
+ System
+ .setProperty("javax.net.ssl.keyStoreType",
+ systemKeyStoreType);
+ }
+ if (systemKeyStore == null) {
+ System.clearProperty("javax.net.ssl.keyStore");
+ } else {
+ System.setProperty("javax.net.ssl.keyStore", systemKeyStore);
+ }
+ if (systemKeyStorePassword == null) {
+ System.clearProperty("javax.net.ssl.keyStorePassword");
+ } else {
+ System.setProperty("javax.net.ssl.keyStorePassword",
+ systemKeyStorePassword);
+ }
+
+ if (systemTrustStoreType == null) {
+ System.clearProperty("javax.net.ssl.trustStoreType");
+ } else {
+ System.setProperty("javax.net.ssl.trustStoreType",
+ systemTrustStoreType);
+ }
+ if (systemTrustStore == null) {
+ System.clearProperty("javax.net.ssl.trustStore");
+ } else {
+ System.setProperty("javax.net.ssl.trustStore", systemTrustStore);
+ }
+ if (systemTrustStorePassword == null) {
+ System.clearProperty("javax.net.ssl.trustStorePassword");
+ } else {
+ System.setProperty("javax.net.ssl.trustStorePassword",
+ systemTrustStorePassword);
+ }
+ }
+
+ /**
+ * Performs interaction between client's HttpURLConnection and
+ * servers side (ServerSocket).
+ */
+ public static Socket doInteraction(
+ final HttpURLConnection clientConnection,
+ final ServerSocket serverSocket) throws Throwable {
+ return doInteraction(clientConnection, serverSocket, OK_CODE, false, false);
+ }
+
+ /**
+ * Performs interaction between client's HttpURLConnection and
+ * servers side (ServerSocket). Server will response with specified
+ * response code.
+ */
+ public static Socket doInteraction(
+ final HttpURLConnection clientConnection,
+ final ServerSocket serverSocket, final int responseCode)
+ throws Throwable {
+ return doInteraction(clientConnection, serverSocket, responseCode,
+ 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);
+ }
+
+ /**
+ * Performs interaction between client's HttpURLConnection and
+ * servers side (ServerSocket). Server will response with specified
+ * response code.
+ * @param doAuthentication specifies
+ * if the server needs client authentication.
+ */
+ public static Socket doInteraction(
+ final HttpURLConnection clientConnection,
+ final ServerSocket serverSocket, final int responseCode,
+ final boolean doAuthentication,
+ final boolean checkPersistence) throws Throwable {
+
+ // set up the connection
+ clientConnection.setDoInput(true);
+ clientConnection.setConnectTimeout(TIMEOUT);
+ clientConnection.setReadTimeout(TIMEOUT);
+
+ ServerWork server = new ServerWork(serverSocket, responseCode,
+ doAuthentication, checkPersistence);
+
+ ClientConnectionWork client = new ClientConnectionWork(clientConnection);
+
+ server.start();
+ client.start();
+
+ client.join();
+ if (client.thrown != null) {
+ if (responseCode != OK_CODE) { // not OK response expected
+ // it is probably expected exception, keep it as is
+ throw client.thrown;
+ }
+ if ((client.thrown instanceof SocketTimeoutException)
+ && (server.thrown != null)) {
+ // server's exception is more informative in this case
+ throw new Exception(server.thrown);
+ } else {
+ 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;
+ }
+ return server.peerSocket;
+ }
+
+ /**
+ * The host name verifier used in test.
+ */
+ static class TestHostnameVerifier implements HostnameVerifier {
+
+ boolean verified = false;
+
+ public boolean verify(String hostname, SSLSession session) {
+ if (DO_LOG) {
+ System.out.println("***> verification " + hostname + " "
+ + session.getPeerHost());
+ }
+ verified = true;
+ return true;
+ }
+ }
+
+ /**
+ * The base class for mock Client and Server.
+ */
+ static class Work extends Thread {
+
+ /**
+ * The header of OK HTTP response.
+ */
+ static String responseHead = "HTTP/1.1 200 OK\n";
+
+ /**
+ * The content of the response.
+ */
+ static String plainResponseContent = "\n"
+ + "Plain Response Content\n"
+ + "";
+
+ /**
+ * The tail of the response.
+ */
+ static String plainResponseTail = "Content-type: text/html\n"
+ + "Content-length: " + plainResponseContent.length() + "\n\n"
+ + plainResponseContent;
+
+ /**
+ * The response message to be sent in plain (HTTP) format.
+ */
+ static String plainResponse = responseHead + plainResponseTail;
+
+ /**
+ * The content of the response to be sent during HTTPS session.
+ */
+ static String httpsResponseContent = "\n"
+ + "HTTPS Response Content\n"
+ + "";
+
+ /**
+ * The tail of the response to be sent during HTTPS session.
+ */
+ static String httpsResponseTail = "Content-type: text/html\n"
+ + "Content-length: " + httpsResponseContent.length() + "\n\n"
+ + httpsResponseContent;
+
+ /**
+ * The response requiring client's proxy authentication.
+ */
+ static String respAuthenticationRequired = "HTTP/1.0 407 Proxy authentication required\n"
+ + "Proxy-authenticate: Basic realm=\"localhost\"\n\n";
+
+ /**
+ * The data to be posted by client to the server.
+ */
+ static String clientsData = "_.-^ Client's Data ^-._";
+
+ /**
+ * The exception thrown during peers interaction.
+ */
+ protected Throwable thrown;
+
+ /**
+ * The print stream used for debug log.
+ * If it is null debug info will not be printed.
+ */
+ private PrintStream out = new PrintStream(System.out);
+
+ /**
+ * Prints log message.
+ */
+ public synchronized void log(String message) {
+ if (DO_LOG && (out != null)) {
+ System.out.println("[" + getName() + "]: " + message);
+ }
+ }
+ }
+
+ /**
+ * The class used for server side works.
+ */
+ static class ServerWork extends Work {
+
+ // the server socket used for connection
+ private ServerSocket serverSocket;
+
+ // the socket connected with client peer
+ private Socket peerSocket;
+
+ // indicates if the server acts as proxy server
+ private boolean actAsProxy;
+
+ // 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;
+
+ /**
+ * Creates the thread acting as a server side.
+ */
+ public ServerWork(ServerSocket serverSocket) {
+ // the server does not require proxy authentication
+ // and sends OK_CODE (OK) response code
+ this(serverSocket, OK_CODE, false, false);
+ }
+
+ /**
+ * Creates the thread acting as a server side.
+ * @param serverSocket the server socket to be used during connection
+ * @param responseCode the response code to be sent to the client
+ * @param needProxyAuthentication
+ * indicates if the server needs proxy authentication
+ */
+ public ServerWork(ServerSocket serverSocket, int responseCode,
+ 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) {
+ // demand client to send its certificate
+ ((SSLServerSocket) serverSocket).setNeedClientAuth(true);
+ // work as a HTTPS server, not as HTTP proxy
+ this.actAsProxy = false;
+ } else {
+ this.actAsProxy = true;
+ }
+ this.actAsProxy = !(serverSocket instanceof SSLServerSocket);
+ setName(this.actAsProxy ? "Proxy Server" : "Server");
+ }
+
+ /**
+ * Closes the connection.
+ */
+ public void closeSocket(Socket socket) {
+ try {
+ socket.getInputStream().close();
+ } catch (IOException e) {}
+ try {
+ socket.getOutputStream().close();
+ } catch (IOException e) {}
+ try {
+ socket.close();
+ } catch (IOException e) {}
+ }
+
+ /**
+ * Performs the actual server work.
+ * If some exception occurs during the work it will be
+ * stored in the thrown field.
+ */
+ public void run() {
+ // the buffer used for reading the messages
+ byte[] buff = new byte[2048];
+ // the number of bytes read into the buffer
+ int num;
+ try {
+ // configure the server socket to avoid blocking
+ serverSocket.setSoTimeout(TIMEOUT);
+ // accept client connection
+ peerSocket = serverSocket.accept();
+ // configure the client connection to avoid blocking
+ peerSocket.setSoTimeout(TIMEOUT);
+ log("Client connection ACCEPTED");
+
+ InputStream is = peerSocket.getInputStream();
+ OutputStream os = peerSocket.getOutputStream();
+
+ // how many times established connection will be used
+ int number_of_uses = checkPersistence ? 2 : 1;
+ for (int it=0; it 0);
+ }
+
+ 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");
+ }
+
+ 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("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("Work is DONE");
+ };
+ } catch (Throwable e) {
+ if (DO_LOG) {
+ e.printStackTrace();
+ }
+ thrown = e;
+ } finally {
+ closeSocket(peerSocket);
+ try {
+ serverSocket.close();
+ } catch (IOException e) {}
+ }
+ }
+ }
+
+ /**
+ * The class used for client side works. It could be used to test
+ * both HttpURLConnection and HttpsURLConnection.
+ */
+ static class ClientConnectionWork extends Work {
+
+ // connection to be used to contact the server side
+ private HttpURLConnection connection;
+
+ /**
+ * Creates the thread acting as a client side.
+ * @param connection connection to be used to contact the server side
+ */
+ public ClientConnectionWork(HttpURLConnection connection) {
+ this.connection = connection;
+ setName("Client Connection");
+ log("Created over connection: " + connection.getClass());
+ }
+
+ /**
+ * Performs the actual client work.
+ * If some exception occurs during the work it will be
+ * stored in the thrown field.
+ */
+ public void run() {
+ try {
+ log("Opening the connection..");
+ connection.connect();
+ log("Connection has been ESTABLISHED, using proxy: "
+ + connection.usingProxy());
+ if (connection.getDoOutput()) {
+ // connection configured to post data, do so
+ connection.getOutputStream().write(clientsData.getBytes());
+ }
+ // read the content of HTTP(s) response
+ InputStream is = connection.getInputStream();
+ log("Input Stream obtained: "+is.getClass());
+ byte[] buff = new byte[2048];
+ int num = 0;
+ int byt = 0;
+ while ((num < buff.length) && (is.available() > 0)
+ && ((byt = is.read()) != -1)) {
+ buff[num++] = (byte) byt;
+ }
+ String message = new String(buff, 0, num);
+ log("Got content:\n" + message);
+ log("------------------");
+ log("Response code: " + connection.getResponseCode());
+
+ if (connection instanceof HttpsURLConnection) {
+ assertEquals(httpsResponseContent, message);
+ } else {
+ assertEquals(plainResponseContent, message);
+ }
+ } catch (Throwable e) {
+ if (DO_LOG) {
+ e.printStackTrace();
+ }
+ thrown = e;
+ }
+ }
+ }
+
+ public static junit.framework.Test suite() {
+ return new TestSuite(HttpsURLConnectionTest.class);
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+}