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() {} -}