Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimone Bordet2010-07-29 23:34:47 +0000
committerSimone Bordet2010-07-29 23:34:47 +0000
commitfcc0c71d8fc0121e948992c7b856663292b64b80 (patch)
tree3508bcc071d65ff2ec4d8438852f26274dd313c3 /jetty-client
parente23d08749a4a96fd7150a9a2d6c9bb75b1f24b7f (diff)
downloadorg.eclipse.jetty.project-fcc0c71d8fc0121e948992c7b856663292b64b80.tar.gz
org.eclipse.jetty.project-fcc0c71d8fc0121e948992c7b856663292b64b80.tar.xz
org.eclipse.jetty.project-fcc0c71d8fc0121e948992c7b856663292b64b80.zip
Fixes #298502 (Https exchange through an http proxy).
git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2197 7e9141cc-0065-0410-87d8-b60c137991c4
Diffstat (limited to 'jetty-client')
-rw-r--r--jetty-client/pom.xml203
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java42
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java60
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java222
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/ProxyTunnellingTest.java287
5 files changed, 681 insertions, 133 deletions
diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml
index 37d6461b2e..c7ef18c08d 100644
--- a/jetty-client/pom.xml
+++ b/jetty-client/pom.xml
@@ -1,101 +1,104 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <parent>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-project</artifactId>
- <version>7.2.0-SNAPSHOT</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
- <artifactId>jetty-client</artifactId>
- <name>Jetty :: Asynchronous HTTP Client</name>
- <url>{$jetty.url}</url>
- <properties>
- <bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
- </properties>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
- <version>${felix.bundle.version}</version>
- <extensions>true</extensions>
- <executions>
- <execution>
- <goals>
- <goal>manifest</goal>
- </goals>
- <configuration>
- <instructions>
- <Import-Package>javax.net.*,*</Import-Package>
- </instructions>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <!--
- Required for OSGI
- -->
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jar-plugin</artifactId>
- <configuration>
- <archive>
- <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
- </archive>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
- <configuration>
- <onlyAnalyze>org.eclipse.jetty.client.*</onlyAnalyze>
- </configuration>
- </plugin>
- </plugins>
- </build>
- <dependencies>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-http</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-server</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-security</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>${junit4-version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-servlet</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
- <!--
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-servlet</artifactId>
- <version>${project.version}</version>
- <type>jar</type>
- <scope>test</scope>
- </dependency>
- -->
- <dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-websocket</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-project</artifactId>
+ <version>7.2.0-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>jetty-client</artifactId>
+ <name>Jetty :: Asynchronous HTTP Client</name>
+ <url>{$jetty.url}</url>
+
+ <properties>
+ <bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>${felix.bundle.version}</version>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ <configuration>
+ <instructions>
+ <Import-Package>javax.net.*,*</Import-Package>
+ </instructions>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <!--
+ Required for OSGI
+ -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <configuration>
+ <onlyAnalyze>org.eclipse.jetty.client.*</onlyAnalyze>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-security</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlets</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-websocket</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit4-version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
</project>
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
index 9244314355..05253c0612 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
@@ -36,7 +36,6 @@ import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.View;
-import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
import org.eclipse.jetty.io.nio.SslSelectChannelEndPoint;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.thread.Timeout;
@@ -209,7 +208,7 @@ public class HttpConnection implements Connection
if (_exchange == null)
continue;
}
-
+
long flushed = _generator.flushBuffer();
io += flushed;
@@ -236,7 +235,7 @@ public class HttpConnection implements Connection
}
else
_generator.complete();
- }
+ }
else
_generator.complete();
}
@@ -254,7 +253,7 @@ public class HttpConnection implements Connection
long filled = _parser.parseAvailable();
io += filled;
}
-
+
if (io > 0)
no_progress = 0;
else if (no_progress++ >= 2 && !_endp.isBlocking())
@@ -342,12 +341,12 @@ public class HttpConnection implements Connection
HttpExchange exchange=_exchange;
_exchange.disassociate();
_exchange = null;
-
+
if (_status==HttpStatus.SWITCHING_PROTOCOLS_101)
{
Connection switched=exchange.onSwitchProtocol(_endp);
if (switched!=null)
- {
+ {
// switched protocol!
exchange = _pipeline;
_pipeline = null;
@@ -394,13 +393,13 @@ public class HttpConnection implements Connection
{
_exchange.disassociate();
}
-
+
if (!_generator.isComplete() && _generator.getBytesBuffered()>0 && _endp instanceof AsyncEndPoint)
- {
+ {
((AsyncEndPoint)_endp).setWritable(false);
}
}
-
+
return this;
}
@@ -436,18 +435,27 @@ public class HttpConnection implements Connection
_exchange.setStatus(HttpExchange.STATUS_SENDING_REQUEST);
_generator.setVersion(_exchange.getVersion());
+ String method=_exchange.getMethod();
String uri = _exchange.getURI();
- if (_destination.isProxied() && uri.startsWith("/"))
+ if (_destination.isProxied() && !HttpMethods.CONNECT.equals(method) && uri.startsWith("/"))
{
- // TODO suppress port 80 or 443
- uri = (_destination.isSecure()?HttpSchemes.HTTPS:HttpSchemes.HTTP) + "://" + _destination.getAddress().getHost() + ":"
- + _destination.getAddress().getPort() + uri;
+ boolean secure = _destination.isSecure();
+ String host = _destination.getAddress().getHost();
+ int port = _destination.getAddress().getPort();
+ StringBuilder absoluteURI = new StringBuilder();
+ absoluteURI.append(secure ? HttpSchemes.HTTPS : HttpSchemes.HTTP);
+ absoluteURI.append("://");
+ absoluteURI.append(host);
+ // Avoid adding default ports
+ if (!(secure && port == 443 || !secure && port == 80))
+ absoluteURI.append(":").append(port);
+ absoluteURI.append(uri);
+ uri = absoluteURI.toString();
Authentication auth = _destination.getProxyAuthentication();
if (auth != null)
auth.setCredentials(_exchange);
}
- String method=_exchange.getMethod();
_generator.setRequest(method, uri);
_parser.setHeadResponse(HttpMethods.HEAD.equalsIgnoreCase(method));
@@ -594,10 +602,10 @@ public class HttpConnection implements Connection
public void close() throws IOException
{
- //if there is a live, unfinished exchange, set its status to be
+ //if there is a live, unfinished exchange, set its status to be
//excepted and wake up anyone waiting on waitForDone()
-
- if (_exchange != null && !_exchange.isDone())
+
+ if (_exchange != null && !_exchange.isDone())
{
switch (_exchange.getStatus())
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
index b885828022..590179c5f3 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
@@ -12,9 +12,9 @@
// ========================================================================
package org.eclipse.jetty.client;
-
import java.io.IOException;
import java.lang.reflect.Constructor;
+import java.net.ConnectException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -25,9 +25,12 @@ import org.eclipse.jetty.client.security.Authentication;
import org.eclipse.jetty.client.security.SecurityListener;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.PathMap;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.log.Log;
/**
@@ -307,7 +310,7 @@ public class HttpDestination
}
}
- public void onNewConnection(HttpConnection connection) throws IOException
+ public void onNewConnection(final HttpConnection connection) throws IOException
{
HttpConnection q_connection = null;
@@ -328,8 +331,20 @@ public class HttpDestination
}
else
{
- HttpExchange ex = _queue.removeFirst();
- send(connection, ex);
+ EndPoint endPoint = connection.getEndPoint();
+ if (isProxied() && endPoint instanceof SelectConnector.ProxySelectChannelEndPoint)
+ {
+ SelectConnector.ProxySelectChannelEndPoint proxyEndPoint = (SelectConnector.ProxySelectChannelEndPoint)endPoint;
+ HttpExchange exchange = _queue.peekFirst();
+ ConnectExchange connect = new ConnectExchange(getAddress(), proxyEndPoint, exchange);
+ connect.setAddress(getProxy());
+ send(connection, connect);
+ }
+ else
+ {
+ HttpExchange exchange = _queue.removeFirst();
+ send(connection, exchange);
+ }
}
}
@@ -580,4 +595,41 @@ public class HttpDestination
}
}
}
+
+ private class ConnectExchange extends ContentExchange
+ {
+ private final SelectConnector.ProxySelectChannelEndPoint proxyEndPoint;
+ private final HttpExchange exchange;
+
+ public ConnectExchange(Address serverAddress, SelectConnector.ProxySelectChannelEndPoint proxyEndPoint, HttpExchange exchange)
+ {
+ this.proxyEndPoint = proxyEndPoint;
+ this.exchange = exchange;
+ setMethod(HttpMethods.CONNECT);
+ String serverHostAndPort = serverAddress.toString();
+ setURI(serverHostAndPort);
+ addRequestHeader(HttpHeaders.HOST, serverHostAndPort);
+ addRequestHeader(HttpHeaders.PROXY_CONNECTION, "keep-alive");
+ addRequestHeader(HttpHeaders.USER_AGENT, "Jetty-Client");
+ }
+
+ @Override
+ protected void onResponseComplete() throws IOException
+ {
+ if (getResponseStatus() == HttpStatus.OK_200)
+ {
+ proxyEndPoint.upgrade();
+ }
+ else
+ {
+ onConnectionFailed(new ConnectException(exchange.getAddress().toString()));
+ }
+ }
+
+ @Override
+ protected void onConnectionFailed(Throwable x)
+ {
+ HttpDestination.this.onConnectionFailed(x);
+ }
+ }
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java
index 24e714b803..cb58420caf 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java
@@ -16,20 +16,18 @@ package org.eclipse.jetty.client;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.nio.channels.SelectionKey;
-import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
-import org.eclipse.jetty.http.HttpMethods;
-import org.eclipse.jetty.http.HttpVersions;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.ConnectedEndPoint;
import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ThreadLocalBuffers;
import org.eclipse.jetty.io.nio.DirectNIOBuffer;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
@@ -218,14 +216,14 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector,
{
if (dest.isProxied())
{
- String connect = HttpMethods.CONNECT+" "+dest.getAddress()+HttpVersions.HTTP_1_0+"\r\n\r\n";
- // TODO need to send this over channel unencrypted and setup endpoint to ignore the 200 OK response.
-
- throw new IllegalStateException("Not Implemented");
+ SSLEngine engine=newSslEngine();
+ ep = new ProxySelectChannelEndPoint(channel,selectSet,key,_sslBuffers,engine);
+ }
+ else
+ {
+ SSLEngine engine=newSslEngine();
+ ep = new SslSelectChannelEndPoint(_sslBuffers,channel,selectSet,key,engine);
}
-
- SSLEngine engine=newSslEngine();
- ep = new SslSelectChannelEndPoint(_sslBuffers,channel,selectSet,key,engine);
}
else
{
@@ -283,4 +281,204 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector,
}
}
}
+
+ /**
+ * An endpoint that is able to "upgrade" from a normal endpoint to a SSL endpoint.
+ * Since {@link HttpParser} and {@link HttpGenerator} only depend on the {@link EndPoint}
+ * interface, this class overrides all methods of {@link EndPoint} to provide the right
+ * behavior depending on the fact that it has been upgraded or not.
+ */
+ public static class ProxySelectChannelEndPoint extends SslSelectChannelEndPoint
+ {
+ private final SelectChannelEndPoint plainEndPoint;
+ private volatile boolean upgraded = false;
+
+ public ProxySelectChannelEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, Buffers sslBuffers, SSLEngine engine) throws IOException
+ {
+ super(sslBuffers, channel, selectSet, key, engine);
+ this.plainEndPoint = new SelectChannelEndPoint(channel, selectSet, key);
+ }
+
+ public void upgrade()
+ {
+ upgraded = true;
+ }
+
+ public void shutdownOutput() throws IOException
+ {
+ if (upgraded)
+ super.shutdownOutput();
+ else
+ plainEndPoint.shutdownOutput();
+ }
+
+ public void close() throws IOException
+ {
+ if (upgraded)
+ super.close();
+ else
+ plainEndPoint.close();
+ }
+
+ public int fill(Buffer buffer) throws IOException
+ {
+ if (upgraded)
+ return super.fill(buffer);
+ else
+ return plainEndPoint.fill(buffer);
+ }
+
+ public int flush(Buffer buffer) throws IOException
+ {
+ if (upgraded)
+ return super.flush(buffer);
+ else
+ return plainEndPoint.flush(buffer);
+ }
+
+ public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
+ {
+ if (upgraded)
+ return super.flush(header, buffer, trailer);
+ else
+ return plainEndPoint.flush(header, buffer, trailer);
+ }
+
+ public String getLocalAddr()
+ {
+ if (upgraded)
+ return super.getLocalAddr();
+ else
+ return plainEndPoint.getLocalAddr();
+ }
+
+ public String getLocalHost()
+ {
+ if (upgraded)
+ return super.getLocalHost();
+ else
+ return plainEndPoint.getLocalHost();
+ }
+
+ public int getLocalPort()
+ {
+ if (upgraded)
+ return super.getLocalPort();
+ else
+ return plainEndPoint.getLocalPort();
+ }
+
+ public String getRemoteAddr()
+ {
+ if (upgraded)
+ return super.getRemoteAddr();
+ else
+ return plainEndPoint.getRemoteAddr();
+ }
+
+ public String getRemoteHost()
+ {
+ if (upgraded)
+ return super.getRemoteHost();
+ else
+ return plainEndPoint.getRemoteHost();
+ }
+
+ public int getRemotePort()
+ {
+ if (upgraded)
+ return super.getRemotePort();
+ else
+ return plainEndPoint.getRemotePort();
+ }
+
+ public boolean isBlocking()
+ {
+ if (upgraded)
+ return super.isBlocking();
+ else
+ return plainEndPoint.isBlocking();
+ }
+
+ public boolean isBufferred()
+ {
+ if (upgraded)
+ return super.isBufferred();
+ else
+ return plainEndPoint.isBufferred();
+ }
+
+ public boolean blockReadable(long millisecs) throws IOException
+ {
+ if (upgraded)
+ return super.blockReadable(millisecs);
+ else
+ return plainEndPoint.blockReadable(millisecs);
+ }
+
+ public boolean blockWritable(long millisecs) throws IOException
+ {
+ if (upgraded)
+ return super.blockWritable(millisecs);
+ else
+ return plainEndPoint.blockWritable(millisecs);
+ }
+
+ public boolean isOpen()
+ {
+ if (upgraded)
+ return super.isOpen();
+ else
+ return plainEndPoint.isOpen();
+ }
+
+ public Object getTransport()
+ {
+ if (upgraded)
+ return super.getTransport();
+ else
+ return plainEndPoint.getTransport();
+ }
+
+ public boolean isBufferingInput()
+ {
+ if (upgraded)
+ return super.isBufferingInput();
+ else
+ return plainEndPoint.isBufferingInput();
+ }
+
+ public boolean isBufferingOutput()
+ {
+ if (upgraded)
+ return super.isBufferingOutput();
+ else
+ return plainEndPoint.isBufferingOutput();
+ }
+
+ public void flush() throws IOException
+ {
+ if (upgraded)
+ super.flush();
+ else
+ plainEndPoint.flush();
+
+ }
+
+ public int getMaxIdleTime()
+ {
+ if (upgraded)
+ return super.getMaxIdleTime();
+ else
+ return plainEndPoint.getMaxIdleTime();
+ }
+
+ public void setMaxIdleTime(int timeMs) throws IOException
+ {
+ if (upgraded)
+ super.setMaxIdleTime(timeMs);
+ else
+ plainEndPoint.setMaxIdleTime(timeMs);
+ }
+ }
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyTunnellingTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyTunnellingTest.java
new file mode 100644
index 0000000000..f0c864789f
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyTunnellingTest.java
@@ -0,0 +1,287 @@
+package org.eclipse.jetty.client;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.ProxyHandler;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlets.ProxyServlet;
+import org.junit.After;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ProxyTunnellingTest
+{
+ private Server server;
+ private Connector serverConnector;
+ private Server proxy;
+ private Connector proxyConnector;
+
+ private void startSSLServer(Handler handler) throws Exception
+ {
+ SslSelectChannelConnector connector = new SslSelectChannelConnector();
+ String keyStorePath = System.getProperty("basedir");
+ assertNotNull(keyStorePath);
+ keyStorePath += File.separator + "src" + File.separator + "test" + File.separator + "resources" + File.separator + "keystore";
+ connector.setKeystore(keyStorePath);
+ connector.setPassword("storepwd");
+ connector.setKeyPassword("keypwd");
+ startServer(connector, handler);
+ }
+
+ private void startServer(Connector connector, Handler handler) throws Exception
+ {
+ server = new Server();
+ serverConnector = connector;
+ server.addConnector(serverConnector);
+ server.setHandler(handler);
+ server.start();
+ }
+
+ private void startProxy() throws Exception
+ {
+ proxy = new Server();
+ proxyConnector = new SelectChannelConnector();
+ proxy.addConnector(proxyConnector);
+ ProxyHandler proxyHandler = new ProxyHandler();
+ proxy.setHandler(proxyHandler);
+ HandlerCollection handlers = new HandlerCollection();
+ proxyHandler.setHandler(handlers);
+ ServletContextHandler context = new ServletContextHandler(handlers, "/", ServletContextHandler.SESSIONS);
+ ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class);
+ context.addServlet(proxyServlet, "/*");
+ proxy.start();
+ }
+
+ @After
+ public void stop() throws Exception
+ {
+ stopProxy();
+ stopServer();
+ }
+
+ private void stopServer() throws Exception
+ {
+ server.stop();
+ server.join();
+ }
+
+ private void stopProxy() throws Exception
+ {
+ proxy.stop();
+ proxy.join();
+ }
+
+
+ @Test
+ public void testNoSSL() throws Exception
+ {
+ startServer(new SelectChannelConnector(), new ServerHandler());
+ startProxy();
+
+ HttpClient httpClient = new HttpClient();
+ httpClient.setProxy(new Address("localhost", proxyConnector.getLocalPort()));
+ httpClient.start();
+
+ try
+ {
+ ContentExchange exchange = new ContentExchange(true);
+ String body = "BODY";
+ exchange.setURL("http://localhost:" + serverConnector.getLocalPort() + "/echo?body=" + URLEncoder.encode(body, "UTF-8"));
+ exchange.setMethod(HttpMethods.GET);
+
+ httpClient.send(exchange);
+ assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
+ String content = exchange.getResponseContent();
+ assertEquals(body, content);
+ }
+ finally
+ {
+ httpClient.stop();
+ }
+ }
+
+ @Test
+ public void testOneMessageSSL() throws Exception
+ {
+ startSSLServer(new ServerHandler());
+ startProxy();
+
+ HttpClient httpClient = new HttpClient();
+ httpClient.setProxy(new Address("localhost", proxyConnector.getLocalPort()));
+ httpClient.start();
+
+ try
+ {
+ ContentExchange exchange = new ContentExchange(true);
+ exchange.setMethod(HttpMethods.GET);
+ String body = "BODY";
+ exchange.setURL("https://localhost:" + serverConnector.getLocalPort() + "/echo?body=" + URLEncoder.encode(body, "UTF-8"));
+
+ httpClient.send(exchange);
+ assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
+ String content = exchange.getResponseContent();
+ assertEquals(body, content);
+ }
+ finally
+ {
+ httpClient.stop();
+ }
+ }
+
+ @Test
+ public void testTwoMessagesSSL() throws Exception
+ {
+ startSSLServer(new ServerHandler());
+ startProxy();
+
+ HttpClient httpClient = new HttpClient();
+ httpClient.setProxy(new Address("localhost", proxyConnector.getLocalPort()));
+ httpClient.start();
+
+ try
+ {
+ ContentExchange exchange = new ContentExchange(true);
+ exchange.setMethod(HttpMethods.GET);
+ String body = "BODY";
+ exchange.setURL("https://localhost:" + serverConnector.getLocalPort() + "/echo?body=" + URLEncoder.encode(body, "UTF-8"));
+
+ httpClient.send(exchange);
+ assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
+ String content = exchange.getResponseContent();
+ assertEquals(body, content);
+
+ exchange = new ContentExchange(true);
+ exchange.setMethod(HttpMethods.POST);
+ exchange.setURL("https://localhost:" + serverConnector.getLocalPort() + "/echo");
+ exchange.setRequestHeader(HttpHeaders.CONTENT_TYPE, MimeTypes.FORM_ENCODED);
+ content = "body=" + body;
+ exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(content.length()));
+ exchange.setRequestContent(new ByteArrayBuffer(content, "UTF-8"));
+
+ httpClient.send(exchange);
+ assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
+ content = exchange.getResponseContent();
+ assertEquals(body, content);
+ }
+ finally
+ {
+ httpClient.stop();
+ }
+ }
+
+ @Test
+ public void testProxyDown() throws Exception
+ {
+ startSSLServer(new ServerHandler());
+ startProxy();
+ int proxyPort = proxyConnector.getLocalPort();
+ stopProxy();
+
+ HttpClient httpClient = new HttpClient();
+ httpClient.setProxy(new Address("localhost", proxyPort));
+ httpClient.start();
+
+ try
+ {
+ final CountDownLatch latch = new CountDownLatch(1);
+ ContentExchange exchange = new ContentExchange(true)
+ {
+ @Override
+ protected void onConnectionFailed(Throwable x)
+ {
+ latch.countDown();
+ }
+ };
+ exchange.setMethod(HttpMethods.GET);
+ String body = "BODY";
+ exchange.setURL("https://localhost:" + serverConnector.getLocalPort() + "/echo?body=" + URLEncoder.encode(body, "UTF-8"));
+
+ httpClient.send(exchange);
+ assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
+ }
+ finally
+ {
+ httpClient.stop();
+ }
+ }
+
+ @Test
+ public void testServerDown() throws Exception
+ {
+ startSSLServer(new ServerHandler());
+ int serverPort = serverConnector.getLocalPort();
+ stopServer();
+ startProxy();
+
+ HttpClient httpClient = new HttpClient();
+ httpClient.setProxy(new Address("localhost", proxyConnector.getLocalPort()));
+ httpClient.start();
+
+ try
+ {
+ final CountDownLatch latch = new CountDownLatch(1);
+ ContentExchange exchange = new ContentExchange(true)
+ {
+ @Override
+ protected void onConnectionFailed(Throwable x)
+ {
+ latch.countDown();
+ }
+ };
+ exchange.setMethod(HttpMethods.GET);
+ String body = "BODY";
+ exchange.setURL("https://localhost:" + serverPort + "/echo?body=" + URLEncoder.encode(body, "UTF-8"));
+
+ httpClient.send(exchange);
+ assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
+ }
+ finally
+ {
+ httpClient.stop();
+ }
+ }
+
+ private static class ServerHandler extends AbstractHandler
+ {
+ public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+ {
+ request.setHandled(true);
+
+ String uri = httpRequest.getRequestURI();
+ if ("/echo".equals(uri))
+ {
+ String body = httpRequest.getParameter("body");
+ ServletOutputStream output = httpResponse.getOutputStream();
+ output.print(body);
+ }
+ else
+ {
+ throw new ServletException();
+ }
+ }
+ }
+}

Back to the top