Enhancements for #297104.

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@1496 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Simone Bordet 2010-04-12 16:26:07 +00:00
parent 9cb41db6df
commit 92c95f7fea
5 changed files with 1112 additions and 205 deletions

View File

@ -16,7 +16,6 @@ package org.eclipse.jetty.server;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import javax.servlet.ServletInputStream; import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -41,13 +40,12 @@ import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.Parser; import org.eclipse.jetty.http.Parser;
import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.BufferCache.CachedBuffer;
import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.io.UncheckedPrintWriter; import org.eclipse.jetty.io.UncheckedPrintWriter;
import org.eclipse.jetty.io.BufferCache.CachedBuffer;
import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.URIUtil;
@ -68,7 +66,7 @@ import org.eclipse.jetty.util.thread.Timeout;
* with the connection via the parser and/or generator. * with the connection via the parser and/or generator.
* </p> * </p>
* <p> * <p>
* The connection state is held by 3 separate state machines: The request state, the * The connection state is held by 3 separate state machines: The request state, the
* response state and the continuation state. All three state machines must be driven * response state and the continuation state. All three state machines must be driven
* to completion for every request, and all three can complete in any order. * to completion for every request, and all three can complete in any order.
* </p> * </p>
@ -76,12 +74,12 @@ import org.eclipse.jetty.util.thread.Timeout;
* The HttpConnection support protocol upgrade. If on completion of a request, the * The HttpConnection support protocol upgrade. If on completion of a request, the
* response code is 101 (switch protocols), then the org.eclipse.jetty.io.Connection * response code is 101 (switch protocols), then the org.eclipse.jetty.io.Connection
* request attribute is checked to see if there is a new Connection instance. If so, * request attribute is checked to see if there is a new Connection instance. If so,
* the new connection is returned from {@link #handle()} and is used for future * the new connection is returned from {@link #handle()} and is used for future
* handling of the underlying connection. Note that for switching protocols that * handling of the underlying connection. Note that for switching protocols that
* don't use 101 responses (eg CONNECT), the response should be sent and then the * don't use 101 responses (eg CONNECT), the response should be sent and then the
* status code changed to 101 before returning from the handler. Implementors * status code changed to 101 before returning from the handler. Implementors
* of new Connection types should be careful to extract any buffered data from * of new Connection types should be careful to extract any buffered data from
* (HttpParser)http.getParser()).getHeaderBuffer() and * (HttpParser)http.getParser()).getHeaderBuffer() and
* (HttpParser)http.getParser()).getBodyBuffer() to initialise their new connection. * (HttpParser)http.getParser()).getBodyBuffer() to initialise their new connection.
* </p> * </p>
* *
@ -378,7 +376,7 @@ public class HttpConnection implements Connection
public Connection handle() throws IOException public Connection handle() throws IOException
{ {
Connection connection = this; Connection connection = this;
// Loop while more in buffer // Loop while more in buffer
boolean more_in_buffer =true; // assume true until proven otherwise boolean more_in_buffer =true; // assume true until proven otherwise
boolean progress=true; boolean progress=true;
@ -396,9 +394,9 @@ public class HttpConnection implements Connection
{ {
if (_request._async.isAsync()) if (_request._async.isAsync())
{ {
// TODO - handle the case of input being read for a // TODO - handle the case of input being read for a
// suspended request. // suspended request.
Log.debug("async request",_request); Log.debug("async request",_request);
if (!_request._async.isComplete()) if (!_request._async.isComplete())
handleRequest(); handleRequest();
@ -470,7 +468,7 @@ public class HttpConnection implements Connection
// look for a switched connection instance? // look for a switched connection instance?
Connection switched=(_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101) Connection switched=(_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101)
?(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection"):null; ?(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection"):null;
// have we switched? // have we switched?
if (switched!=null) if (switched!=null)
{ {
@ -490,7 +488,7 @@ public class HttpConnection implements Connection
if (more_in_buffer) if (more_in_buffer)
{ {
reset(false); reset(false);
more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput(); more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput();
} }
else else
reset(true); reset(true);
@ -503,7 +501,7 @@ public class HttpConnection implements Connection
Log.debug("return with suspended request"); Log.debug("return with suspended request");
more_in_buffer=false; more_in_buffer=false;
} }
else if (_generator.isCommitted() && !_generator.isComplete() && _endp instanceof AsyncEndPoint) else if (_generator.isCommitted() && !_generator.isComplete() && _endp instanceof AsyncEndPoint)
((AsyncEndPoint)_endp).setWritable(false); ((AsyncEndPoint)_endp).setWritable(false);
} }
} }
@ -628,7 +626,7 @@ public class HttpConnection implements Connection
Log.debug(e); Log.debug(e);
_request.setHandled(true); _request.setHandled(true);
_generator.sendError(info==null?400:500, null, null, true); _generator.sendError(info==null?400:500, null, null, true);
} }
finally finally
{ {
@ -839,18 +837,17 @@ public class HttpConnection implements Connection
switch (HttpMethods.CACHE.getOrdinal(method)) switch (HttpMethods.CACHE.getOrdinal(method))
{ {
case HttpMethods.CONNECT_ORDINAL: case HttpMethods.CONNECT_ORDINAL:
// _uri.parseConnect(uri.array(), uri.getIndex(), uri.length()); _uri.parseConnect(uri.array(), uri.getIndex(), uri.length());
_uri.parse("http://"+uri+"/");
break; break;
case HttpMethods.HEAD_ORDINAL: case HttpMethods.HEAD_ORDINAL:
_head=true; _head=true;
// fall through // fall through
default: default:
_uri.parse(uri.array(), uri.getIndex(), uri.length()); _uri.parse(uri.array(), uri.getIndex(), uri.length());
} }
_request.setUri(_uri); _request.setUri(_uri);
if (version==null) if (version==null)

View File

@ -0,0 +1,497 @@
package org.eclipse.jetty.server.handler;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ConnectedEndPoint;
import org.eclipse.jetty.io.Connection;
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.HttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
/**
* @version $Revision$ $Date$
*/
public class ProxyHandler extends AbstractHandler
{
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 QueuedThreadPool threadPool;
public ProxyHandler()
{
this(null);
}
public ProxyHandler(String serverAddress)
{
this.serverAddress = serverAddress;
}
public int getConnectTimeout()
{
return connectTimeout;
}
public void setConnectTimeout(int connectTimeout)
{
this.connectTimeout = connectTimeout;
}
public int getWriteTimeout()
{
return writeTimeout;
}
public void setWriteTimeout(int writeTimeout)
{
this.writeTimeout = writeTimeout;
}
@Override
protected void doStart() throws Exception
{
super.doStart();
// TODO: configure threadPool
threadPool = new QueuedThreadPool();
threadPool.start();
selectorManager.start();
threadPool.dispatch(new Runnable()
{
public void run()
{
while (isRunning())
{
try
{
selectorManager.doSelect(0);
}
catch (IOException x)
{
logger.warn("Unexpected exception", x);
}
}
}
});
}
@Override
protected void doStop() throws Exception
{
selectorManager.stop();
QueuedThreadPool threadPool = this.threadPool;
if (threadPool != null)
threadPool.stop();
super.doStop();
}
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
if (HttpMethods.CONNECT.equalsIgnoreCase(request.getMethod()))
{
logger.info("CONNECT request for {}", request.getRequestURI(), null);
handle(request, response, request.getRequestURI());
}
else
{
logger.info("Plain request for {}", serverAddress, null);
if (serverAddress == null)
throw new ServletException("Parameter 'serverAddress' cannot be null");
handle(request, response, serverAddress);
}
}
/**
* <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>
* @param request the http request
* @param response the http response
* @param connectURI the CONNECT URI
* @throws ServletException if an application error occurs
* @throws IOException if an I/O error occurs
*/
protected void handle(HttpServletRequest request, HttpServletResponse response, String connectURI) throws ServletException, IOException
{
boolean proceed = handleAuthentication(request, response, connectURI);
if (!proceed)
return;
String host = connectURI;
int port = 80;
boolean secure = false;
int colon = connectURI.indexOf(':');
if (colon > 0)
{
host = connectURI.substring(0, colon);
port = Integer.parseInt(connectURI.substring(colon + 1));
secure = isTunnelSecure(host, port);
}
setupTunnel(request, response, host, port, secure);
}
protected boolean isTunnelSecure(String host, int port)
{
return port == 443;
}
protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String connectURI) 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);
// Transfer unread data from old connection to new connection
// We need to copy the data to avoid races:
// 1. when this unread data is written and the server replies before the clientToProxy
// connection is installed (it is only installed after returning from this method)
// 2. when the client sends data before this unread data has been written.
HttpConnection httpConnection = HttpConnection.getCurrentConnection();
Buffer headerBuffer = ((HttpParser)httpConnection.getParser()).getHeaderBuffer();
Buffer bodyBuffer = ((HttpParser)httpConnection.getParser()).getBodyBuffer();
int length = headerBuffer == null ? 0 : headerBuffer.length();
length += bodyBuffer == null ? 0 : bodyBuffer.length();
IndirectNIOBuffer buffer = null;
if (length > 0)
{
buffer = new IndirectNIOBuffer(length);
if (headerBuffer != null)
{
buffer.put(headerBuffer);
headerBuffer.clear();
}
if (bodyBuffer != null)
{
buffer.put(bodyBuffer);
bodyBuffer.clear();
}
}
// Setup connections, before registering the channel to avoid races
// where the server sends data before the connections are set up
ProxyToServerConnection proxyToServer = new ProxyToServerConnection(secure, buffer);
ClientToProxyConnection clientToProxy = new ClientToProxyConnection(channel, httpConnection.getEndPoint(), httpConnection.getTimeStamp());
clientToProxy.setConnection(proxyToServer);
proxyToServer.setConnection(clientToProxy);
upgradeConnection(request, response, clientToProxy);
}
protected SocketChannel connect(HttpServletRequest request, String host, int port) throws IOException
{
logger.info("Establishing connection to {}:{}", host, port);
// Connect to remote server
SocketChannel channel = SocketChannel.open();
channel.socket().setTcpNoDelay(true);
channel.socket().connect(new InetSocketAddress(host, port), getConnectTimeout());
logger.info("Established connection to {}:{}", host, port);
return channel;
}
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);
response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
logger.info("Upgraded connection to {}", connection, null);
}
private void register(SocketChannel channel, ProxyToServerConnection proxyToServer) throws IOException
{
selectorManager.register(channel, proxyToServer);
proxyToServer.waitReady(connectTimeout);
}
/**
* Writes (with blocking semantic) the given buffer of data onto the given endPoint
* @param endPoint the endPoint to write to
* @param buffer the buffer to write
* @throws IOException if the buffer cannot be written
*/
private void write(EndPoint endPoint, Buffer buffer) throws IOException
{
if (buffer == null)
return;
int length = buffer.length();
StringBuilder builder = new StringBuilder();
int written = endPoint.flush(buffer);
builder.append(written);
buffer.compact();
if (!endPoint.isBlocking())
{
while (buffer.space() == 0)
{
boolean ready = endPoint.blockWritable(getWriteTimeout());
if (!ready)
throw new IOException("Write timeout");
written = endPoint.flush(buffer);
builder.append("+").append(written);
buffer.compact();
}
}
logger.info("Written {}/{} bytes " + endPoint, builder, length);
}
private class Manager extends SelectorManager
{
@Override
protected SocketChannel acceptChannel(SelectionKey key) throws IOException
{
// This is a client-side selector manager
throw new IllegalStateException();
}
@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);
}
}
@Override
protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint)
{
ProxyToServerConnection proxyToServer = (ProxyToServerConnection)endpoint.getSelectionKey().attachment();
proxyToServer.setTimeStamp(System.currentTimeMillis());
proxyToServer.setEndPoint(endpoint);
return proxyToServer;
}
@Override
protected void endPointOpened(SelectChannelEndPoint endpoint)
{
ProxyToServerConnection proxyToServer = (ProxyToServerConnection)endpoint.getSelectionKey().attachment();
proxyToServer.ready();
}
@Override
public boolean dispatch(Runnable task)
{
return threadPool.dispatch(task);
}
@Override
protected void endPointClosed(SelectChannelEndPoint endpoint)
{
}
@Override
protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
{
}
}
private class ProxyToServerConnection implements Connection
{
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 connection;
private volatile long timestamp;
private volatile SelectChannelEndPoint endPoint;
public ProxyToServerConnection(boolean secure, Buffer data)
{
this.secure = secure;
this.data = data;
}
public Connection handle() throws IOException
{
logger.info("ProxyToServer: handle entered");
if (data != null)
{
write(endPoint, data);
data = null;
}
while (true)
{
int read = endPoint.fill(buffer);
if (read == -1)
{
logger.info("ProxyToServer: closed connection {}", endPoint, null);
connection.close();
break;
}
if (read == 0)
break;
logger.info("ProxyToServer: read {} bytes {}", read, endPoint);
write(connection.endPoint, buffer);
}
logger.info("ProxyToServer: handle exited");
return this;
}
public void setConnection(ClientToProxyConnection connection)
{
this.connection = connection;
}
public long getTimeStamp()
{
return timestamp;
}
public void setTimeStamp(long timestamp)
{
this.timestamp = timestamp;
}
public void setEndPoint(SelectChannelEndPoint endpoint)
{
this.endPoint = endpoint;
}
public boolean isIdle()
{
return false;
}
public boolean isSuspended()
{
return false;
}
public void ready()
{
ready.countDown();
}
public void waitReady(long timeout) throws IOException
{
try
{
ready.await(timeout, TimeUnit.MILLISECONDS);
}
catch (InterruptedException x)
{
throw new IOException(x);
}
}
public void close() throws IOException
{
endPoint.close();
}
}
private class ClientToProxyConnection implements Connection
{
private final Buffer buffer = new IndirectNIOBuffer(1024);
private final SocketChannel channel;
private final EndPoint endPoint;
private final long timestamp;
private volatile ProxyToServerConnection connection;
private boolean firstTime = true;
public ClientToProxyConnection(SocketChannel channel, EndPoint endPoint, long timestamp)
{
this.channel = channel;
this.endPoint = endPoint;
this.timestamp = timestamp;
}
public Connection handle() throws IOException
{
logger.info("ClientToProxy: handle entered");
if (firstTime)
{
firstTime = false;
register(channel, connection);
}
while (true)
{
int read = endPoint.fill(buffer);
if (read == -1)
{
logger.info("ClientToProxy: closed connection {}", endPoint, null);
connection.close();
break;
}
if (read == 0)
break;
logger.info("ClientToProxy: read {} bytes {}", read, endPoint);
write(connection.endPoint, buffer);
}
logger.info("ClientToProxy: handle exited");
return this;
}
public long getTimeStamp()
{
return timestamp;
}
public boolean isIdle()
{
return false;
}
public boolean isSuspended()
{
return false;
}
public void setConnection(ProxyToServerConnection connection)
{
this.connection = connection;
}
public void close() throws IOException
{
endPoint.close();
}
}
}

