diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxyHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxyHandler.java
index ae0669844b5..bf466147950 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxyHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxyHandler.java
@@ -19,6 +19,7 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
import org.eclipse.jetty.io.nio.SelectorManager;
+import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
@@ -36,32 +37,23 @@ import org.eclipse.jetty.util.thread.ThreadPool;
*
* @version $Revision$ $Date$
*/
-public class ProxyHandler extends AbstractHandler
+public class ProxyHandler extends HandlerWrapper
{
private final Logger _logger = Log.getLogger(getClass().getName());
private final SelectorManager _selectorManager = new Manager();
- private final String _serverAddress;
private volatile int _connectTimeout = 5000;
private volatile int _writeTimeout = 30000;
private volatile ThreadPool _threadPool;
private volatile boolean _privateThreadPool;
- /**
- *
Constructor to be used to make this proxy work via HTTP CONNECT.
- */
public ProxyHandler()
{
this(null);
}
- /**
- * Constructor to be used to make this proxy work as transparent proxy.
- *
- * @param serverAddress the address of the remote server in the form {@code host:port}
- */
- public ProxyHandler(String serverAddress)
+ public ProxyHandler(Handler handler)
{
- _serverAddress = serverAddress;
+ setHandler(handler);
}
/**
@@ -102,7 +94,7 @@ public class ProxyHandler extends AbstractHandler
super.setServer(server);
server.getContainer().update(this,null,_selectorManager,"selectManager");
-
+
if (_privateThreadPool)
server.getContainer().update(this,null,_privateThreadPool,"threadpool",true);
else
@@ -118,7 +110,7 @@ public class ProxyHandler extends AbstractHandler
}
/**
- * @param the thread pool
+ * @param threadPool the thread pool
*/
public void setThreadPool(ThreadPool threadPool)
{
@@ -173,24 +165,22 @@ public class ProxyHandler extends AbstractHandler
super.doStop();
}
+ @Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
if (HttpMethods.CONNECT.equalsIgnoreCase(request.getMethod()))
{
_logger.debug("CONNECT request for {}", request.getRequestURI(), null);
- handle(request, response, request.getRequestURI());
+ handleConnect(request, response, request.getRequestURI());
}
else
{
- _logger.debug("Plain request for {}", _serverAddress, null);
- if (_serverAddress == null)
- throw new ServletException("Parameter 'serverAddress' cannot be null");
- handle(request, response, _serverAddress);
+ super.handle(target, baseRequest, request, response);
}
}
/**
- * Handles a tunnelling request, either a CONNECT or a transparent request.
+ * Handles a CONNECT request.
* CONNECT requests may have authentication headers such as Proxy-Authorization
* that authenticate the client with the proxy.
*
@@ -200,7 +190,7 @@ public class ProxyHandler extends AbstractHandler
* @throws ServletException if an application error occurs
* @throws IOException if an I/O error occurs
*/
- protected void handle(HttpServletRequest request, HttpServletResponse response, String serverAddress) throws ServletException, IOException
+ protected void handleConnect(HttpServletRequest request, HttpServletResponse response, String serverAddress) throws ServletException, IOException
{
boolean proceed = handleAuthentication(request, response, serverAddress);
if (!proceed)
@@ -208,51 +198,14 @@ public class ProxyHandler extends AbstractHandler
String host = serverAddress;
int port = 80;
- boolean secure = false;
int colon = serverAddress.indexOf(':');
if (colon > 0)
{
host = serverAddress.substring(0, colon);
port = Integer.parseInt(serverAddress.substring(colon + 1));
- secure = isTunnelSecure(host, port);
}
- setupTunnel(request, response, host, port, secure);
- }
-
- /**
- * Returns whether the given {@code host} and {@code port} identify a SSL communication channel.
- *
Default implementation returns true if the {@code port} is 443.
- *
- * @param host the host to connect to
- * @param port the port to connect to
- * @return true if the communication channel is confidential, false otherwise
- */
- protected boolean isTunnelSecure(String host, int port)
- {
- return port == 443;
- }
-
- /**
- * Handles the authentication before setting up the tunnel to the remote server.
- * The default implementation returns true.
- *
- * @param request the HTTP request
- * @param response the HTTP response
- * @param address the address of the remote server in the form {@code host:port}.
- * @return true to allow to connect to the remote host, false otherwise
- * @throws ServletException to report a server error to the caller
- * @throws IOException to report a server error to the caller
- */
- protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException
- {
- return true;
- }
-
- protected void setupTunnel(HttpServletRequest request, HttpServletResponse response, String host, int port, boolean secure) throws IOException
- {
- SocketChannel channel = connect(request, host, port);
- channel.configureBlocking(false);
+ SocketChannel channel = connectToServer(request, host, port);
// Transfer unread data from old connection to new connection
// We need to copy the data to avoid races:
@@ -280,14 +233,40 @@ public class ProxyHandler extends AbstractHandler
}
}
- // Setup connections, before registering the channel to avoid races
- // where the server sends data before the connections are set up
- ProxyToServerConnection proxyToServer = newProxyToServerConnection(secure, buffer);
+ ClientToProxyConnection clientToProxy = prepareConnections(channel, buffer);
+
+ // CONNECT expects a 200 response
+ response.setStatus(HttpServletResponse.SC_OK);
+ // Flush it so that the client receives it
+ response.flushBuffer();
+
+ upgradeConnection(request, response, clientToProxy);
+ }
+
+ private ClientToProxyConnection prepareConnections(SocketChannel channel, Buffer buffer)
+ {
+ HttpConnection httpConnection = HttpConnection.getCurrentConnection();
+ ProxyToServerConnection proxyToServer = newProxyToServerConnection(buffer);
ClientToProxyConnection clientToProxy = newClientToProxyConnection(channel, httpConnection.getEndPoint(), httpConnection.getTimeStamp());
clientToProxy.setConnection(proxyToServer);
proxyToServer.setConnection(clientToProxy);
+ return clientToProxy;
+ }
- upgradeConnection(request, response, clientToProxy);
+ /**
+ * Handles the authentication before setting up the tunnel to the remote server.
+ * The default implementation returns true.
+ *
+ * @param request the HTTP request
+ * @param response the HTTP response
+ * @param address the address of the remote server in the form {@code host:port}.
+ * @return true to allow to connect to the remote host, false otherwise
+ * @throws ServletException to report a server error to the caller
+ * @throws IOException to report a server error to the caller
+ */
+ protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException
+ {
+ return true;
}
protected ClientToProxyConnection newClientToProxyConnection(SocketChannel channel, EndPoint endPoint, long timeStamp)
@@ -295,9 +274,16 @@ public class ProxyHandler extends AbstractHandler
return new ClientToProxyConnection(channel, endPoint, timeStamp);
}
- protected ProxyToServerConnection newProxyToServerConnection(boolean secure, IndirectNIOBuffer buffer)
+ protected ProxyToServerConnection newProxyToServerConnection(Buffer buffer)
{
- return new ProxyToServerConnection(secure, buffer);
+ return new ProxyToServerConnection(buffer);
+ }
+
+ private SocketChannel connectToServer(HttpServletRequest request, String host, int port) throws IOException
+ {
+ SocketChannel channel = connect(request, host, port);
+ channel.configureBlocking(false);
+ return channel;
}
/**
@@ -321,10 +307,6 @@ public class ProxyHandler extends AbstractHandler
private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection) throws IOException
{
- // CONNECT expects a 200 response
- response.setStatus(HttpServletResponse.SC_OK);
- // Flush it so that the client receives it
- response.flushBuffer();
// Set the new connection as request attribute and change the status to 101
// so that Jetty understands that it has to upgrade the connection
request.setAttribute("org.eclipse.jetty.io.Connection", connection);
@@ -358,10 +340,10 @@ public class ProxyHandler extends AbstractHandler
* @param buffer the buffer to write
* @throws IOException if the buffer cannot be written
*/
- protected void write(EndPoint endPoint, Buffer buffer) throws IOException
+ protected int write(EndPoint endPoint, Buffer buffer) throws IOException
{
if (buffer == null)
- return;
+ return 0;
int length = buffer.length();
StringBuilder builder = new StringBuilder();
@@ -382,6 +364,7 @@ public class ProxyHandler extends AbstractHandler
}
}
_logger.debug("Written {}/{} bytes " + endPoint, builder, length);
+ return length;
}
private class Manager extends SelectorManager
@@ -396,16 +379,7 @@ public class ProxyHandler extends AbstractHandler
@Override
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey selectionKey) throws IOException
{
- ProxyToServerConnection proxyToServer = (ProxyToServerConnection)selectionKey.attachment();
- if (proxyToServer._secure)
- {
- throw new UnsupportedOperationException();
-// return new SslSelectChannelEndPoint(???, channel, selectSet, selectionKey, sslContext.createSSLEngine(address.host, address.port));
- }
- else
- {
- return new SelectChannelEndPoint(channel, selectSet, selectionKey);
- }
+ return new SelectChannelEndPoint(channel, selectSet, selectionKey);
}
@Override
@@ -445,46 +419,64 @@ public class ProxyHandler extends AbstractHandler
{
private final CountDownLatch _ready = new CountDownLatch(1);
private final Buffer _buffer = new IndirectNIOBuffer(1024);
- private final boolean _secure;
private volatile Buffer _data;
private volatile ClientToProxyConnection _toClient;
private volatile long _timestamp;
private volatile SelectChannelEndPoint _endPoint;
- public ProxyToServerConnection(boolean secure, Buffer data)
+ public ProxyToServerConnection(Buffer data)
{
- _secure = secure;
_data = data;
}
public Connection handle() throws IOException
{
- _logger.debug("ProxyToServer: handle entered");
- if (_data != null)
+ _logger.debug("ProxyToServer: begin reading from server");
+ try
{
- write(_endPoint, _data);
- _data = null;
- }
-
- while (true)
- {
- int read = read(_endPoint, _buffer);
-
- if (read == -1)
+ if (_data != null)
{
- _logger.debug("ProxyToServer: closed connection {}", _endPoint, null);
- _toClient.close();
- break;
+ int written = write(_endPoint, _data);
+ _logger.debug("ProxyToServer: written to server {} bytes", written, null);
+ _data = null;
}
- if (read == 0)
- break;
+ while (true)
+ {
+ int read = read(_endPoint, _buffer);
- _logger.debug("ProxyToServer: read {} bytes {}", read, _endPoint);
- write(_toClient._endPoint, _buffer);
+ if (read == -1)
+ {
+ _logger.debug("ProxyToServer: server closed connection {}", _endPoint, null);
+ close();
+ break;
+ }
+
+ if (read == 0)
+ break;
+
+ _logger.debug("ProxyToServer: read from server {} bytes {}", read, _endPoint);
+ int written = write(_toClient._endPoint, _buffer);
+ _logger.debug("ProxyToServer: written to client {} bytes", written, null);
+ }
+ return this;
+ }
+ catch (IOException x)
+ {
+ _logger.warn("ProxyToServer: Unexpected exception", x);
+ close();
+ throw x;
+ }
+ catch (RuntimeException x)
+ {
+ _logger.warn("ProxyToServer: Unexpected exception", x);
+ close();
+ throw x;
+ }
+ finally
+ {
+ _logger.debug("ProxyToServer: end reading from server");
}
- _logger.debug("ProxyToServer: handle exited");
- return this;
}
public void setConnection(ClientToProxyConnection connection)
@@ -505,6 +497,7 @@ public class ProxyHandler extends AbstractHandler
public void setEndPoint(SelectChannelEndPoint endpoint)
{
_endPoint = endpoint;
+ _logger.debug("ProxyToServer: {}", _endPoint, null);
}
public boolean isIdle()
@@ -534,10 +527,36 @@ public class ProxyHandler extends AbstractHandler
}
}
- public void close() throws IOException
+ public void closeClient() throws IOException
+ {
+ _toClient.closeClient();
+ }
+
+ public void closeServer() throws IOException
{
_endPoint.close();
}
+
+ public void close()
+ {
+ try
+ {
+ closeClient();
+ }
+ catch (IOException x)
+ {
+ _logger.debug("ProxyToServer: Unexpected exception closing the client", x);
+ }
+
+ try
+ {
+ closeServer();
+ }
+ catch (IOException x)
+ {
+ _logger.debug("ProxyToServer: Unexpected exception closing the server", x);
+ }
+ }
}
public class ClientToProxyConnection implements Connection
@@ -554,37 +573,57 @@ public class ProxyHandler extends AbstractHandler
_channel = channel;
_endPoint = endPoint;
_timestamp = timestamp;
+ _logger.debug("ClientToProxy: {}", _endPoint, null);
}
public Connection handle() throws IOException
{
- _logger.debug("ClientToProxy: handle entered");
-
- if (_firstTime)
+ _logger.debug("ClientToProxy: begin reading from client");
+ try
{
- _firstTime = false;
- register(_channel, _toServer);
- }
-
- while (true)
- {
- int read = read(_endPoint, _buffer);
-
- if (read == -1)
+ if (_firstTime)
{
- _logger.debug("ClientToProxy: closed connection {}", _endPoint, null);
- _toServer.close();
- break;
+ _firstTime = false;
+ register(_channel, _toServer);
+ _logger.debug("ClientToProxy: registered channel {} with connection {}", _channel, _toServer);
}
- if (read == 0)
- break;
+ while (true)
+ {
+ int read = read(_endPoint, _buffer);
- _logger.debug("ClientToProxy: read {} bytes {}", read, _endPoint);
- write(_toServer._endPoint, _buffer);
+ if (read == -1)
+ {
+ _logger.debug("ClientToProxy: client closed connection {}", _endPoint, null);
+ close();
+ break;
+ }
+
+ if (read == 0)
+ break;
+
+ _logger.debug("ClientToProxy: read from client {} bytes {}", read, _endPoint);
+ int written = write(_toServer._endPoint, _buffer);
+ _logger.debug("ClientToProxy: written to server {} bytes", written, null);
+ }
+ return this;
+ }
+ catch (IOException x)
+ {
+ _logger.warn("ClientToProxy: Unexpected exception", x);
+ close();
+ throw x;
+ }
+ catch (RuntimeException x)
+ {
+ _logger.warn("ClientToProxy: Unexpected exception", x);
+ close();
+ throw x;
+ }
+ finally
+ {
+ _logger.debug("ClientToProxy: end reading from client");
}
- _logger.debug("ClientToProxy: handle exited");
- return this;
}
public long getTimeStamp()
@@ -607,9 +646,35 @@ public class ProxyHandler extends AbstractHandler
_toServer = connection;
}
- public void close() throws IOException
+ public void closeClient() throws IOException
{
_endPoint.close();
}
+
+ public void closeServer() throws IOException
+ {
+ _toServer.closeServer();
+ }
+
+ public void close()
+ {
+ try
+ {
+ closeClient();
+ }
+ catch (IOException x)
+ {
+ _logger.debug("ClientToProxy: Unexpected exception closing the client", x);
+ }
+
+ try
+ {
+ closeServer();
+ }
+ catch (IOException x)
+ {
+ _logger.debug("ClientToProxy: Unexpected exception closing the server", x);
+ }
+ }
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AbstractProxyHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AbstractProxyHandlerTest.java
new file mode 100644
index 00000000000..28455014b29
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AbstractProxyHandlerTest.java
@@ -0,0 +1,172 @@
+package org.eclipse.jetty.server.handler;
+
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.Socket;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import junit.framework.TestCase;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class AbstractProxyHandlerTest extends TestCase
+{
+ protected Server server;
+ protected Connector serverConnector;
+ protected Server proxy;
+ protected Connector proxyConnector;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ server = new Server();
+ serverConnector = newServerConnector();
+ server.addConnector(serverConnector);
+ configureServer(server);
+ server.start();
+
+ proxy = new Server();
+ proxyConnector = new SelectChannelConnector();
+ proxy.addConnector(proxyConnector);
+ configureProxy(proxy);
+ proxy.start();
+ }
+
+ protected SelectChannelConnector newServerConnector()
+ {
+ return new SelectChannelConnector();
+ }
+
+ protected void configureServer(Server server)
+ {
+ }
+
+ protected void configureProxy(Server proxy)
+ {
+ proxy.setHandler(new ProxyHandler());
+ }
+
+ @Override
+ protected void tearDown() throws Exception
+ {
+ proxy.stop();
+ proxy.join();
+
+ server.stop();
+ server.join();
+ }
+
+ protected Response readResponse(BufferedReader reader) throws IOException
+ {
+ // Simplified parser for HTTP responses
+ String line = reader.readLine();
+ if (line == null)
+ throw new EOFException();
+ Matcher responseLine = Pattern.compile("HTTP/1\\.1\\s+(\\d+)").matcher(line);
+ assertTrue(responseLine.lookingAt());
+ String code = responseLine.group(1);
+
+ Map headers = new LinkedHashMap();
+ while ((line = reader.readLine()) != null)
+ {
+ if (line.trim().length() == 0)
+ break;
+
+ Matcher header = Pattern.compile("([^:]+):\\s*(.*)").matcher(line);
+ assertTrue(header.lookingAt());
+ String headerName = header.group(1);
+ String headerValue = header.group(2);
+ headers.put(headerName.toLowerCase(), headerValue.toLowerCase());
+ }
+
+ StringBuilder body = new StringBuilder();
+ if (headers.containsKey("content-length"))
+ {
+ int length = Integer.parseInt(headers.get("content-length"));
+ for (int i = 0; i < length; ++i)
+ {
+ char c = (char)reader.read();
+ body.append(c);
+ }
+ }
+ else if ("chunked".equals(headers.get("transfer-encoding")))
+ {
+ while ((line = reader.readLine()) != null)
+ {
+ if ("0".equals(line))
+ {
+ line = reader.readLine();
+ assertEquals("", line);
+ break;
+ }
+
+ int length = Integer.parseInt(line, 16);
+ for (int i = 0; i < length; ++i)
+ {
+ char c = (char)reader.read();
+ body.append(c);
+ }
+ line = reader.readLine();
+ assertEquals("", line);
+ }
+ }
+
+ return new Response(code, headers, body.toString().trim());
+ }
+
+ protected Socket newSocket() throws IOException
+ {
+ Socket socket = new Socket("localhost", proxyConnector.getLocalPort());
+ socket.setSoTimeout(5000);
+ return socket;
+ }
+
+ protected class Response
+ {
+ private final String code;
+ private final Map headers;
+ private final String body;
+
+ private Response(String code, Map headers, String body)
+ {
+ this.code = code;
+ this.headers = headers;
+ this.body = body;
+ }
+
+ public String getCode()
+ {
+ return code;
+ }
+
+ public Map getHeaders()
+ {
+ return headers;
+ }
+
+ public String getBody()
+ {
+ return body;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append(code).append("\r\n");
+ for (Map.Entry entry : headers.entrySet())
+ builder.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
+ builder.append("\r\n");
+ builder.append(body);
+ return builder.toString();
+ }
+ }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerConnectSSLTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerConnectSSLTest.java
new file mode 100644
index 00000000000..3d09103d248
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerConnectSSLTest.java
@@ -0,0 +1,226 @@
+package org.eclipse.jetty.server.handler;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class ProxyHandlerConnectSSLTest extends AbstractProxyHandlerTest
+{
+ @Override
+ protected SelectChannelConnector newServerConnector()
+ {
+ SslSelectChannelConnector connector = new SslSelectChannelConnector();
+
+ String keyStorePath = System.getProperty("basedir");
+ keyStorePath += File.separator + "src" + File.separator + "test" + File.separator + "resources" + File.separator + "keystore";
+ connector.setKeystore(keyStorePath);
+ connector.setPassword("storepwd");
+ connector.setKeyPassword("keypwd");
+
+ return connector;
+ }
+
+ @Override
+ protected void configureServer(Server server)
+ {
+ server.setHandler(new ServerHandler());
+ }
+
+ public void testGETRequest() throws Exception
+ {
+ String hostPort = "localhost:" + serverConnector.getLocalPort();
+ String request = "" +
+ "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "\r\n";
+ Socket socket = newSocket();
+ try
+ {
+ OutputStream output = socket.getOutputStream();
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ // Expect 200 OK from the CONNECT request
+ Response response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+
+ // Be sure the buffered input does not have anything buffered
+ assertFalse(input.ready());
+
+ // Upgrade the socket to SSL
+ SSLSocket sslSocket = wrapSocket(socket);
+ try
+ {
+ output = sslSocket.getOutputStream();
+ input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
+
+ request = "" +
+ "GET /echo HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "\r\n";
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+ assertEquals("GET /echo", response.getBody());
+ }
+ finally
+ {
+ sslSocket.close();
+ }
+ }
+ finally
+ {
+ socket.close();
+ }
+ }
+
+ public void testPOSTRequests() throws Exception
+ {
+ String hostPort = "localhost:" + serverConnector.getLocalPort();
+ String request = "" +
+ "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "\r\n";
+ Socket socket = newSocket();
+ try
+ {
+ OutputStream output = socket.getOutputStream();
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ // Expect 200 OK from the CONNECT request
+ Response response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+
+ // Be sure the buffered input does not have anything buffered
+ assertFalse(input.ready());
+
+ // Upgrade the socket to SSL
+ SSLSocket sslSocket = wrapSocket(socket);
+ try
+ {
+ output = sslSocket.getOutputStream();
+ input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
+
+ for (int i = 0; i < 10; ++i)
+ {
+ request = "" +
+ "POST /echo?param=" + i + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "Content-Length: 5\r\n" +
+ "\r\n" +
+ "HELLO";
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+ assertEquals("POST /echo?param=" + i + "\r\nHELLO", response.getBody());
+ }
+ }
+ finally
+ {
+ sslSocket.close();
+ }
+ }
+ finally
+ {
+ socket.close();
+ }
+ }
+
+ private SSLSocket wrapSocket(Socket socket) throws Exception
+ {
+ SSLContext sslContext = SSLContext.getInstance("SSLv3");
+ sslContext.init(null, new TrustManager[]{new AlwaysTrustManager()}, new SecureRandom());
+ SSLSocketFactory socketFactory = sslContext.getSocketFactory();
+ SSLSocket sslSocket = (SSLSocket)socketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
+ sslSocket.setUseClientMode(true);
+ sslSocket.startHandshake();
+ return sslSocket;
+ }
+
+ private class AlwaysTrustManager implements X509TrustManager
+ {
+ public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
+ {
+ }
+
+ public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
+ {
+ }
+
+ public X509Certificate[] getAcceptedIssuers()
+ {
+ return null;
+ }
+ }
+
+ private 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))
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append(httpRequest.getMethod()).append(" ").append(uri);
+ if (httpRequest.getQueryString() != null)
+ builder.append("?").append(httpRequest.getQueryString());
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InputStream input = httpRequest.getInputStream();
+ int read = -1;
+ while ((read = input.read()) >= 0)
+ baos.write(read);
+ baos.close();
+
+ System.err.println("server echoing:\r\n" + builder);
+ ServletOutputStream output = httpResponse.getOutputStream();
+ output.println(builder.toString());
+ output.write(baos.toByteArray());
+ }
+ else
+ {
+ throw new ServletException();
+ }
+ }
+ }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerConnectTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerConnectTest.java
index 3f40fc03016..6ac2ff1d320 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerConnectTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerConnectTest.java
@@ -1,82 +1,51 @@
package org.eclipse.jetty.server.handler;
import java.io.BufferedReader;
-import java.io.EOFException;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import junit.framework.TestCase;
-import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
/**
* @version $Revision$ $Date$
*/
-public class ProxyHandlerConnectTest extends TestCase
+public class ProxyHandlerConnectTest extends AbstractProxyHandlerTest
{
- private Server server;
- private Connector serverConnector;
- private Server proxy;
- private Connector proxyConnector;
-
@Override
- protected void setUp() throws Exception
+ protected void configureServer(Server server)
{
- server = new Server();
- serverConnector = new SelectChannelConnector();
- server.addConnector(serverConnector);
server.setHandler(new ServerHandler());
- server.start();
-
- proxy = new Server();
- proxyConnector = new SelectChannelConnector();
- proxy.addConnector(proxyConnector);
- proxy.setHandler(new ProxyHandler());
- proxy.start();
}
- @Override
- protected void tearDown() throws Exception
- {
- proxy.stop();
- proxy.join();
-
- server.stop();
- server.join();
- }
-
- public void testHttpConnect() throws Exception
+ public void testCONNECT() throws Exception
{
+ String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" +
- "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
+ "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
"\r\n";
- Socket socket = new Socket("localhost", proxyConnector.getLocalPort());
+ Socket socket = newSocket();
try
{
OutputStream output = socket.getOutputStream();
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
output.write(request.getBytes("UTF-8"));
output.flush();
// Expect 200 OK from the CONNECT request
- InputStream input = socket.getInputStream();
Response response = readResponse(input);
System.err.println(response);
- assertEquals("200", response.code);
+ assertEquals("200", response.getCode());
}
finally
{
@@ -84,37 +53,38 @@ public class ProxyHandlerConnectTest extends TestCase
}
}
- public void testHttpConnectWithNormalRequest() throws Exception
+ public void testCONNECTAndGET() throws Exception
{
+ String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" +
- "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
+ "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
"\r\n";
- Socket socket = new Socket("localhost", proxyConnector.getLocalPort());
+ Socket socket = newSocket();
try
{
OutputStream output = socket.getOutputStream();
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
output.write(request.getBytes("UTF-8"));
output.flush();
// Expect 200 OK from the CONNECT request
- InputStream input = socket.getInputStream();
Response response = readResponse(input);
System.err.println(response);
- assertEquals("200", response.code);
+ assertEquals("200", response.getCode());
- String echoURI = "GET /echo";
request = "" +
- echoURI + " HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
+ "GET /echo" + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
"\r\n";
output.write(request.getBytes("UTF-8"));
output.flush();
response = readResponse(input);
System.err.println(response);
- assertEquals("200", response.code);
- assertEquals(echoURI, response.body);
+ assertEquals("200", response.getCode());
+ assertEquals("GET /echo", response.getBody());
}
finally
{
@@ -122,34 +92,35 @@ public class ProxyHandlerConnectTest extends TestCase
}
}
- public void testHttpConnectWithPipelinedRequest() throws Exception
+ public void testCONNECTAndGETPipelined() throws Exception
{
- String pipelinedMethodURI = "GET /echo";
+ String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" +
- "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
+ "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
"\r\n" +
- pipelinedMethodURI + " HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
+ "GET /echo" + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
"\r\n";
- Socket socket = new Socket("localhost", proxyConnector.getLocalPort());
+ Socket socket = newSocket();
try
{
OutputStream output = socket.getOutputStream();
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
output.write(request.getBytes("UTF-8"));
output.flush();
// Expect 200 OK from the CONNECT request
- InputStream input = socket.getInputStream();
Response response = readResponse(input);
System.err.println(response);
- assertEquals("200", response.code);
+ assertEquals("200", response.getCode());
// The pipelined request must have gone up to the server as is
response = readResponse(input);
System.err.println(response);
- assertEquals("200", response.code);
- assertEquals(pipelinedMethodURI, response.body);
+ assertEquals("200", response.getCode());
+ assertEquals("GET /echo", response.getBody());
}
finally
{
@@ -157,24 +128,80 @@ public class ProxyHandlerConnectTest extends TestCase
}
}
- public void testHttpConnectWithNoRequestServerClose() throws Exception
+ public void testCONNECTAndMultipleGETs() throws Exception
{
+ String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" +
- "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
+ "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
"\r\n";
- Socket socket = new Socket("localhost", proxyConnector.getLocalPort());
+ Socket socket = newSocket();
try
{
OutputStream output = socket.getOutputStream();
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
output.write(request.getBytes("UTF-8"));
output.flush();
// Expect 200 OK from the CONNECT request
- InputStream input = socket.getInputStream();
Response response = readResponse(input);
System.err.println(response);
- assertEquals("200", response.code);
+ assertEquals("200", response.getCode());
+
+ for (int i = 0; i < 10; ++i)
+ {
+ request = "" +
+ "GET /echo" + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "\r\n";
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+ assertEquals("GET /echo", response.getBody());
+ }
+ }
+ finally
+ {
+ socket.close();
+ }
+ }
+
+ public void testCONNECTAndGETServerStop() throws Exception
+ {
+ String hostPort = "localhost:" + serverConnector.getLocalPort();
+ String request = "" +
+ "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "\r\n";
+ Socket socket = newSocket();
+ try
+ {
+ OutputStream output = socket.getOutputStream();
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ // Expect 200 OK from the CONNECT request
+ Response response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+
+ request = "" +
+ "GET /echo HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "\r\n";
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+ assertEquals("GET /echo", response.getBody());
// Idle server is shut down
server.stop();
@@ -189,28 +216,30 @@ public class ProxyHandlerConnectTest extends TestCase
}
}
- public void testHttpConnectWithRequestServerClose() throws Exception
+ public void testCONNECTAndGETAndServerSideClose() throws Exception
{
+ String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" +
- "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
+ "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
"\r\n";
- Socket socket = new Socket("localhost", proxyConnector.getLocalPort());
+ Socket socket = newSocket();
try
{
OutputStream output = socket.getOutputStream();
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
output.write(request.getBytes("UTF-8"));
output.flush();
// Expect 200 OK from the CONNECT request
- InputStream input = socket.getInputStream();
Response response = readResponse(input);
System.err.println(response);
- assertEquals("200", response.code);
+ assertEquals("200", response.getCode());
request = "" +
"GET /close HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
+ "Host: " + hostPort + "\r\n" +
"\r\n";
output.write(request.getBytes("UTF-8"));
output.flush();
@@ -224,57 +253,103 @@ public class ProxyHandlerConnectTest extends TestCase
}
}
- private Response readResponse(InputStream input) throws IOException
+ public void testCONNECTAndPOSTAndGET() throws Exception
{
- // Simplified parser for HTTP responses
- BufferedReader reader = new BufferedReader(new InputStreamReader(input));
- String line = reader.readLine();
- if (line == null)
- throw new EOFException();
- Matcher responseLine = Pattern.compile("HTTP/1\\.1\\s+(\\d+)").matcher(line);
- assertTrue(responseLine.lookingAt());
- String code = responseLine.group(1);
-
- Map headers = new LinkedHashMap();
- while ((line = reader.readLine()) != null)
+ String hostPort = "localhost:" + serverConnector.getLocalPort();
+ String request = "" +
+ "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "\r\n";
+ Socket socket = newSocket();
+ try
{
- if (line.trim().length() == 0)
- break;
+ OutputStream output = socket.getOutputStream();
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- Matcher header = Pattern.compile("([^:]+):\\s*(.*)").matcher(line);
- assertTrue(header.lookingAt());
- String headerName = header.group(1);
- String headerValue = header.group(2);
- headers.put(headerName.toLowerCase(), headerValue.toLowerCase());
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ // Expect 200 OK from the CONNECT request
+ Response response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+
+ request = "" +
+ "POST /echo HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "Content-Length: 5\r\n" +
+ "\r\n" +
+ "HELLO";
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+ assertEquals("POST /echo\r\nHELLO", response.getBody());
+
+ request = "" +
+ "GET /echo" + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "\r\n";
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+ assertEquals("GET /echo", response.getBody());
}
-
- StringBuilder body = new StringBuilder();
- if (headers.containsKey("content-length"))
+ finally
{
- int length = Integer.parseInt(headers.get("content-length"));
- for (int i = 0; i < length; ++i)
- body.append((char)reader.read());
+ socket.close();
}
- else if ("chunked".equals(headers.get("transfer-encoding")))
- {
- while ((line = reader.readLine()) != null)
- {
- if ("0".equals(line))
- {
- reader.readLine();
- break;
- }
-
- body.append(reader.readLine());
- reader.readLine();
- }
- }
-
- return new Response(code, headers, body.toString().trim());
}
- public class TestServlet extends HttpServlet
+ public void testCONNECTAndPOSTWithBigBody() throws Exception
{
+ String hostPort = "localhost:" + serverConnector.getLocalPort();
+ String request = "" +
+ "CONNECT " + hostPort + " HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "\r\n";
+ Socket socket = newSocket();
+ try
+ {
+ OutputStream output = socket.getOutputStream();
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ // Expect 200 OK from the CONNECT request
+ Response response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+
+ StringBuilder body = new StringBuilder();
+ String chunk = "0123456789ABCDEF";
+ for (int i = 0; i < 1024; ++i)
+ body.append(chunk);
+
+ request = "" +
+ "POST /echo HTTP/1.1\r\n" +
+ "Host: " + hostPort + "\r\n" +
+ "Content-Length: " + body.length() + "\r\n" +
+ "\r\n" +
+ body;
+ output.write(request.getBytes("UTF-8"));
+ output.flush();
+
+ response = readResponse(input);
+ System.err.println(response);
+ assertEquals("200", response.getCode());
+ assertEquals("POST /echo\r\n" + body, response.getBody());
+ }
+ finally
+ {
+ socket.close();
+ }
}
private class ServerHandler extends AbstractHandler
@@ -288,41 +363,30 @@ public class ProxyHandlerConnectTest extends TestCase
{
StringBuilder builder = new StringBuilder();
builder.append(httpRequest.getMethod()).append(" ").append(uri);
+ if (httpRequest.getQueryString() != null)
+ builder.append("?").append(httpRequest.getQueryString());
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InputStream input = httpRequest.getInputStream();
+ int read = -1;
+ while ((read = input.read()) >= 0)
+ baos.write(read);
+ baos.close();
+
System.err.println("server echoing:\r\n" + builder);
ServletOutputStream output = httpResponse.getOutputStream();
output.println(builder.toString());
+ output.write(baos.toByteArray());
}
else if ("/close".equals(uri))
{
request.getConnection().getEndPoint().close();
System.err.println("server closed");
}
- }
- }
-
- private class Response
- {
- private final String code;
- private final Map headers;
- private final String body;
-
- private Response(String code, Map headers, String body)
- {
- this.code = code;
- this.headers = headers;
- this.body = body;
- }
-
- @Override
- public String toString()
- {
- StringBuilder builder = new StringBuilder();
- builder.append(code).append("\r\n");
- for (Map.Entry entry : headers.entrySet())
- builder.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
- builder.append("\r\n");
- builder.append(body);
- return builder.toString();
+ else
+ {
+ throw new ServletException();
+ }
}
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerSSLTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerSSLTest.java
deleted file mode 100644
index 96f1f64f441..00000000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerSSLTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.eclipse.jetty.server.handler;
-
-import junit.framework.TestCase;
-
-/**
- * @version $Revision$ $Date$
- */
-public class ProxyHandlerSSLTest extends TestCase
-{
-/*
- private Server server;
- private Connector serverConnector;
- private Server proxy;
- private Connector proxyConnector;
-
- @Override
- protected void setUp() throws Exception
- {
- server = new Server();
- serverConnector = new SslSelectChannelConnector();
- server.addConnector(serverConnector);
-// server.setHandler(new EchoHandler());
- server.start();
-
- proxy = new Server();
- proxyConnector = new SslSelectChannelConnector();
- proxy.addConnector(proxyConnector);
- proxy.setHandler(new ProxyHandler()
- {
- @Override
- protected boolean isTunnelSecure(String host, int port)
- {
- return true;
- }
- });
- proxy.start();
- }
-
- @Override
- protected void tearDown() throws Exception
- {
- proxy.stop();
- proxy.join();
-
- server.stop();
- server.join();
- }
-
- public void testHttpConnectWithNormalRequest() throws Exception
- {
- String request = "" +
- "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "\r\n";
- Socket socket = new Socket("localhost", proxyConnector.getLocalPort());
- try
- {
- OutputStream output = socket.getOutputStream();
- output.write(request.getBytes("UTF-8"));
- output.flush();
-
- // Expect 200 OK from the CONNECT request
- InputStream input = socket.getInputStream();
-// Response response = readResponse(input);
-// System.err.println(response);
-// assertEquals("200", response.code);
-
- // Now what ? Upgrade the socket to SSL ?
-
- }
- finally
- {
- socket.close();
- }
- }
-*/
- public void test() {}
-}