jLuger.de - Implmenting CONNECT for jetty based http proxy

A browser uses the CONNECT method to tell a proxy to create a tunnel, e.g. for a https connection. See RFC 2817 for details.
As I've created a http proxy based on jetty I was faced with the CONNECT method. The good news is that the jetty project has already created some classes for http proxies. See artifactid jetty-proxy in maven. The class org.eclipse.jetty.proxy.ConnectHandler implements the CONNECT method. It works fine but is not very suited to be extended in order to create a man-in-the-middle proxy. So I've decided to create my own implementation that doesn't uses as many classes/subclasses as ConnectHandler.

I've started implementing a org.eclipse.jetty.server.Handler. The handle method of this interface provides the parameters javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response. Whenever you see those two variables in the following code remember that they are given as parameter.

The first task in implementing the CONNECT method is to get the target. The call request.getRequestURI() gets the remote host and port separated by a colon.

Opening a socket to the remote target is easy but won't help as jetty uses NIO. The following code creates a non blocking connection the the target:
        InetSocketAddress address = getRequestedAddress(request);
        try (SocketChannel targetChannel = SocketChannel.open();) {
            targetChannel.socket().setTcpNoDelay(true);
            targetChannel.configureBlocking(false);
            targetChannel.connect(address);
Be beware that this code won't wait until the connection is established. You have to wait for it separately.

Another interesting task to do is in getting the request connection. Jetty hides the connection and presents every http request as a single method call even when there are several calls over one connection. That is great when you want to focus on handling one request at a time but not good for a tunnel that should handle multiple connections.
The trick is done with these two lines:
        final EndPoint downstreamEndPoint = HttpConnection.getCurrentConnection().getEndPoint();
java.nio.channels.SocketChannel requestChannel = (java.nio.channels.SocketChannel)downstreamEndPoint.getTransport();
According to the RFC 2817 an 200 message has to be sent now to the browser before any data are exchanged. I've extracted the following lines from the jetty ConnectHandler.:
            response.setStatus(HttpServletResponse.SC_OK);
response.getOutputStream().close();
response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
Now just listen for data on any side and write it to the other side. I've done it with this code:
            Selector selector = Selector.open();
SelectionKey targetKey = targetChannel.register(selector, SelectionKey.OP_READ);
SelectionKey requestKey = requestChannel.register(selector, SelectionKey.OP_READ);
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
if(selectedKeys.size() == 0) continue;
if (selectedKeys.contains(targetKey)) {
int read = copyData(targetChannel,requestChannel);
selectedKeys.remove(targetKey);
if (read==-1) {
break;
}
}
if (selectedKeys.contains(requestKey)) {
int read = copyData(requestChannel,targetChannel);
selectedKeys.remove(requestKey);
if (read==-1) {
break;
}
}
}
The method copyData is pretty simple:
    private int copyData(SocketChannel source, SocketChannel target) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(16384);
buffer.clear();
int read = source.read(buffer);
buffer.flip();
if (buffer.limit()>0) {
target.write(buffer);
}
return read;
}
Please note that the allocation of buffer is bad. I've put it just there to get the example complete. In the real world you want to allocate it only once per thread and then use the clear method at the beginning of copy.