View File

@ -0,0 +1,328 @@
package org.eclipse.jetty.server.handler;
import java.io.BufferedReader;
import java.io.EOFException;
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
{
private Server server;
private Connector serverConnector;
private Server proxy;
private Connector proxyConnector;
@Override
protected void setUp() throws Exception
{
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
{
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);
}
finally
{
socket.close();
}
}
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);
String echoURI = "GET /echo";
request = "" +
echoURI + " HTTP/1.1\r\n" +
"Host: localhost\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);
}
finally
{
socket.close();
}
}
public void testHttpConnectWithPipelinedRequest() throws Exception
{
String pipelinedMethodURI = "GET /echo";
String request = "" +
"CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"\r\n" +
pipelinedMethodURI + " 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);
// 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);
}
finally
{
socket.close();
}
}
public void testHttpConnectWithNoRequestServerClose() 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);
// Idle server is shut down
server.stop();
server.join();
int read = input.read();
assertEquals(-1, read);
}
finally
{
socket.close();
}
}
public void testHttpConnectWithRequestServerClose() 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);
request = "" +
"GET /close HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"\r\n";
output.write(request.getBytes("UTF-8"));
output.flush();
int read = input.read();
assertEquals(-1, read);
}
finally
{
socket.close();
}
}
private Response readResponse(InputStream input) throws IOException
{
// 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)
{
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)
body.append((char)reader.read());
}
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
{
}
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);
System.err.println("server echoing:\r\n" + builder);
ServletOutputStream output = httpResponse.getOutputStream();
output.println(builder.toString());
}
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();
}
}
}

View File

@ -0,0 +1,82 @@
package org.eclipse.jetty.server.handler;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import junit.framework.TestCase;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
/**
* @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();
}
}
}