Improvements and tests for #297104.

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@1521 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Simone Bordet 2010-04-16 16:45:14 +00:00
parent e0d07733bd
commit 95430a1b33
5 changed files with 799 additions and 350 deletions

View File

@ -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;
/**
* <p>Constructor to be used to make this proxy work via HTTP CONNECT.</p>
*/
public ProxyHandler()
{
this(null);
}
/**
* <p>Constructor to be used to make this proxy work as transparent proxy.</p>
*
* @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);
}
}
/**
* <p>Handles a tunnelling request, either a CONNECT or a transparent request.</p>
* <p>Handles a CONNECT request.</p>
* <p>CONNECT requests may have authentication headers such as <code>Proxy-Authorization</code>
* that authenticate the client with the proxy.</p>
*
@ -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);
}
/**
* <p>Returns whether the given {@code host} and {@code port} identify a SSL communication channel.<p>
* <p>Default implementation returns true if the {@code port} is 443.</p>
*
* @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;
}
/**
* <p>Handles the authentication before setting up the tunnel to the remote server.</p>
* <p>The default implementation returns true.</p>
*
* @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);
/**
* <p>Handles the authentication before setting up the tunnel to the remote server.</p>
* <p>The default implementation returns true.</p>
*
* @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);
}
}
}
}

View File

@ -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<String, String> headers = new LinkedHashMap<String, String>();
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<String, String> headers;
private final String body;
private Response(String code, Map<String, String> headers, String body)
{
this.code = code;
this.headers = headers;
this.body = body;
}
public String getCode()
{
return code;
}
public Map<String, String> 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<String, String> entry : headers.entrySet())
builder.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
builder.append("\r\n");
builder.append(body);
return builder.toString();
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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<String, String> headers = new LinkedHashMap<String, String>();
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<String, String> headers;
private final String body;
private Response(String code, Map<String, String> 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<String, String> 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();
}
}
}
}

View File

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