diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java index 063cdbd5283..cf64c1c1c41 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java @@ -20,43 +20,56 @@ package org.eclipse.jetty.proxy; import java.io.IOException; import java.net.InetSocketAddress; -import java.net.SocketException; -import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; +import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.MappedByteBufferPool; +import org.eclipse.jetty.io.SelectChannelEndPoint; import org.eclipse.jetty.io.SelectorManager; import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.util.HostMap; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Scheduler; +import org.eclipse.jetty.util.thread.TimerScheduler; /** *

Implementation of a {@link Handler} that supports HTTP CONNECT.

*/ public class ConnectHandler extends HandlerWrapper { - private static final Logger LOG = Log.getLogger(ConnectHandler.class); + protected static final Logger LOG = Log.getLogger(ConnectHandler.class); - private final Set _whiteList = new HashSet<>(); - private final Set _blackList = new HashSet<>(); - private final SelectorManager _selector = new Manager(null, null, 1); - private volatile int _connectTimeout = 15000; - private volatile int _idleTimeout = 30000; + private final Set whiteList = new HashSet<>(); + private final Set blackList = new HashSet<>(); + private Executor executor; + private Scheduler scheduler; + private ByteBufferPool bufferPool; + private SelectorManager selector; + private long connectTimeout = 15000; + private long idleTimeout = 30000; + private int bufferSize = 4096; public ConnectHandler() { @@ -68,51 +81,103 @@ public class ConnectHandler extends HandlerWrapper setHandler(handler); } + public Executor getExecutor() + { + return executor; + } + + public void setExecutor(Executor executor) + { + this.executor = executor; + } + + public Scheduler getScheduler() + { + return scheduler; + } + + public void setScheduler(Scheduler scheduler) + { + this.scheduler = scheduler; + } + + public ByteBufferPool getByteBufferPool() + { + return bufferPool; + } + + public void setByteBufferPool(ByteBufferPool bufferPool) + { + this.bufferPool = bufferPool; + } + /** * @return the timeout, in milliseconds, to connect to the remote server */ - public int getConnectTimeout() + public long getConnectTimeout() { - return _connectTimeout; + return connectTimeout; } /** * @param connectTimeout the timeout, in milliseconds, to connect to the remote server */ - public void setConnectTimeout(int connectTimeout) + public void setConnectTimeout(long connectTimeout) { - _connectTimeout = connectTimeout; + this.connectTimeout = connectTimeout; } /** * @return the idle timeout, in milliseconds */ - public int getIdleTimeout() + public long getIdleTimeout() { - return _idleTimeout; + return idleTimeout; } /** * @param idleTimeout the idle timeout, in milliseconds */ - public void setIdleTimeout(int idleTimeout) + public void setIdleTimeout(long idleTimeout) { - _idleTimeout = idleTimeout; + this.idleTimeout = idleTimeout; + } + + public int getBufferSize() + { + return bufferSize; + } + + public void setBufferSize(int bufferSize) + { + this.bufferSize = bufferSize; } @Override protected void doStart() throws Exception { - _selector.setConnectTimeout(getConnectTimeout()); - _selector.start(); + if (executor == null) + { + setExecutor(getServer().getThreadPool()); + } + if (scheduler == null) + { + setScheduler(new TimerScheduler()); + addBean(getScheduler()); + } + if (bufferPool == null) + { + setByteBufferPool(new MappedByteBufferPool()); + addBean(getByteBufferPool()); + } + addBean(selector = newSelectorManager()); + selector.setConnectTimeout(getConnectTimeout()); super.doStart(); } - @Override - protected void doStop() throws Exception + protected SelectorManager newSelectorManager() { - super.doStop(); - _selector.stop(); + return new Manager(getExecutor(), getScheduler(), 1); } @Override @@ -120,15 +185,17 @@ public class ConnectHandler extends HandlerWrapper { if (HttpMethod.CONNECT.is(request.getMethod())) { - LOG.debug("CONNECT request for {}", request.getRequestURI()); + String serverAddress = request.getRequestURI(); + LOG.debug("CONNECT request for {}", serverAddress); try { - handleConnect(baseRequest, request, response, request.getRequestURI()); + handleConnect(baseRequest, request, response, serverAddress); } - catch(Exception e) + catch (Exception x) { - LOG.warn("ConnectHandler "+baseRequest.getUri()+" "+ e); - LOG.debug(e); + // TODO + LOG.warn("ConnectHandler " + baseRequest.getUri() + " " + x); + LOG.debug(x); } } else @@ -142,129 +209,115 @@ public class ConnectHandler extends HandlerWrapper *

CONNECT requests may have authentication headers such as {@code Proxy-Authorization} * that authenticate the client with the proxy.

* - * @param jettyRequest Jetty-specific http request + * @param jettyRequest Jetty-specific http request * @param request the http request * @param response the http response * @param serverAddress the remote server address in the form {@code host:port} - * @throws ServletException if an application error occurs - * @throws IOException if an I/O error occurs */ - protected void handleConnect(Request jettyRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress) throws ServletException, IOException + protected void handleConnect(Request jettyRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress) { - boolean proceed = handleAuthentication(request, response, serverAddress); - if (!proceed) - return; - - String host = serverAddress; - int port = 80; - int colon = serverAddress.indexOf(':'); - if (colon > 0) - { - host = serverAddress.substring(0, colon); - port = Integer.parseInt(serverAddress.substring(colon + 1)); - } - - if (!validateDestination(host)) - { - LOG.info("ProxyHandler: Forbidden destination " + host); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - jettyRequest.setHandled(true); - return; - } - - SocketChannel channel = SocketChannel.open(); - channel.socket().setTcpNoDelay(true); - channel.configureBlocking(false); - channel.connect(new InetSocketAddress(host, port)); - -// _selector.connect(channel, ); - -// SocketChannel channel; - + jettyRequest.setHandled(true); try { - channel = connectToServer(request,host,port); - } - catch (SocketException se) - { - LOG.info("ConnectHandler: SocketException " + se.getMessage()); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - jettyRequest.setHandled(true); - return; - } - catch (SocketTimeoutException ste) - { - LOG.info("ConnectHandler: SocketTimeoutException" + ste.getMessage()); - response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT); - jettyRequest.setHandled(true); - return; - } - catch (IOException ioe) - { - LOG.info("ConnectHandler: IOException" + ioe.getMessage()); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - jettyRequest.setHandled(true); - return; - } - - // 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. - - /* TODO - AbstractHttpConnection httpConnection = AbstractHttpConnection.getCurrentHttpChannel(); - ByteBuffer headerBuffer = ((HttpParser)httpConnection.getParser()).getHeaderBuffer(); - ByteBuffer 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) + boolean proceed = handleAuthentication(request, response, serverAddress); + if (!proceed) { - buffer.put(headerBuffer); - headerBuffer.clear(); + LOG.debug("Missing proxy authentication"); + sendConnectResponse(request, response, HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED); + return; } - if (bodyBuffer != null) + + String host = serverAddress; + int port = 80; + int colon = serverAddress.indexOf(':'); + if (colon > 0) { - buffer.put(bodyBuffer); - bodyBuffer.clear(); + host = serverAddress.substring(0, colon); + port = Integer.parseInt(serverAddress.substring(colon + 1)); } + + if (!validateDestination(host, port)) + { + LOG.debug("Destination {}:{} forbidden", host, port); + sendConnectResponse(request, response, HttpServletResponse.SC_FORBIDDEN); + return; + } + + SocketChannel channel = SocketChannel.open(); + channel.socket().setTcpNoDelay(true); + channel.configureBlocking(false); + InetSocketAddress address = new InetSocketAddress(host, port); + channel.connect(address); + + AsyncContext asyncContext = request.startAsync(); + asyncContext.setTimeout(0); + + LOG.debug("Connecting to {}", address); + ConnectContext connectContext = new ConnectContext(request, response, asyncContext, HttpConnection.getCurrentConnection()); + selector.connect(channel, connectContext); + } + catch (Exception x) + { + onConnectFailure(request, response, null, x); + } + } + + protected void onConnectSuccess(ConnectContext connectContext, UpstreamConnection upstreamConnection) + { + HttpConnection httpConnection = connectContext.getHttpConnection(); + ByteBuffer requestBuffer = httpConnection.getRequestBuffer(); + ByteBuffer buffer = BufferUtil.EMPTY_BUFFER; + int remaining = requestBuffer.remaining(); + if (remaining > 0) + { + buffer = bufferPool.acquire(remaining, requestBuffer.isDirect()); + BufferUtil.flipToFill(buffer); + buffer.put(requestBuffer); + buffer.flip(); } - ConcurrentMap context = new ConcurrentHashMap(); + ConcurrentMap context = connectContext.getContext(); + HttpServletRequest request = connectContext.getRequest(); prepareContext(request, context); - ClientToProxyConnection clientToProxy = prepareConnections(context, channel, buffer); + EndPoint downstreamEndPoint = httpConnection.getEndPoint(); + DownstreamConnection downstreamConnection = newDownstreamConnection(downstreamEndPoint, context, buffer); + downstreamConnection.setInputBufferSize(getBufferSize()); - // CONNECT expects a 200 response - response.setStatus(HttpServletResponse.SC_OK); + upstreamConnection.setConnection(downstreamConnection); + downstreamConnection.setConnection(upstreamConnection); + LOG.debug("Connection setup completed: {}<->{}", downstreamConnection, upstreamConnection); - // Prevent close - jettyRequest.getConnection().getGenerator().setPersistent(true); + HttpServletResponse response = connectContext.getResponse(); + sendConnectResponse(request, response, HttpServletResponse.SC_OK); - // Close to force last flush it so that the client receives it - response.getOutputStream().close(); - - upgradeConnection(request, response, clientToProxy); - */ + upgradeConnection(request, response, downstreamConnection); + connectContext.getAsyncContext().complete(); } - /* TODO - private ClientToProxyConnection prepareConnections(ConcurrentMap context, SocketChannel channel, ByteBuffer buffer) + protected void onConnectFailure(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, Throwable failure) { - AbstractHttpConnection httpConnection = AbstractHttpConnection.getCurrentHttpChannel(); - ProxyToServerConnection proxyToServer = newProxyToServerConnection(context, buffer); - ClientToProxyConnection clientToProxy = newClientToProxyConnection(context, channel, httpConnection.getEndPoint(), httpConnection.getTimeStamp()); - clientToProxy.setConnection(proxyToServer); - proxyToServer.setConnection(clientToProxy); - return clientToProxy; - return null; + LOG.debug("CONNECT failed", failure); + sendConnectResponse(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + if (asyncContext != null) + asyncContext.complete(); + } + + private void sendConnectResponse(HttpServletRequest request, HttpServletResponse response, int statusCode) + { + try + { + response.setStatus(statusCode); + if (statusCode != HttpServletResponse.SC_OK) + response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString()); + response.getOutputStream().close(); + LOG.debug("CONNECT response sent {} {}", request.getProtocol(), response.getStatus()); + } + catch (IOException x) + { + // TODO: nothing we can do, close the connection + } } - */ /** *

Handles the authentication before setting up the tunnel to the remote server.

@@ -274,95 +327,35 @@ public class ConnectHandler extends HandlerWrapper * @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 + protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) { return true; } - /* TODO - protected ClientToProxyConnection newClientToProxyConnection(ConcurrentMap context, SocketChannel channel, EndPoint endPoint, long timeStamp) + protected DownstreamConnection newDownstreamConnection(EndPoint endPoint, ConcurrentMap context, ByteBuffer buffer) { - return new ClientToProxyConnection(context, channel, endPoint, timeStamp); + return new DownstreamConnection(endPoint, getExecutor(), getByteBufferPool(), context, this, buffer); } - protected ProxyToServerConnection newProxyToServerConnection(ConcurrentMap context, ByteBuffer buffer) + protected UpstreamConnection newUpstreamConnection(EndPoint endPoint, ConnectContext connectContext) { - return new ProxyToServerConnection(context, buffer); - } - */ - - // may return null - private SocketChannel connectToServer(HttpServletRequest request, String host, int port) throws IOException - { - SocketChannel channel = connect(request, host, port); - channel.configureBlocking(false); - return channel; - } - - /** - *

Establishes a connection to the remote server.

- * - * @param request the HTTP request that initiated the tunnel - * @param host the host to connect to - * @param port the port to connect to - * @return a {@link SocketChannel} connected to the remote server - */ - protected SocketChannel connect(HttpServletRequest request, String host, int port) throws IOException - { - SocketChannel channel = null; - try - { - channel = SocketChannel.open(); - if (channel == null) - throw new IOException("Unable to connect to " + host + ":" + port); - - // Connect to remote server - LOG.debug("Establishing connection to {}:{}", host, port); - channel.socket().setTcpNoDelay(true); - channel.socket().connect(new InetSocketAddress(host, port), getConnectTimeout()); - LOG.debug("Established connection to {}:{}", host, port); - return channel; - } - catch (IOException x) - { - try - { - if (channel != null) - channel.close(); - } - catch (IOException xx) - { - LOG.ignore(xx); - } - LOG.debug("Failed to establish connection to " + host + ":" + port, x); - throw x; - } + return new UpstreamConnection(endPoint, getExecutor(), getByteBufferPool(), this, connectContext); } protected void prepareContext(HttpServletRequest request, ConcurrentMap context) { } - private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection) throws IOException + private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection) { // 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); + request.setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE, connection); response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS); LOG.debug("Upgraded connection to {}", connection); } - /* TODO - private void register(SocketChannel channel, ProxyToServerConnection proxyToServer) throws IOException - { - _selector.register(channel, proxyToServer); - proxyToServer.waitReady(_connectTimeout); - } - */ - /** *

Reads (with non-blocking semantic) into the given {@code buffer} from the given {@code endPoint}.

* @@ -379,50 +372,67 @@ public class ConnectHandler extends HandlerWrapper } /** - *

Writes (with blocking semantic) the given buffer of data onto the given endPoint.

+ *

Writes (with non-blocking semantic) the given buffer of data onto the given endPoint.

* * @param endPoint the endPoint to write to * @param buffer the buffer to write * @param context the context information related to the connection - * @throws IOException if the buffer cannot be written - * @return the number of bytes written + * @param callback the completion callback to invoke */ - protected int write(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap context) throws IOException + protected void write(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap context, Callback callback) { - /* TODO - if (buffer == null) - return 0; - - int length = buffer.length(); - final StringBuilder debug = LOG.isDebugEnabled()?new StringBuilder():null; - int flushed = endPoint.flush(buffer); - if (debug!=null) - debug.append(flushed); - - // Loop until all written - while (buffer.length()>0 && !endPoint.isOutputShutdown()) - { - if (!endPoint.isBlocking()) - { - boolean ready = endPoint.blockWritable(getIdleTimeout()); - if (!ready) - throw new IOException("Write timeout"); - } - flushed = endPoint.flush(buffer); - if (debug!=null) - debug.append("+").append(flushed); - } - - LOG.debug("Written {}/{} bytes {}", debug, length, endPoint); - buffer.compact(); - return length; - */ - return -1; + endPoint.write(null, callback, buffer); } - // TODO - private class Manager extends SelectorManager + public Set getWhiteListHosts() { + return whiteList; + } + + public Set getBlackListHosts() + { + return blackList; + } + + /** + * Checks the given {@code host} and {@code port} against whitelist and blacklist. + * + * @param host the host to check + * @param port the port to check + * @return true if it is allowed to connect to the given host and port + */ + public boolean validateDestination(String host, int port) + { + String hostPort = host + ":" + port; + if (!whiteList.isEmpty()) + { + if (!whiteList.contains(hostPort)) + { + LOG.debug("Host {}:{} not whitelisted", host, port); + return false; + } + } + if (!blackList.isEmpty()) + { + if (blackList.contains(hostPort)) + { + LOG.debug("Host {}:{} blacklisted", host, port); + return false; + } + } + return true; + } + + @Override + public void dump(Appendable out, String indent) throws IOException + { + dumpThis(out); + dump(out, indent, getBeans(), TypeUtil.asList(getHandlers())); + } + + protected class Manager extends SelectorManager + { + private Manager(Executor executor, Scheduler scheduler, int selectors) { super(executor, scheduler, selectors); @@ -431,557 +441,66 @@ public class ConnectHandler extends HandlerWrapper @Override protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException { - return null; + return new SelectChannelEndPoint(channel, selector, selectionKey, getScheduler(), getIdleTimeout()); } @Override public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException { - return null; - } - // @Override -// protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException -// { -// SelectChannelEndPoint endp = new SelectChannelEndPoint(channel, selectSet, key, channel.socket().getSoTimeout()); -// endp.setConnection(selectSet.getSelectorManager().newConnection(channel,endp, key.attachment())); -// endp.setIdleTimeout(_idleTimeout); -// return endp; -// } -// -// @Override -// public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment) -// { -// ProxyToServerConnection proxyToServer = (ProxyToServerConnection)attachment; -// proxyToServer.setTimeStamp(System.currentTimeMillis()); -// proxyToServer.setEndPoint(endpoint); -// return proxyToServer; -// } -// -// @Override -// protected void endPointOpened(SelectChannelEndPoint endpoint) -// { -// ProxyToServerConnection proxyToServer = (ProxyToServerConnection)endpoint.getSelectionKey().attachment(); -// proxyToServer.complete(); -// } -// -// @Override -// public boolean dispatch(Runnable task) -// { -// return _threadPool.dispatch(task); -// } -// -// @Override -// protected void endPointClosed(AsyncEndPoint endpoint) -// { -// } -// -// @Override -// protected void connectionUpgraded(ConnectedEndPoint endpoint, AsyncConnection oldConnection) -// { -// } - } - - -/* - public class ProxyToServerConnection implements AsyncConnection - { - private final CountDownLatch _ready = new CountDownLatch(1); - private final ByteBuffer _buffer = new IndirectNIOBuffer(4096); - private final ConcurrentMap _context; - private volatile ByteBuffer _data; - private volatile ClientToProxyConnection _toClient; - private volatile long _timestamp; - private volatile AsyncEndPoint _endPoint; - - public ProxyToServerConnection(ConcurrentMap context, ByteBuffer data) - { - _context = context; - _data = data; + ConnectHandler.LOG.debug("Connected to {}", channel.getRemoteAddress()); + ConnectContext connectContext = (ConnectContext)attachment; + UpstreamConnection connection = newUpstreamConnection(endpoint, connectContext); + connection.setInputBufferSize(getBufferSize()); + return connection; } @Override - public String toString() + protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment) { - StringBuilder builder = new StringBuilder("ProxyToServer"); - builder.append("(:").write(_endPoint.getLocalPort()); - builder.append("<=>:").write(_endPoint.getRemotePort()); - return builder.append(")").toString(); - } - - public AsyncConnection handle() throws IOException - { - LOG.debug("{}: begin reading from server", this); - try - { - writeData(); - - while (true) - { - int read = read(_endPoint, _buffer, _context); - - if (read == -1) - { - LOG.debug("{}: server closed connection {}", this, _endPoint); - - if (_endPoint.isOutputShutdown() || !_endPoint.isOpen()) - closeClient(); - else - _toClient.shutdownOutput(); - - break; - } - - if (read == 0) - break; - - LOG.debug("{}: read from server {} bytes {}", this, read, _endPoint); - int written = write(_toClient._endPoint, _buffer, _context); - LOG.debug("{}: written to {} {} bytes", this, _toClient, written); - } - return this; - } - catch (ClosedChannelException x) - { - LOG.debug(x); - throw x; - } - catch (IOException x) - { - LOG.warn(this + ": unexpected exception", x); - close(); - throw x; - } - catch (RuntimeException x) - { - LOG.warn(this + ": unexpected exception", x); - close(); - throw x; - } - finally - { - LOG.debug("{}: end reading from server", this); - } - } - - public void onInputShutdown() throws IOException - { - // TODO - } - - private void writeData() throws IOException - { - // This method is called from handle() and closeServer() - // which may happen concurrently (e.g. a client closing - // while reading from the server), so needs synchronization - synchronized (this) - { - if (_data != null) - { - try - { - int written = write(_endPoint, _data, _context); - LOG.debug("{}: written to server {} bytes", this, written); - } - finally - { - // Attempt once to write the data; if the write fails (for example - // because the connection is already closed), clear the data and - // give up to avoid to continue to write data to a closed connection - _data = null; - } - } - } - } - - public void setConnection(ClientToProxyConnection connection) - { - _toClient = connection; - } - - public long getTimeStamp() - { - return _timestamp; - } - - public void setTimeStamp(long timestamp) - { - _timestamp = timestamp; - } - - public void setEndPoint(AsyncEndPoint endpoint) - { - _endPoint = endpoint; - } - - public boolean isIdle() - { - return false; - } - - public boolean isSuspended() - { - return false; - } - - public void onClose() - { - } - - public void ready() - { - _ready.countDown(); - } - - public void waitReady(long timeout) throws IOException - { - try - { - _ready.await(timeout, TimeUnit.MILLISECONDS); - } - catch (final InterruptedException x) - { - throw new IOException() - {{ - initCause(x); - }}; - } - } - - public void closeClient() throws IOException - { - _toClient.closeClient(); - } - - public void closeServer() throws IOException - { - _endPoint.close(); - } - - public void close() - { - try - { - closeClient(); - } - catch (IOException x) - { - LOG.debug(this + ": unexpected exception closing the client", x); - } - - try - { - closeServer(); - } - catch (IOException x) - { - LOG.debug(this + ": unexpected exception closing the server", x); - } - } - - public void shutdownOutput() throws IOException - { - writeData(); - _endPoint.shutdownOutput(); - } - - public void onIdleExpired(long idleForMs) - { - try - { - shutdownOutput(); - } - catch(Exception e) - { - LOG.debug(e); - close(); - } + ConnectContext connectContext = (ConnectContext)attachment; + onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex); } } - public class ClientToProxyConnection implements AsyncConnection + protected static class ConnectContext { - private final ByteBuffer _buffer = new IndirectNIOBuffer(4096); - private final ConcurrentMap _context; - private final SocketChannel _channel; - private final EndPoint _endPoint; - private final long _timestamp; - private volatile ProxyToServerConnection _toServer; - private boolean _firstTime = true; + private final ConcurrentMap context = new ConcurrentHashMap<>(); + private final HttpServletRequest request; + private final HttpServletResponse response; + private final AsyncContext asyncContext; + private final HttpConnection httpConnection; - public ClientToProxyConnection(ConcurrentMap context, SocketChannel channel, EndPoint endPoint, long timestamp) + public ConnectContext(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, HttpConnection httpConnection) { - _context = context; - _channel = channel; - _endPoint = endPoint; - _timestamp = timestamp; + this.request = request; + this.response = response; + this.asyncContext = asyncContext; + this.httpConnection = httpConnection; } - @Override - public String toString() + public ConcurrentMap getContext() { - StringBuilder builder = new StringBuilder("ClientToProxy"); - builder.append("(:").write(_endPoint.getLocalPort()); - builder.append("<=>:").write(_endPoint.getRemotePort()); - return builder.append(")").toString(); + return context; } - public AsyncConnection handle() throws IOException + public HttpServletRequest getRequest() { - LOG.debug("{}: begin reading from client", this); - try - { - if (_firstTime) - { - _firstTime = false; - register(_channel, _toServer); - LOG.debug("{}: registered channel {} with connection {}", this, _channel, _toServer); - } - - while (true) - { - int read = read(_endPoint, _buffer, _context); - - if (read == -1) - { - LOG.debug("{}: client closed connection {}", this, _endPoint); - - if (_endPoint.isOutputShutdown() || !_endPoint.isOpen()) - closeServer(); - else - _toServer.shutdownOutput(); - - break; - } - - if (read == 0) - break; - - LOG.debug("{}: read from client {} bytes {}", this, read, _endPoint); - int written = write(_toServer._endPoint, _buffer, _context); - LOG.debug("{}: written to {} {} bytes", this, _toServer, written); - } - return this; - } - catch (ClosedChannelException x) - { - LOG.debug(x); - closeServer(); - throw x; - } - catch (IOException x) - { - LOG.warn(this + ": unexpected exception", x); - close(); - throw x; - } - catch (RuntimeException x) - { - LOG.warn(this + ": unexpected exception", x); - close(); - throw x; - } - finally - { - LOG.debug("{}: end reading from client", this); - } + return request; } - public void onInputShutdown() throws IOException + public HttpServletResponse getResponse() { - // TODO + return response; } - public long getTimeStamp() + public AsyncContext getAsyncContext() { - return _timestamp; + return asyncContext; } - public boolean isIdle() + public HttpConnection getHttpConnection() { - return false; + return httpConnection; } - - public boolean isSuspended() - { - return false; - } - - public void onClose() - { - } - - public void setConnection(ProxyToServerConnection connection) - { - _toServer = connection; - } - - public void closeClient() throws IOException - { - _endPoint.close(); - } - - public void closeServer() throws IOException - { - _toServer.closeServer(); - } - - public void close() - { - try - { - closeClient(); - } - catch (IOException x) - { - LOG.debug(this + ": unexpected exception closing the client", x); - } - - try - { - closeServer(); - } - catch (IOException x) - { - LOG.debug(this + ": unexpected exception closing the server", x); - } - } - - public void shutdownOutput() throws IOException - { - _endPoint.shutdownOutput(); - } - - public void onIdleExpired(long idleForMs) - { - try - { - shutdownOutput(); - } - catch(Exception e) - { - LOG.debug(e); - close(); - } - } - } - - /** - * Adds the given {@code host} to the whitelist - * - * @param host the whitelisted host - */ - public void addWhitelistHost(String host) - { - _whiteList.add(host); - } - - /** - * Adds the given {@code host} to the blacklist - * - * @param host the blacklisted host - */ - public void addBlacklistHost(String host) - { - _blackList.add(host); - } - - /** - * Re-initialize the whitelist of existing handler object - * - * @param entries array of whitelist entries - */ - public void setWhite(String[] entries) - { -// set(entries, _whiteList); - } - - /** - * Re-initialize the blacklist of existing handler object - * - * @param entries array of blacklist entries - */ - public void setBlack(String[] entries) - { -// set(entries, _blackList); - } - - /** - * Helper method to process a list of new entries and replace - * the content of the specified host map - * - * @param entries new entries - * @param hostMap target host map - */ - protected void set(String[] entries, HostMap hostMap) - { - hostMap.clear(); - - if (entries != null && entries.length > 0) - { - for (String addrPath : entries) - { - add(addrPath, hostMap); - } - } - } - - /** - * Helper method to process the new entry and add it to - * the specified host map. - * - * @param entry new entry - * @param hostMap target host map - */ - private void add(String entry, HostMap hostMap) - { - if (entry != null && entry.length() > 0) - { - entry = entry.trim(); - if (hostMap.get(entry) == null) - { - hostMap.put(entry, entry); - } - } - } - - /** - * Check the request hostname against white- and blacklist. - * - * @param host hostname to check - * @return true if hostname is allowed to be proxied - */ - public boolean validateDestination(String host) - { - if (_whiteList.size() > 0) - { -// Object whiteObj = _whiteList.getLazyMatches(host); -// if (whiteObj == null) - { - return false; - } - } - - if (_blackList.size() > 0) - { -// Object blackObj = _blackList.getLazyMatches(host); -// if (blackObj != null) - { - return false; - } - } - - return true; - } - - @Override - public void dump(Appendable out, String indent) throws IOException - { - dumpThis(out); - /* TODO - if (_privateThreadPool) - dump(out, indent, Arrays.asList(_threadPool, _selector), TypeUtil.asList(getHandlers()), getBeans()); - else - dump(out, indent, Arrays.asList(_selector), TypeUtil.asList(getHandlers()), getBeans()); - */ } } diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/DownstreamConnection.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/DownstreamConnection.java new file mode 100644 index 00000000000..24a241f183b --- /dev/null +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/DownstreamConnection.java @@ -0,0 +1,62 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.proxy; + +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; + +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.Callback; + +public class DownstreamConnection extends ProxyConnection +{ + private final ByteBuffer buffer; + + public DownstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConcurrentMap context, ConnectHandler connectHandler, ByteBuffer buffer) + { + super(endPoint, executor, bufferPool, context, connectHandler); + this.buffer = buffer; + } + + @Override + public void onOpen() + { + super.onOpen(); + final int remaining = buffer.remaining(); + write(buffer, new Callback() + { + @Override + public void completed(Void context) + { + LOG.debug("{} wrote initial {} bytes to server", DownstreamConnection.this, remaining); + fillInterested(); + } + + @Override + public void failed(Void context, Throwable x) + { + LOG.debug(this + " failed to write initial " + remaining + " bytes to server", x); + close(); + getConnection().close(); + } + }); + } +} diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyConnection.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyConnection.java new file mode 100644 index 00000000000..67a1037748a --- /dev/null +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyConnection.java @@ -0,0 +1,171 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.proxy; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; + +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.ForkInvoker; +import org.eclipse.jetty.util.log.Logger; + +public abstract class ProxyConnection extends AbstractConnection +{ + protected static final Logger LOG = ConnectHandler.LOG; + private final ForkInvoker invoker = new ProxyForkInvoker(); + private final ByteBufferPool bufferPool; + private final ConcurrentMap context; + private final ConnectHandler connectHandler; + private Connection connection; + + protected ProxyConnection(EndPoint endp, Executor executor, ByteBufferPool bufferPool, ConcurrentMap context, ConnectHandler connectHandler) + { + super(endp, executor); + this.bufferPool = bufferPool; + this.context = context; + this.connectHandler = connectHandler; + } + + public ByteBufferPool getByteBufferPool() + { + return bufferPool; + } + + public ConcurrentMap getContext() + { + return context; + } + + public ConnectHandler getConnectHandler() + { + return connectHandler; + } + + public Connection getConnection() + { + return connection; + } + + public void setConnection(Connection connection) + { + this.connection = connection; + } + + @Override + public void onFillable() + { + ByteBuffer buffer = getByteBufferPool().acquire(getInputBufferSize(), true); + fill(buffer); + } + + private void fill(final ByteBuffer buffer) + { + try + { + final int filled = connectHandler.read(getEndPoint(), buffer, getContext()); + LOG.debug("{} filled {} bytes", this, filled); + if (filled > 0) + { + write(buffer, new Callback() + { + @Override + public void completed(Void context) + { + LOG.debug("{} wrote {} bytes", this, filled); + buffer.clear(); + invoker.invoke(buffer); + } + + @Override + public void failed(Void context, Throwable x) + { + LOG.debug(this + " failed to write " + filled + " bytes", x); + bufferPool.release(buffer); + connection.close(); + } + }); + } + else if (filled == 0) + { + bufferPool.release(buffer); + fillInterested(); + } + else + { + bufferPool.release(buffer); + connection.getEndPoint().shutdownOutput(); + } + } + catch (IOException x) + { + LOG.debug(this + " could not fill", x); + bufferPool.release(buffer); + close(); + connection.close(); + } + } + + protected void write(ByteBuffer buffer, Callback callback) + { + LOG.debug("{} writing {} bytes", this, buffer.remaining()); + connectHandler.write(getConnection().getEndPoint(), buffer, context, callback); + } + + @Override + public String toString() + { + return String.format("%s[l:%d<=>r:%d]", + super.toString(), + getEndPoint().getLocalAddress().getPort(), + getEndPoint().getRemoteAddress().getPort()); + } + + private class ProxyForkInvoker extends ForkInvoker + { + private ProxyForkInvoker() + { + super(4); + } + + @Override + public void fork(final ByteBuffer buffer) + { + getExecutor().execute(new Runnable() + { + @Override + public void run() + { + call(buffer); + } + }); + } + + @Override + public void call(ByteBuffer buffer) + { + fill(buffer); + } + } +} diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java index 99d1ca247f9..b5656586148 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java @@ -124,11 +124,11 @@ public class ProxyServlet extends HttpServlet String whiteList = config.getInitParameter("whiteList"); if (whiteList != null) - getWhitelistHosts().addAll(parseList(whiteList)); + getWhiteListHosts().addAll(parseList(whiteList)); String blackList = config.getInitParameter("blackList"); if (blackList != null) - getBlacklistHosts().addAll(parseList(blackList)); + getBlackListHosts().addAll(parseList(blackList)); } catch (Exception e) { @@ -146,12 +146,12 @@ public class ProxyServlet extends HttpServlet this._timeout = timeout; } - public Set getWhitelistHosts() + public Set getWhiteListHosts() { return _whiteList; } - public Set getBlacklistHosts() + public Set getBlackListHosts() { return _blackList; } @@ -313,12 +313,11 @@ public class ProxyServlet extends HttpServlet } /** - * Checks the request hostname and path against whitelist and blacklist. + * Checks the given {@code host} and {@code port} against whitelist and blacklist. * - * - * @param host host to check + * @param host the host to check * @param port the port to check - * @return true if request is allowed to be proxied + * @return true if it is allowed to be proxy to the given host and port */ public boolean validateDestination(String host, int port) { diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/UpstreamConnection.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/UpstreamConnection.java new file mode 100644 index 00000000000..43717e40e7d --- /dev/null +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/UpstreamConnection.java @@ -0,0 +1,43 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.proxy; + +import java.util.concurrent.Executor; + +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.EndPoint; + +public class UpstreamConnection extends ProxyConnection +{ + private ConnectHandler.ConnectContext connectContext; + + public UpstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConnectHandler connectHandler, ConnectHandler.ConnectContext connectContext) + { + super(endPoint, executor, bufferPool, connectContext.getContext(), connectHandler); + this.connectContext = connectContext; + } + + @Override + public void onOpen() + { + super.onOpen(); + getConnectHandler().onConnectSuccess(connectContext, this); + fillInterested(); + } +} diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AbstractConnectHandlerTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AbstractConnectHandlerTest.java index ec55d5e9c71..57297e3b73f 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AbstractConnectHandlerTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AbstractConnectHandlerTest.java @@ -18,180 +18,62 @@ package org.eclipse.jetty.proxy; -/** - * @version $Revision$ $Date$ - */ +import java.io.BufferedReader; +import java.io.IOException; +import java.net.Socket; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.NetworkConnector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser; +import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse; +import org.junit.After; + public abstract class AbstractConnectHandlerTest { -// protected static Server server; -// protected static Connector.NetConnector serverConnector; -// protected static Server proxy; -// protected static Connector proxyConnector; -// -// protected static void startServer(Connector.NetConnector connector, Handler handler) throws Exception -// { -// server = new Server(); -// serverConnector = connector; -// server.addConnector(serverConnector); -// server.setHandler(handler); -// server.start(); -// } -// -// protected static void startProxy() throws Exception -// { -// proxy = new Server(); -// proxyConnector = new SelectChannelConnector(); -// proxy.addConnector(proxyConnector); -// proxy.setHandler(new ConnectHandler()); -// proxy.start(); -// } -// -// @AfterClass -// public static void stop() throws Exception -// { -// stopProxy(); -// stopServer(); -// } -// -// protected static void stopServer() throws Exception -// { -// server.stop(); -// server.join(); -// } -// -// protected static void stopProxy() throws Exception -// { -// proxy.stop(); -// proxy.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; -// if (headers.containsKey("content-length")) -// { -// int readLen = 0; -// int length = Integer.parseInt(headers.get("content-length")); -// body=new StringBuilder(length); -// try -// { -// for (int i = 0; i < length; ++i) -// { -// char c = (char)reader.read(); -// body.append(c); -// readLen++; -// -// } -// -// } -// catch (SocketTimeoutException e) -// { -// System.err.printf("Read %,d bytes (out of an expected %,d bytes)%n",readLen,length); -// throw e; -// } -// } -// else if ("chunked".equals(headers.get("transfer-encoding"))) -// { -// body = new StringBuilder(64*1024); -// while ((line = reader.readLine()) != null) -// { -// if ("0".equals(line)) -// { -// line = reader.readLine(); -// assertEquals("", line); -// break; -// } -// -// try -// { -// Thread.sleep(5); -// } -// catch (InterruptedException e) -// { -// e.printStackTrace(); -// } -// -// 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); -// } -// } -// else throw new IllegalStateException(); -// -// return new Response(code, headers, body.toString().trim()); -// } -// -// protected Socket newSocket() throws IOException -// { -// Socket socket = new Socket("localhost", ((Connector.NetConnector)proxyConnector).getLocalPort()); -// socket.setSoTimeout(10000); -// 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(); -// } -// } + protected Server server; + protected ServerConnector serverConnector; + protected Server proxy; + protected Connector proxyConnector; + protected ConnectHandler connectHandler; + + protected void prepareProxy() throws Exception + { + proxy = new Server(); + proxyConnector = new ServerConnector(proxy); + proxy.addConnector(proxyConnector); + connectHandler = new ConnectHandler(); + proxy.setHandler(connectHandler); + proxy.start(); + } + + @After + public void dispose() throws Exception + { + disposeServer(); + disposeProxy(); + } + + protected void disposeServer() throws Exception + { + server.stop(); + } + + protected void disposeProxy() throws Exception + { + proxy.stop(); + } + + protected SimpleHttpResponse readResponse(BufferedReader reader) throws IOException + { + return new SimpleHttpParser().readResponse(reader); + } + + protected Socket newSocket() throws IOException + { + Socket socket = new Socket("localhost", ((NetworkConnector)proxyConnector).getLocalPort()); + socket.setSoTimeout(5000); + return socket; + } } diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerSSLTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerSSLTest.java index d6b8aaf0d10..53e9079da42 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerSSLTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerSSLTest.java @@ -18,199 +18,184 @@ package org.eclipse.jetty.proxy; -import org.junit.Ignore; +import java.io.BufferedReader; +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 javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +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.ServerConnector; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; -/** - * @version $Revision$ $Date$ - */ -@Ignore public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest { -// @BeforeClass -// public static void init() throws Exception -// { -// SslSelectChannelConnector connector = new SslSelectChannelConnector(); -// connector.setMaxIdleTime(3600000); // TODO remove -// -// String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); -// SslContextFactory cf = connector.getSslContextFactory(); -// cf.setKeyStorePath(keyStorePath); -// cf.setKeyStorePassword("storepwd"); -// cf.setKeyManagerPassword("keypwd"); -// -// startServer(connector, new ServerHandler()); -// startProxy(); -// } -// -// @Test -// public void testGETRequest() throws Exception -// { -// String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); -// String request = "" + -// "CONNECT " + hostPort + " HTTP/1.1\r\n" + -// "Host: " + hostPort + "\r\n" + -// "\r\n"; -// Socket socket = newSocket(); -// socket.setSoTimeout(3600000); // TODO remove -// 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); -// assertEquals("200", response.getCode()); -// assertEquals("GET /echo", response.getBody()); -// } -// finally -// { -// sslSocket.close(); -// } -// } -// finally -// { -// socket.close(); -// } -// } -// -// @Test -// public void testPOSTRequests() throws Exception -// { -// String hostPort = "localhost:" + ((Connector.NetConnector)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); -// 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); -// 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 new X509Certificate[]{}; -// } -// } -// -// private static 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(); -// -// ServletOutputStream output = httpResponse.getOutputStream(); -// output.println(builder.toString()); -// output.write(baos.toByteArray()); -// } -// else -// { -// throw new ServletException(); -// } -// } -// } + private SslContextFactory sslContextFactory; + + @Before + public void prepare() throws Exception + { + sslContextFactory = new SslContextFactory(); + String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore.jks").getAbsolutePath(); + sslContextFactory.setKeyStorePath(keyStorePath); + sslContextFactory.setKeyStorePassword("storepwd"); + String trustStorePath = MavenTestingUtils.getTestResourceFile("truststore.jks").getAbsolutePath(); + sslContextFactory.setTrustStorePath(trustStorePath); + sslContextFactory.setTrustStorePassword("storepwd"); + server = new Server(); + serverConnector = new ServerConnector(server, sslContextFactory); + server.addConnector(serverConnector); + server.setHandler(new ServerHandler()); + server.start(); + prepareProxy(); + } + + @Test + 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"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + + // Be sure the buffered input does not have anything buffered + Assert.assertFalse(input.ready()); + + // Upgrade the socket to SSL + try (SSLSocket sslSocket = wrapSocket(socket)) + { + 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); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + } + } + } + + @Test + 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"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + + // Be sure the buffered input does not have anything buffered + Assert.assertFalse(input.ready()); + + // Upgrade the socket to SSL + try (SSLSocket sslSocket = wrapSocket(socket)) + { + 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); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("POST /echo?param=" + i + "\r\nHELLO", response.getBody()); + } + } + } + } + + private SSLSocket wrapSocket(Socket socket) throws Exception + { + SSLContext sslContext = sslContextFactory.getSslContext(); + SSLSocketFactory socketFactory = sslContext.getSocketFactory(); + SSLSocket sslSocket = (SSLSocket)socketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true); + sslSocket.setUseClientMode(true); + sslSocket.startHandshake(); + return sslSocket; + } + + private static 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; + while ((read = input.read()) >= 0) + baos.write(read); + baos.close(); + + ServletOutputStream output = httpResponse.getOutputStream(); + output.println(builder.toString()); + output.write(baos.toByteArray()); + } + else + { + throw new ServletException(); + } + } + } } diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerTest.java index 0f2bb72df0c..0fcd031115c 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerTest.java @@ -18,634 +18,780 @@ package org.eclipse.jetty.proxy; -import org.junit.Ignore; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Locale; +import java.util.concurrent.ConcurrentMap; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse; +import org.eclipse.jetty.util.B64Code; +import org.eclipse.jetty.util.Callback; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; -@Ignore public class ConnectHandlerTest extends AbstractConnectHandlerTest { -// @BeforeClass -// public static void init() throws Exception -// { -// startServer(new SelectChannelConnector(), new ServerHandler()); -// startProxy(); -// } -// -// @Test -// public void testCONNECT() throws Exception -// { -// String hostPort = "localhost:" + ((Connector.NetConnector)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); -// assertEquals("200", response.getCode()); -// } -// finally -// { -// socket.close(); -// } -// } -// -// @Test -// public void testCONNECTAndGET() throws Exception -// { -// String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); -// String request = "" + -// "CONNECT " + hostPort + " HTTP/1.1\r\n" + -// "Host: " + hostPort + "\r\n" + -// "\r\n"; -// Socket socket = newSocket(); -// socket.setSoTimeout(30000); -// 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); -// 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); -// assertEquals("200", response.getCode()); -// assertEquals("GET /echo", response.getBody()); -// } -// finally -// { -// socket.close(); -// } -// } -// -// -// @Test -// public void testCONNECTBadHostPort() throws Exception -// { -// String invalidHostname = "AMAZEBALLS_BADHOST.webtide.com"; -// -// try -// { -// InetAddress addr = InetAddress.getByName(invalidHostname); -// StringBuilder err = new StringBuilder(); -// err.append("DNS Hijacking detected: "); -// err.append(invalidHostname).append(" should have not returned a valid IP address ["); -// err.append(addr.getHostAddress()).append("]. "); -// err.append("Fix your DNS provider to have this test pass."); -// err.append("\nFor more info see https://en.wikipedia.org/wiki/DNS_hijacking"); -// Assert.assertNull(err.toString(), addr); -// } -// catch (UnknownHostException e) -// { -// // expected path -// } -// -// String hostPort = String.format("%s:%d",invalidHostname,serverConnector.getLocalPort()); -// String request = "" + -// "CONNECT " + hostPort + " HTTP/1.1\r\n" + -// "Host: " + hostPort + "\r\n" + -// "\r\n"; -// Socket socket = newSocket(); -// socket.setSoTimeout(30000); -// try -// { -// OutputStream output = socket.getOutputStream(); -// BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); -// -// output.write(request.getBytes("UTF-8")); -// output.flush(); -// -// // Expect 500 OK from the CONNECT request -// Response response = readResponse(input); -// assertEquals("Response Code", "500", response.getCode()); -// } -// finally -// { -// socket.close(); -// } -// } -// -// @Test -// public void testCONNECT10AndGET() throws Exception -// { -// String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); -// String request = "" + -// "CONNECT " + hostPort + " HTTP/1.0\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); -// 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); -// assertEquals("200", response.getCode()); -// assertEquals("GET /echo", response.getBody()); -// } -// finally -// { -// socket.close(); -// } -// } -// -// @Test -// public void testCONNECTAndGETPipelined() throws Exception -// { -// String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); -// String request = "" + -// "CONNECT " + hostPort + " HTTP/1.1\r\n" + -// "Host: " + hostPort + "\r\n" + -// "\r\n" + -// "GET /echo" + " 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); -// assertEquals("200", response.getCode()); -// -// // The pipelined request must have gone up to the server as is -// response = readResponse(input); -// assertEquals("200", response.getCode()); -// assertEquals("GET /echo", response.getBody()); -// } -// finally -// { -// socket.close(); -// } -// } -// -// @Test -// public void testCONNECTAndMultipleGETs() throws Exception -// { -// String hostPort = "localhost:" + ((Connector.NetConnector)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); -// 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); -// assertEquals("200", response.getCode()); -// assertEquals("GET /echo", response.getBody()); -// } -// } -// finally -// { -// socket.close(); -// } -// } -// -// @Test -// public void testCONNECTAndGETServerStop() throws Exception -// { -// String hostPort = "localhost:" + ((Connector.NetConnector)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); -// 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); -// assertEquals("200", response.getCode()); -// assertEquals("GET /echo", response.getBody()); -// -// // Idle server is shut down -// stopServer(); -// -// int read = input.read(); -// assertEquals(-1, read); -// } -// finally -// { -// socket.close(); -// // Restart the server for the next test -// server.start(); -// } -// } -// -// @Test -// public void testCONNECTAndGETAndServerSideClose() throws Exception -// { -// String hostPort = "localhost:" + ((Connector.NetConnector)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); -// assertEquals("200", response.getCode()); -// -// request = "" + -// "GET /close HTTP/1.1\r\n" + -// "Host: " + hostPort + "\r\n" + -// "\r\n"; -// output.write(request.getBytes("UTF-8")); -// output.flush(); -// -// int read = input.read(); -// assertEquals(-1, read); -// } -// finally -// { -// socket.close(); -// } -// } -// -// @Test -// public void testCONNECTAndPOSTAndGET() throws Exception -// { -// String hostPort = "localhost:" + ((Connector.NetConnector)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); -// 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); -// 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); -// assertEquals("200", response.getCode()); -// assertEquals("GET /echo", response.getBody()); -// } -// finally -// { -// socket.close(); -// } -// } -// -// @Test -// public void testCONNECTAndPOSTWithBigBody() throws Exception -// { -// String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); -// // fails under windows and occasionally on mac due to OOME -// boolean stress = Boolean.getBoolean( "STRESS" ); -// -// if (!stress) -// { -// return; -// } -// -// // Log.getLogger(ConnectHandler.class).setDebugEnabled(true); -// 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); -// assertEquals("200", response.getCode()); -// -// StringBuilder body = new StringBuilder(); -// String chunk = "0123456789ABCDEF"; -// for (int i = 0; i < 1024 * 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); -// assertEquals("200", response.getCode()); -// assertEquals("POST /echo\r\n" + body, response.getBody()); -// } -// finally -// { -// socket.close(); -// } -// } -// -// @Test -// public void testCONNECTAndPOSTWithContext() throws Exception -// { -// final String contextKey = "contextKey"; -// final String contextValue = "contextValue"; -// -// // Replace the default ProxyHandler with a subclass to test context information passing -// stopProxy(); -// proxy.setHandler(new ConnectHandler() -// { -// @Override -// protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException -// { -// request.setAttribute(contextKey, contextValue); -// return super.handleAuthentication(request, response, address); -// } -// -// @Override -// protected SocketChannel connect(HttpServletRequest request, String host, int port) throws IOException -// { -// assertEquals(contextValue, request.getAttribute(contextKey)); -// return super.connect(request, host, port); -// } -// -// @Override -// protected void prepareContext(HttpServletRequest request, ConcurrentMap context) -// { -// // Transfer data from the HTTP request to the connection context -// assertEquals(contextValue, request.getAttribute(contextKey)); -// context.put(contextKey, request.getAttribute(contextKey)); -// } -// -// @Override -// protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap context) throws IOException -// { -// assertEquals(contextValue, context.get(contextKey)); -// return super.read(endPoint, buffer, context); -// } -// -// @Override -// protected int write(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap context) throws IOException -// { -// assertEquals(contextValue, context.get(contextKey)); -// return super.write(endPoint, buffer, context); -// } -// }); -// proxy.start(); -// -// String hostPort = "localhost:" + ((Connector.NetConnector)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); -// assertEquals("200", response.getCode()); -// -// String body = "0123456789ABCDEF"; -// 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); -// assertEquals("200", response.getCode()); -// assertEquals("POST /echo\r\n" + body, response.getBody()); -// } -// finally -// { -// socket.close(); -// } -// } -// -// @Test -// public void testCONNECTAndGETPipelinedAndOutputShutdown() throws Exception -// { -// // TODO needs to be further investigated -// assumeTrue(!OS.IS_OSX); -// -// String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); -// String request = "" + -// "CONNECT " + hostPort + " HTTP/1.1\r\n" + -// "Host: " + hostPort + "\r\n" + -// "\r\n" + -// "GET /echo" + " 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(); -// socket.shutdownOutput(); -// -// // Expect 200 OK from the CONNECT request -// Response response = readResponse(input); -// assertEquals("200", response.getCode()); -// -// // The pipelined request must have gone up to the server as is -// response = readResponse(input); -// assertEquals("200", response.getCode()); -// assertEquals("GET /echo", response.getBody()); -// } -// finally -// { -// socket.close(); -// } -// } -// -// @Test -// public void testCONNECTAndGETAndOutputShutdown() throws Exception -// { -// // TODO needs to be further investigated -// assumeTrue(!OS.IS_OSX); -// -// String hostPort = "localhost:" + ((Connector.NetConnector)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); -// 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(); -// socket.shutdownOutput(); -// -// // The pipelined request must have gone up to the server as is -// response = readResponse(input); -// assertEquals("200", response.getCode()); -// assertEquals("GET /echo", response.getBody()); -// } -// finally -// { -// socket.close(); -// } -// } -// -// private static 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(); -// -// ServletOutputStream output = httpResponse.getOutputStream(); -// output.println(builder.toString()); -// output.write(baos.toByteArray()); -// } -// else if ("/close".equals(uri)) -// { -// request.getHttpChannel().getConnection().getEndPoint().close(); -// } -// else -// { -// throw new ServletException(); -// } -// } -// } + @Before + public void prepare() throws Exception + { + server = new Server(); + serverConnector = new ServerConnector(server); + server.addConnector(serverConnector); + server.setHandler(new ServerHandler()); + server.start(); + prepareProxy(); + } + + @Test + public void testCONNECT() throws Exception + { + String hostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + } + } + + @Test + public void testCONNECTAndGET() throws Exception + { + String hostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.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); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + } + } + + @Test + public void testProxyWhiteList() throws Exception + { + int port = serverConnector.getLocalPort(); + String hostPort = "127.0.0.1:" + port; + connectHandler.getWhiteListHosts().add(hostPort); + + // Try with the wrong host + String request = "" + + "CONNECT localhost:" + port + " HTTP/1.1\r\n" + + "Host: localhost:" + port + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + OutputStream output = socket.getOutputStream(); + BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 403 from the CONNECT request + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("403", response.getCode()); + + // Socket should be closed + Assert.assertEquals(-1, input.read()); + } + + // Try again with the right host + request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + OutputStream output = socket.getOutputStream(); + BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 200 from the CONNECT request + SimpleHttpResponse response = readResponse(input); + Assert.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); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + } + } + + @Test + public void testProxyBlackList() throws Exception + { + int port = serverConnector.getLocalPort(); + String hostPort = "localhost:" + port; + connectHandler.getBlackListHosts().add(hostPort); + + // Try with the wrong host + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + OutputStream output = socket.getOutputStream(); + BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 403 from the CONNECT request + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("403", response.getCode()); + + // Socket should be closed + Assert.assertEquals(-1, input.read()); + } + + // Try again with the right host + request = "" + + "CONNECT 127.0.0.1:" + port + " HTTP/1.1\r\n" + + "Host: 127.0.0.1:" + port + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + OutputStream output = socket.getOutputStream(); + BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 200 from the CONNECT request + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + + request = "" + + "GET /echo" + " HTTP/1.1\r\n" + + "Host: 127.0.0.1:" + port + "\r\n" + + "\r\n"; + output.write(request.getBytes("UTF-8")); + output.flush(); + + response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + } + } + + @Test + public void testProxyAuthentication() throws Exception + { + disposeProxy(); + connectHandler = new ConnectHandler() + { + @Override + protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) + { + String proxyAuthorization = request.getHeader("Proxy-Authorization"); + if (proxyAuthorization == null) + { + response.setHeader("Proxy-Authenticate", "Basic realm=\"test\""); + return false; + } + String b64 = proxyAuthorization.substring("Basic ".length()); + String credentials = B64Code.decode(b64, "UTF-8"); + return "test:test".equals(credentials); + } + }; + proxy.setHandler(connectHandler); + proxy.start(); + + int port = serverConnector.getLocalPort(); + String hostPort = "localhost:" + port; + + // Try without authentication + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + OutputStream output = socket.getOutputStream(); + BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 407 from the CONNECT request + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("407", response.getCode()); + Assert.assertTrue(response.getHeaders().containsKey("Proxy-Authenticate".toLowerCase(Locale.ENGLISH))); + + // Socket should be closed + Assert.assertEquals(-1, input.read()); + } + + // Try with authentication + String credentials = "Basic " + B64Code.encode("test:test"); + request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "Proxy-Authorization: " + credentials + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + OutputStream output = socket.getOutputStream(); + BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 200 from the CONNECT request + SimpleHttpResponse response = readResponse(input); + Assert.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); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + } + } + + @Test + public void testCONNECTBadHostPort() throws Exception + { + String invalidHostname = "badHost.webtide.com"; + + try + { + InetAddress address = InetAddress.getByName(invalidHostname); + StringBuilder err = new StringBuilder(); + err.append("DNS Hijacking detected: "); + err.append(invalidHostname).append(" should have not returned a valid IP address ["); + err.append(address.getHostAddress()).append("]. "); + err.append("Fix your DNS provider to have this test pass."); + err.append("\nFor more info see https://en.wikipedia.org/wiki/DNS_hijacking"); + Assert.assertNull(err.toString(), address); + } + catch (UnknownHostException e) + { + // expected path + } + + String hostPort = String.format("%s:%d", invalidHostname, serverConnector.getLocalPort()); + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + Socket socket = newSocket(); + socket.setSoTimeout(30000); + try + { + OutputStream output = socket.getOutputStream(); + BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 500 OK from the CONNECT request + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("Response Code", "500", response.getCode()); + } + finally + { + socket.close(); + } + } + + @Test + public void testCONNECT10AndGET() throws Exception + { + String hostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "CONNECT " + hostPort + " HTTP/1.0\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.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); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + } + } + + @Test + public void testCONNECTAndGETPipelined() throws Exception + { + String hostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n" + + "GET /echo" + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + + // The pipelined request must have gone up to the server as is + response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + } + } + + @Test + public void testCONNECTAndMultipleGETs() throws Exception + { + String hostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.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); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + } + } + } + + @Test + 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"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.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); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + + // Idle server is shut down + disposeServer(); + + int read = input.read(); + Assert.assertEquals(-1, read); + } + } + + @Test + public void testCONNECTAndGETAndServerSideClose() throws Exception + { + String hostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + + request = "" + + "GET /close HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + output.write(request.getBytes("UTF-8")); + output.flush(); + + int read = input.read(); + Assert.assertEquals(-1, read); + } + } + + @Test + public void testCONNECTAndPOSTAndGET() throws Exception + { + String hostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.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); + Assert.assertEquals("200", response.getCode()); + Assert.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); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + } + } + + @Test + 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"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + + StringBuilder body = new StringBuilder(); + String chunk = "0123456789ABCDEF"; + for (int i = 0; i < 1024 * 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); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("POST /echo\r\n" + body, response.getBody()); + } + } + + @Test + public void testCONNECTAndPOSTWithContext() throws Exception + { + final String contextKey = "contextKey"; + final String contextValue = "contextValue"; + + // Replace the default ProxyHandler with a subclass to test context information passing + disposeProxy(); + proxy.setHandler(new ConnectHandler() + { + @Override + protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) + { + request.setAttribute(contextKey, contextValue); + return super.handleAuthentication(request, response, address); + } + + @Override + protected void prepareContext(HttpServletRequest request, ConcurrentMap context) + { + // Transfer data from the HTTP request to the connection context + Assert.assertEquals(contextValue, request.getAttribute(contextKey)); + context.put(contextKey, request.getAttribute(contextKey)); + } + + @Override + protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap context) throws IOException + { + Assert.assertEquals(contextValue, context.get(contextKey)); + return super.read(endPoint, buffer, context); + } + + @Override + protected void write(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap context, Callback callback) + { + Assert.assertEquals(contextValue, context.get(contextKey)); + super.write(endPoint, buffer, context, callback); + } + }); + proxy.start(); + + String hostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + + String body = "0123456789ABCDEF"; + 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); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("POST /echo\r\n" + body, response.getBody()); + } + } + + @Test + public void testCONNECTAndGETPipelinedAndOutputShutdown() throws Exception + { + String hostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n" + + "GET /echo" + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + OutputStream output = socket.getOutputStream(); + BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + output.write(request.getBytes("UTF-8")); + output.flush(); + socket.shutdownOutput(); + + // Expect 200 OK from the CONNECT request + SimpleHttpResponse response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + + // The pipelined request must have gone up to the server as is + response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + } + } + + @Test + public void testCONNECTAndGETAndOutputShutdown() throws Exception + { + String hostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "CONNECT " + hostPort + " HTTP/1.1\r\n" + + "Host: " + hostPort + "\r\n" + + "\r\n"; + try (Socket socket = newSocket()) + { + 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 + SimpleHttpResponse response = readResponse(input); + Assert.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(); + socket.shutdownOutput(); + + // The pipelined request must have gone up to the server as is + response = readResponse(input); + Assert.assertEquals("200", response.getCode()); + Assert.assertEquals("GET /echo", response.getBody()); + } + } + + private static 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(); + switch (uri) + { + case "/echo": + { + 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; + while ((read = input.read()) >= 0) + baos.write(read); + baos.close(); + + ServletOutputStream output = httpResponse.getOutputStream(); + output.println(builder.toString()); + output.write(baos.toByteArray()); + break; + } + case "/close": + { + request.getHttpChannel().getEndPoint().close(); + break; + } + default: + { + throw new ServletException(); + } + } + } + } } diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerUnitTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerUnitTest.java deleted file mode 100644 index 3bc16a53d41..00000000000 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerUnitTest.java +++ /dev/null @@ -1,61 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - -package org.eclipse.jetty.proxy; - -/* ------------------------------------------------------------ */ -/** - */ -//@RunWith(MockitoJUnitRunner.class) -public class ConnectHandlerUnitTest -{ -// @Mock -// private EndPoint endPoint; -// -// // TODO update for jetty-9 -// @Test -// @Ignore -// public void testPartialWritesWithNonFullBuffer() throws IOException -// { -// /* -// ConnectHandler connectHandler = new ConnectHandler(); -// final byte[] bytes = "foo bar".getBytes(); -// Buffer buffer = new ByteArrayBuffer(bytes.length * 2); -// buffer.put(bytes); -// when(endPoint.flush(buffer)).thenAnswer(new Answer() -// { -// public Object answer(InvocationOnMock invocation) -// { -// Object[] args = invocation.getArguments(); -// Buffer buffer = (Buffer)args[0]; -// int skip = bytes.length/2; -// buffer.skip(skip); -// return skip; -// } -// }); -// when(endPoint.blockWritable(anyInt())).thenReturn(true); -// -// // method to test -// connectHandler.write(endPoint,buffer,null); -// -// assertThat(buffer.length(),is(0)); -// */ -// } -// -// -} diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java index c53b32baff2..e48fe59f59c 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java @@ -536,12 +536,12 @@ public class ProxyServletTest } @Test - public void testProxyWhitelist() throws Exception + public void testProxyWhiteList() throws Exception { prepareProxy(new ProxyServlet()); prepareServer(new EmptyHttpServlet()); int port = serverConnector.getLocalPort(); - proxyServlet.getWhitelistHosts().add("127.0.0.1:" + port); + proxyServlet.getWhiteListHosts().add("127.0.0.1:" + port); // Try with the wrong host ContentResponse response = client.newRequest("localhost", port) @@ -557,12 +557,12 @@ public class ProxyServletTest } @Test - public void testProxyBlacklist() throws Exception + public void testProxyBlackList() throws Exception { prepareProxy(new ProxyServlet()); prepareServer(new EmptyHttpServlet()); int port = serverConnector.getLocalPort(); - proxyServlet.getBlacklistHosts().add("localhost:" + port); + proxyServlet.getBlackListHosts().add("localhost:" + port); // Try with the wrong host ContentResponse response = client.newRequest("localhost", port) @@ -744,6 +744,12 @@ public class ProxyServletTest Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER)); } + @Test + public void testProxyAuthentication() throws Exception + { + // TODO: + } + private static class EmptyHttpServlet extends HttpServlet { @Override diff --git a/jetty-proxy/src/test/resources/keystore.jks b/jetty-proxy/src/test/resources/keystore.jks new file mode 100644 index 00000000000..428ba54776e Binary files /dev/null and b/jetty-proxy/src/test/resources/keystore.jks differ diff --git a/jetty-proxy/src/test/resources/truststore.jks b/jetty-proxy/src/test/resources/truststore.jks new file mode 100644 index 00000000000..839cb8c3515 Binary files /dev/null and b/jetty-proxy/src/test/resources/truststore.jks differ