From a83d24b3d4d3400f66a5a4ba56dfa0117561c7e2 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Thu, 3 Jan 2019 21:53:05 +0200 Subject: [PATCH 01/43] Initial commit --- .../proxy/impl/ClientToProxyConnection.java | 81 +++++++++++++------ .../proxy/impl/ConnectionState.java | 2 + .../proxy/impl/ProxyConnection.java | 2 + 3 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index b476c14c2..980f579b5 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -3,7 +3,10 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; @@ -137,6 +140,8 @@ public class ClientToProxyConnection extends ProxyConnection { private AtomicBoolean authenticated = new AtomicBoolean(); + public HttpRequest initialHttpRequest; + private final GlobalTrafficShapingHandler globalTrafficShapingHandler; ClientToProxyConnection( @@ -201,8 +206,12 @@ protected ConnectionState readHTTPInitial(HttpRequest httpRequest) { LOG.debug("Not authenticated!!"); return AWAITING_PROXY_AUTHENTICATION; } else { - return doReadHTTPInitial(httpRequest); + this.ctx.fireChannelRead(httpRequest); } + + initialHttpRequest = httpRequest; + + return ConnectionState.CLIENT_TO_PROXY_PROCESSING; } /** @@ -223,30 +232,7 @@ protected ConnectionState readHTTPInitial(HttpRequest httpRequest) { * @param httpRequest * @return */ - private ConnectionState doReadHTTPInitial(HttpRequest httpRequest) { - // Make a copy of the original request - final HttpRequest currentRequest = copy(httpRequest); - - // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response - // should not be filtered), fall back to the default no-op filter source. - HttpFilters filterInstance; - try { - filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); - } finally { - // releasing a copied http request - if (currentRequest instanceof ReferenceCounted) { - ((ReferenceCounted)currentRequest).release(); - } - } - if (filterInstance != null) { - currentFilters = filterInstance; - } else { - currentFilters = HttpFiltersAdapter.NOOP_FILTER; - } - - // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required - HttpResponse clientToProxyFilterResponse = currentFilters.clientToProxyRequest(httpRequest); - + public ConnectionState doReadHTTPInitial(HttpRequest httpRequest, HttpResponse clientToProxyFilterResponse) { if (clientToProxyFilterResponse != null) { LOG.debug("Responding to client with short-circuit response from filter: {}", clientToProxyFilterResponse); @@ -366,6 +352,45 @@ private ConnectionState doReadHTTPInitial(HttpRequest httpRequest) { } } + @Sharable + protected class ClientToProxyProcessor extends ChannelDuplexHandler { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + + HttpRequest httpRequest = (HttpRequest) msg; + // Make a copy of the original request + final HttpRequest currentRequest = copy(httpRequest); + + // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response + // should not be filtered), fall back to the default no-op filter source. + HttpFilters filterInstance; + try { + filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); + } finally { + // releasing a copied http request + if (currentRequest instanceof ReferenceCounted) { + ((ReferenceCounted)currentRequest).release(); + } + } + if (filterInstance != null) { + currentFilters = filterInstance; + } else { + currentFilters = HttpFiltersAdapter.NOOP_FILTER; + } + + // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required + HttpResponse httpResponse = currentFilters.clientToProxyRequest(httpRequest); + ctx.fireChannelRead(httpResponse); + } + + @Override + public void write(ChannelHandlerContext ctx, + Object msg, ChannelPromise promise) throws Exception { + super.write(ctx, msg, promise); + } + } + /** * Returns true if the specified request is a request to an origin server, rather than to a proxy server. If this * request is being MITM'd, this method always returns false. The format of requests to a proxy server are defined @@ -830,7 +855,11 @@ private void initChannelPipeline(ChannelPipeline pipeline) { pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(this)); } - pipeline.addLast("handler", this); + pipeline.addLast("handlerBegin", this); + + pipeline.addLast("clientToProxyProcessor", new ClientToProxyProcessor()); + + pipeline.addLast("handlerEnd", this); } diff --git a/src/main/java/org/littleshoot/proxy/impl/ConnectionState.java b/src/main/java/org/littleshoot/proxy/impl/ConnectionState.java index 371fcd586..1e28d81ff 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ConnectionState.java +++ b/src/main/java/org/littleshoot/proxy/impl/ConnectionState.java @@ -6,6 +6,8 @@ enum ConnectionState { */ CONNECTING(true), + CLIENT_TO_PROXY_PROCESSING, + /** * In the middle of doing an SSL handshake. */ diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java index 1f4ee876b..c435deacc 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java @@ -160,6 +160,8 @@ private void readHTTP(HttpObject httpObject) { // to require authentication. } break; + case CLIENT_TO_PROXY_PROCESSING: + ((ClientToProxyConnection)this).doReadHTTPInitial(((ClientToProxyConnection)this).initialHttpRequest, (HttpResponse)httpObject); case CONNECTING: LOG.warn("Attempted to read from connection that's in the process of connecting. This shouldn't happen."); break; From 815556106a8a4847c98811b57fc551d49a45fff9 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Thu, 3 Jan 2019 23:41:32 +0200 Subject: [PATCH 02/43] Adds cached executor --- .../proxy/impl/ClientToProxyConnection.java | 72 ++++++++++--------- .../proxy/impl/ConnectionState.java | 2 - .../proxy/impl/ProxyConnection.java | 24 ++++--- .../proxy/impl/ProxyToServerConnection.java | 7 +- 4 files changed, 59 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 980f579b5..7a5d98565 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -4,6 +4,7 @@ import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; @@ -49,6 +50,8 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -83,6 +86,7 @@ * . *

*/ +@Sharable public class ClientToProxyConnection extends ProxyConnection { private static final HttpResponseStatus CONNECTION_ESTABLISHED = new HttpResponseStatus( 200, "Connection established"); @@ -140,7 +144,7 @@ public class ClientToProxyConnection extends ProxyConnection { private AtomicBoolean authenticated = new AtomicBoolean(); - public HttpRequest initialHttpRequest; + public HttpResponse clientToProxyResponse; private final GlobalTrafficShapingHandler globalTrafficShapingHandler; @@ -182,7 +186,7 @@ public void operationComplete( **************************************************************************/ @Override - protected ConnectionState readHTTPInitial(HttpRequest httpRequest) { + protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRequest httpRequest) { LOG.debug("Received raw request: {}", httpRequest); // if we cannot parse the request, immediately return a 400 and close the connection, since we do not know what state @@ -206,12 +210,10 @@ protected ConnectionState readHTTPInitial(HttpRequest httpRequest) { LOG.debug("Not authenticated!!"); return AWAITING_PROXY_AUTHENTICATION; } else { - this.ctx.fireChannelRead(httpRequest); + ctx.fireChannelRead(httpRequest); } - initialHttpRequest = httpRequest; - - return ConnectionState.CLIENT_TO_PROXY_PROCESSING; + return state(); } /** @@ -232,11 +234,11 @@ protected ConnectionState readHTTPInitial(HttpRequest httpRequest) { * @param httpRequest * @return */ - public ConnectionState doReadHTTPInitial(HttpRequest httpRequest, HttpResponse clientToProxyFilterResponse) { - if (clientToProxyFilterResponse != null) { - LOG.debug("Responding to client with short-circuit response from filter: {}", clientToProxyFilterResponse); + public ConnectionState doReadHTTPInitial(HttpRequest httpRequest) { + if (clientToProxyResponse != null) { + LOG.debug("Responding to client with short-circuit response from filter: {}", clientToProxyResponse); - boolean keepAlive = respondWithShortCircuitResponse(clientToProxyFilterResponse); + boolean keepAlive = respondWithShortCircuitResponse(clientToProxyResponse); if (keepAlive) { return AWAITING_INITIAL; } else { @@ -352,36 +354,42 @@ public ConnectionState doReadHTTPInitial(HttpRequest httpRequest, HttpResponse c } } + private static final Executor executor = Executors.newCachedThreadPool(); + @Sharable protected class ClientToProxyProcessor extends ChannelDuplexHandler { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - HttpRequest httpRequest = (HttpRequest) msg; - // Make a copy of the original request - final HttpRequest currentRequest = copy(httpRequest); - - // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response - // should not be filtered), fall back to the default no-op filter source. - HttpFilters filterInstance; - try { - filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); - } finally { - // releasing a copied http request - if (currentRequest instanceof ReferenceCounted) { - ((ReferenceCounted)currentRequest).release(); + executor.execute(() -> { + HttpRequest httpRequest = (HttpRequest) msg; + // Make a copy of the original request + final HttpRequest currentRequest = copy(httpRequest); + + // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response + // should not be filtered), fall back to the default no-op filter source. + HttpFilters filterInstance; + try { + filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); + } finally { + // releasing a copied http request + if (currentRequest instanceof ReferenceCounted) { + ((ReferenceCounted)currentRequest).release(); + } } - } - if (filterInstance != null) { - currentFilters = filterInstance; - } else { - currentFilters = HttpFiltersAdapter.NOOP_FILTER; - } + if (filterInstance != null) { + currentFilters = filterInstance; + } else { + currentFilters = HttpFiltersAdapter.NOOP_FILTER; + } + + // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required + clientToProxyResponse = currentFilters.clientToProxyRequest(httpRequest); + + ctx.fireChannelRead(httpRequest); + }); - // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required - HttpResponse httpResponse = currentFilters.clientToProxyRequest(httpRequest); - ctx.fireChannelRead(httpResponse); } @Override diff --git a/src/main/java/org/littleshoot/proxy/impl/ConnectionState.java b/src/main/java/org/littleshoot/proxy/impl/ConnectionState.java index 1e28d81ff..371fcd586 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ConnectionState.java +++ b/src/main/java/org/littleshoot/proxy/impl/ConnectionState.java @@ -6,8 +6,6 @@ enum ConnectionState { */ CONNECTING(true), - CLIENT_TO_PROXY_PROCESSING, - /** * In the middle of doing an SSL handshake. */ diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java index c435deacc..ead0ac69e 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java @@ -108,7 +108,7 @@ protected ProxyConnection(ConnectionState initialState, * * @param msg */ - protected void read(Object msg) { + protected void read(ChannelHandlerContext ctx, Object msg) { LOG.debug("Reading: {}", msg); lastReadTime = System.currentTimeMillis(); @@ -118,7 +118,7 @@ protected void read(Object msg) { readRaw((ByteBuf) msg); } else { // If not tunneling, then we are always dealing with HttpObjects. - readHTTP((HttpObject) msg); + readHTTP(ctx, (HttpObject) msg); } } @@ -128,12 +128,16 @@ protected void read(Object msg) { * @param httpObject */ @SuppressWarnings("unchecked") - private void readHTTP(HttpObject httpObject) { + private void readHTTP(ChannelHandlerContext ctx, HttpObject httpObject) { ConnectionState nextState = getCurrentState(); switch (getCurrentState()) { case AWAITING_INITIAL: if (httpObject instanceof HttpMessage) { - nextState = readHTTPInitial((I) httpObject); + if (this.ctx.name().equals("handlerEnd")) { + nextState = ((ClientToProxyConnection)this).doReadHTTPInitial((HttpRequest) httpObject); + } else { + nextState = readHTTPInitial(ctx, (I) httpObject); + } } else { // Similar to the AWAITING_PROXY_AUTHENTICATION case below, we may enter an AWAITING_INITIAL // state if the proxy responded to an earlier request with a 502 or 504 response, or a short-circuit @@ -151,7 +155,7 @@ private void readHTTP(HttpObject httpObject) { case AWAITING_PROXY_AUTHENTICATION: if (httpObject instanceof HttpRequest) { // Once we get an HttpRequest, try to process it as usual - nextState = readHTTPInitial((I) httpObject); + nextState = readHTTPInitial(ctx, (I) httpObject); } else { // Anything that's not an HttpRequest that came in while // we're pending authentication gets dropped on the floor. This @@ -160,8 +164,6 @@ private void readHTTP(HttpObject httpObject) { // to require authentication. } break; - case CLIENT_TO_PROXY_PROCESSING: - ((ClientToProxyConnection)this).doReadHTTPInitial(((ClientToProxyConnection)this).initialHttpRequest, (HttpResponse)httpObject); case CONNECTING: LOG.warn("Attempted to read from connection that's in the process of connecting. This shouldn't happen."); break; @@ -191,7 +193,7 @@ private void readHTTP(HttpObject httpObject) { * @param httpObject * @return */ - protected abstract ConnectionState readHTTPInitial(I httpObject); + protected abstract ConnectionState readHTTPInitial(ChannelHandlerContext ctx, I httpObject); /** * Implement this to handle reading a chunk in a chunked transfer. @@ -535,6 +537,10 @@ protected void become(ConnectionState state) { this.currentState = state; } + protected ConnectionState state() { + return this.currentState; + } + protected ConnectionState getCurrentState() { return currentState; } @@ -587,7 +593,7 @@ ProxyConnectionLogger getLOG() { @Override protected final void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { - read(msg); + read(ctx, msg); } @Override diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 96e470864..10d8d9fb8 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -8,6 +8,7 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; @@ -209,19 +210,19 @@ private ProxyToServerConnection( **************************************************************************/ @Override - protected void read(Object msg) { + protected void read(ChannelHandlerContext ctx, Object msg) { if (isConnecting()) { LOG.debug( "In the middle of connecting, forwarding message to connection flow: {}", msg); this.connectionFlow.read(msg); } else { - super.read(msg); + super.read(ctx, msg); } } @Override - protected ConnectionState readHTTPInitial(HttpResponse httpResponse) { + protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpResponse httpResponse) { LOG.debug("Received raw response: {}", httpResponse); if (httpResponse.getDecoderResult().isFailure()) { From 02fc4402cb151893a9642b5624147e4c6d3c3112 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 00:21:01 +0200 Subject: [PATCH 03/43] try to fix build --- src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java index ead0ac69e..80fd94e92 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java @@ -133,7 +133,7 @@ private void readHTTP(ChannelHandlerContext ctx, HttpObject httpObject) { switch (getCurrentState()) { case AWAITING_INITIAL: if (httpObject instanceof HttpMessage) { - if (this.ctx.name().equals("handlerEnd")) { + if (ctx.name().equals("handlerEnd")) { nextState = ((ClientToProxyConnection)this).doReadHTTPInitial((HttpRequest) httpObject); } else { nextState = readHTTPInitial(ctx, (I) httpObject); From ad56e6141fd161511fd160fd1ada5d4d11206b6d Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 00:38:48 +0200 Subject: [PATCH 04/43] Remove executor for now --- .../org/littleshoot/proxy/impl/ClientToProxyConnection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 7a5d98565..7d2761e7a 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -362,7 +362,7 @@ protected class ClientToProxyProcessor extends ChannelDuplexHandler { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - executor.execute(() -> { +// executor.execute(() -> { HttpRequest httpRequest = (HttpRequest) msg; // Make a copy of the original request final HttpRequest currentRequest = copy(httpRequest); @@ -388,7 +388,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception clientToProxyResponse = currentFilters.clientToProxyRequest(httpRequest); ctx.fireChannelRead(httpRequest); - }); +// }); } From 58fc61df0d73f6d0527670fe89dcf2d5e48322df Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 00:52:56 +0200 Subject: [PATCH 05/43] attempt to fix the build --- .../java/org/littleshoot/proxy/impl/ProxyConnection.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java index 80fd94e92..d31fb48a0 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java @@ -155,7 +155,11 @@ private void readHTTP(ChannelHandlerContext ctx, HttpObject httpObject) { case AWAITING_PROXY_AUTHENTICATION: if (httpObject instanceof HttpRequest) { // Once we get an HttpRequest, try to process it as usual - nextState = readHTTPInitial(ctx, (I) httpObject); + if (ctx.name().equals("handlerEnd")) { + nextState = ((ClientToProxyConnection)this).doReadHTTPInitial((HttpRequest) httpObject); + } else { + nextState = readHTTPInitial(ctx, (I) httpObject); + } } else { // Anything that's not an HttpRequest that came in while // we're pending authentication gets dropped on the floor. This From dab7744b38721cf89fe0fcffd14992bd7c4ebc45 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 01:26:34 +0200 Subject: [PATCH 06/43] attempt 2 to fix the build --- .../littleshoot/proxy/impl/ClientToProxyConnection.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 7d2761e7a..8469dc98a 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -213,7 +213,7 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRequest ctx.fireChannelRead(httpRequest); } - return state(); + return getCurrentState(); } /** @@ -387,6 +387,11 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required clientToProxyResponse = currentFilters.clientToProxyRequest(httpRequest); + if (msg instanceof ReferenceCounted) { + LOG.debug("Retaining reference counted message"); + ((ReferenceCounted) msg).retain(); + } + ctx.fireChannelRead(httpRequest); // }); From e291eaeda849873350224452533d6c3dd39682ef Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 02:07:44 +0200 Subject: [PATCH 07/43] Try with executor --- .../proxy/impl/ClientToProxyConnection.java | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 8469dc98a..79e1e3d9a 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -362,39 +362,51 @@ protected class ClientToProxyProcessor extends ChannelDuplexHandler { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { -// executor.execute(() -> { - HttpRequest httpRequest = (HttpRequest) msg; - // Make a copy of the original request - final HttpRequest currentRequest = copy(httpRequest); - - // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response - // should not be filtered), fall back to the default no-op filter source. - HttpFilters filterInstance; - try { - filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); - } finally { - // releasing a copied http request - if (currentRequest instanceof ReferenceCounted) { - ((ReferenceCounted)currentRequest).release(); - } - } - if (filterInstance != null) { - currentFilters = filterInstance; - } else { - currentFilters = HttpFiltersAdapter.NOOP_FILTER; - } + if (msg instanceof ReferenceCounted) { + LOG.debug("Retaining reference counted message"); + ((ReferenceCounted) msg).retain(); + } + + HttpRequest httpRequest = (HttpRequest) msg; + + if (ProxyUtils.isChunked(httpRequest)) { + process(ctx, msg, httpRequest); + } else { + executor.execute(() -> process(ctx, msg, httpRequest)); + } + + } - // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required - clientToProxyResponse = currentFilters.clientToProxyRequest(httpRequest); + private void process(ChannelHandlerContext ctx, Object msg, HttpRequest httpRequest) { + // Make a copy of the original request + final HttpRequest currentRequest = copy(httpRequest); - if (msg instanceof ReferenceCounted) { - LOG.debug("Retaining reference counted message"); - ((ReferenceCounted) msg).retain(); + // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response + // should not be filtered), fall back to the default no-op filter source. + HttpFilters filterInstance; + try { + filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); + } finally { + // releasing a copied http request + if (currentRequest instanceof ReferenceCounted) { + ((ReferenceCounted)currentRequest).release(); } + } + if (filterInstance != null) { + currentFilters = filterInstance; + } else { + currentFilters = HttpFiltersAdapter.NOOP_FILTER; + } + + // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required + clientToProxyResponse = currentFilters.clientToProxyRequest(httpRequest); - ctx.fireChannelRead(httpRequest); -// }); + if (msg instanceof ReferenceCounted) { + LOG.debug("Retaining reference counted message"); + ((ReferenceCounted) msg).retain(); + } + ctx.fireChannelRead(httpRequest); } @Override From de5ac21f6d7f71f8b71533366a0546ada8522fae Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 02:18:53 +0200 Subject: [PATCH 08/43] Try proxy to client response in the processor --- .../proxy/impl/ClientToProxyConnection.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 79e1e3d9a..b25643f62 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -412,7 +412,13 @@ private void process(ChannelHandlerContext ctx, Object msg, HttpRequest httpRequ @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - super.write(ctx, msg, promise); + HttpObject httpObject = ((HttpObject)msg); + httpObject = currentFilters.proxyToClientResponse(httpObject); + if (httpObject == null) { + forceDisconnect(currentServerConnection); + } else { + super.write(ctx, msg, promise); + } } } @@ -519,12 +525,6 @@ void respond(ProxyToServerConnection serverConnection, HttpFilters filters, modifyResponseHeadersToReflectProxying(httpResponse); } - httpObject = filters.proxyToClientResponse(httpObject); - if (httpObject == null) { - forceDisconnect(serverConnection); - return; - } - write(httpObject); if (ProxyUtils.isLastChunk(httpObject)) { From ac1ea265f994be6de07ca29b6372b958ab5cb890 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 02:27:27 +0200 Subject: [PATCH 09/43] Revert proxy to client response in the processor --- .../proxy/impl/ClientToProxyConnection.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index b25643f62..1ddf040bb 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -412,13 +412,7 @@ private void process(ChannelHandlerContext ctx, Object msg, HttpRequest httpRequ @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - HttpObject httpObject = ((HttpObject)msg); - httpObject = currentFilters.proxyToClientResponse(httpObject); - if (httpObject == null) { - forceDisconnect(currentServerConnection); - } else { - super.write(ctx, msg, promise); - } + super.write(ctx, msg, promise); } } @@ -525,6 +519,12 @@ void respond(ProxyToServerConnection serverConnection, HttpFilters filters, modifyResponseHeadersToReflectProxying(httpResponse); } + httpObject = filters.proxyToClientResponse(httpObject); + if (httpObject == null) { + forceDisconnect(serverConnection); + return; + } + write(httpObject); if (ProxyUtils.isLastChunk(httpObject)) { From f38c65bde48ee50b3f904bd7e259c91246be51c6 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 02:30:28 +0200 Subject: [PATCH 10/43] Wrap processor in global state --- .../littleshoot/proxy/impl/ClientToProxyConnection.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 1ddf040bb..1b04aa9a1 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -372,7 +372,14 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception if (ProxyUtils.isChunked(httpRequest)) { process(ctx, msg, httpRequest); } else { - executor.execute(() -> process(ctx, msg, httpRequest)); + executor.execute(() -> { + proxyServer.getGlobalStateHandler().restoreFromChannel(channel); + try { + process(ctx, msg, httpRequest); + } finally { + proxyServer.getGlobalStateHandler().clear(); + } + }); } } From f2c7ef23fff8e45ff5a3bd016845aeca9fe8afb9 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 04:24:20 +0200 Subject: [PATCH 11/43] Global state wrapper --- .../proxy/impl/ClientToProxyConnection.java | 139 +++++++++++------- .../proxy/impl/DefaultHttpProxyServer.java | 3 +- 2 files changed, 91 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 1b04aa9a1..df786cf0a 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -8,6 +8,8 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; +import io.netty.channel.DefaultEventLoop; +import io.netty.channel.EventLoopGroup; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; @@ -25,6 +27,7 @@ import io.netty.handler.timeout.IdleStateHandler; import io.netty.handler.traffic.GlobalTrafficShapingHandler; import io.netty.util.ReferenceCounted; +import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import org.apache.commons.lang3.StringUtils; @@ -153,10 +156,13 @@ public class ClientToProxyConnection extends ProxyConnection { SslEngineSource sslEngineSource, boolean authenticateClients, ChannelPipeline pipeline, - GlobalTrafficShapingHandler globalTrafficShapingHandler) { + GlobalTrafficShapingHandler globalTrafficShapingHandler, + Channel channel) { super(AWAITING_INITIAL, proxyServer, false); - initChannelPipeline(pipeline); + this.channel = channel; + + initChannelPipeline(pipeline, channel); if (sslEngineSource != null) { LOG.debug("Enabling encryption of traffic from client to proxy"); @@ -204,14 +210,7 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRequest return DISCONNECT_REQUESTED; } - boolean authenticationRequired = authenticationRequired(httpRequest); - - if (authenticationRequired) { - LOG.debug("Not authenticated!!"); - return AWAITING_PROXY_AUTHENTICATION; - } else { - ctx.fireChannelRead(httpRequest); - } + ctx.fireChannelRead(httpRequest); return getCurrentState(); } @@ -372,48 +371,50 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception if (ProxyUtils.isChunked(httpRequest)) { process(ctx, msg, httpRequest); } else { - executor.execute(() -> { - proxyServer.getGlobalStateHandler().restoreFromChannel(channel); - try { - process(ctx, msg, httpRequest); - } finally { - proxyServer.getGlobalStateHandler().clear(); - } - }); + executor.execute(wrapTask(() -> process(ctx, msg, httpRequest))); } } private void process(ChannelHandlerContext ctx, Object msg, HttpRequest httpRequest) { - // Make a copy of the original request - final HttpRequest currentRequest = copy(httpRequest); - // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response - // should not be filtered), fall back to the default no-op filter source. - HttpFilters filterInstance; - try { + boolean authenticationRequired = authenticationRequired(httpRequest); + + if (authenticationRequired) { + LOG.debug("Not authenticated!!"); + become(AWAITING_PROXY_AUTHENTICATION); + } else { + + // Make a copy of the original request + final HttpRequest currentRequest = copy(httpRequest); + + // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response + // should not be filtered), fall back to the default no-op filter source. + HttpFilters filterInstance; + try { filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); - } finally { + } finally { // releasing a copied http request if (currentRequest instanceof ReferenceCounted) { - ((ReferenceCounted)currentRequest).release(); + ((ReferenceCounted) currentRequest).release(); } - } - if (filterInstance != null) { + } + if (filterInstance != null) { currentFilters = filterInstance; - } else { + } else { currentFilters = HttpFiltersAdapter.NOOP_FILTER; - } + } - // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required - clientToProxyResponse = currentFilters.clientToProxyRequest(httpRequest); + // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required + clientToProxyResponse = currentFilters.clientToProxyRequest(httpRequest); - if (msg instanceof ReferenceCounted) { + if (msg instanceof ReferenceCounted) { LOG.debug("Retaining reference counted message"); ((ReferenceCounted) msg).retain(); - } + } - ctx.fireChannelRead(httpRequest); + ctx.fireChannelRead(httpRequest); + } } @Override @@ -423,6 +424,50 @@ public void write(ChannelHandlerContext ctx, } } + public class GlobalStateWrapperEvenLoop extends DefaultEventLoop { + + private final Channel channel; + + private final EventExecutor eventLoop; + + GlobalStateWrapperEvenLoop(Channel channel) { + this.channel = channel; + this.eventLoop = channel.eventLoop(); + } + + @Override + public void execute(Runnable task) { + if (eventLoop.inEventLoop()) { + wrapTask(task).run(); + } else { + eventLoop.execute(wrapTask(task)); + } + } + + @Override + public boolean inEventLoop() { + return false; + } + } + + private Runnable wrapTask(Runnable task) { + return () -> { + if (proxyServer.getGlobalStateHandler() != null) { + try { + proxyServer.getGlobalStateHandler().restoreFromChannel(channel); + } finally { + try { + task.run(); + } finally { + proxyServer.getGlobalStateHandler().clear(); + } + } + } else { + task.run(); + } + }; + } + /** * Returns true if the specified request is a request to an origin server, rather than to a proxy server. If this * request is being MITM'd, this method always returns false. The format of requests to a proxy server are defined @@ -846,19 +891,17 @@ protected void exceptionCaught(Throwable cause) { * * @param pipeline */ - private void initChannelPipeline(ChannelPipeline pipeline) { + private void initChannelPipeline(ChannelPipeline pipeline, Channel channel) { LOG.debug("Configuring ChannelPipeline"); if (proxyServer.getRequestTracer() != null) { pipeline.addLast("requestTracerHandler", new RequestTracerHandler(this)); } - if (proxyServer.getGlobalStateHandler() != null) { - pipeline.addLast("inboundGlobalStateHandler", new InboundGlobalStateHandler(this)); - } + EventLoopGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(channel); - pipeline.addLast("bytesReadMonitor", bytesReadMonitor); - pipeline.addLast("bytesWrittenMonitor", bytesWrittenMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); pipeline.addLast("encoder", new HttpResponseEncoder()); // We want to allow longer request lines, headers, and chunks @@ -875,23 +918,19 @@ private void initChannelPipeline(ChannelPipeline pipeline) { aggregateContentForFiltering(pipeline, numberOfBytesToBuffer); } - pipeline.addLast("requestReadMonitor", requestReadMonitor); - pipeline.addLast("responseWrittenMonitor", responseWrittenMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "requestReadMonitor", requestReadMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "responseWrittenMonitor", responseWrittenMonitor); pipeline.addLast( "idle", new IdleStateHandler(0, 0, proxyServer .getIdleConnectionTimeout())); - if (proxyServer.getGlobalStateHandler() != null) { - pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(this)); - } - - pipeline.addLast("handlerBegin", this); + pipeline.addLast(globalStateWrapperEvenLoop, "handlerBegin", this); - pipeline.addLast("clientToProxyProcessor", new ClientToProxyProcessor()); + pipeline.addLast(globalStateWrapperEvenLoop, "clientToProxyProcessor", new ClientToProxyProcessor()); - pipeline.addLast("handlerEnd", this); + pipeline.addLast(globalStateWrapperEvenLoop, "handlerEnd", this); } diff --git a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java index aaee33af8..6a55f508a 100644 --- a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java +++ b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java @@ -552,7 +552,8 @@ protected void initChannel(Channel ch) throws Exception { sslEngineSource, authenticateSslClients, ch.pipeline(), - globalTrafficShapingHandler); + globalTrafficShapingHandler, + ch); }; }; switch (transportProtocol) { From 6d58bc3fe9b3a67100726c0c6bb05f4b88b7880e Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 17:23:08 +0200 Subject: [PATCH 12/43] Try to use async executor for response --- .../proxy/impl/ProxyToServerConnection.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 10d8d9fb8..0fc95ae27 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -53,6 +53,8 @@ import java.net.UnknownHostException; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import static org.littleshoot.proxy.impl.ConnectionState.AWAITING_CHUNK; @@ -241,17 +243,23 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRespons currentFilters.serverToProxyResponseReceiving(); rememberCurrentResponse(httpResponse); - respondWith(httpResponse); + + final HttpResponse resp = httpResponse; if (ProxyUtils.isChunked(httpResponse)) { + respondWith(resp); return AWAITING_CHUNK; } else { - currentFilters.serverToProxyResponseReceived(); - + executor.execute(() -> { + currentFilters.serverToProxyResponseReceived(); + respondWith(resp); + }); return AWAITING_INITIAL; } } + Executor executor = Executors.newCachedThreadPool(); + @Override protected void readHTTPChunk(HttpContent chunk) { respondWith(chunk); From c9e9958f23513f946d6e1b06ffe712d2318144fe Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 17:31:44 +0200 Subject: [PATCH 13/43] Retain before async execution --- .../proxy/impl/ProxyToServerConnection.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 0fc95ae27..2f9856439 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -250,10 +250,15 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRespons respondWith(resp); return AWAITING_CHUNK; } else { + if (resp instanceof ReferenceCounted) { + LOG.debug("Retaining reference counted message"); + ((ReferenceCounted) resp).retain(); + } + executor.execute(() -> { - currentFilters.serverToProxyResponseReceived(); - respondWith(resp); - }); + currentFilters.serverToProxyResponseReceived(); + respondWith(resp); + }); return AWAITING_INITIAL; } } From 81efa845c762b61a7c91b692218537bf54ca22c6 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 20:42:53 +0200 Subject: [PATCH 14/43] Global state handler wrapper for proxy to server connection --- .../proxy/impl/ClientToProxyConnection.java | 21 +++++---------- .../proxy/impl/ProxyToServerConnection.java | 26 +++++++------------ 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index df786cf0a..595cd8548 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -162,7 +162,7 @@ public class ClientToProxyConnection extends ProxyConnection { this.channel = channel; - initChannelPipeline(pipeline, channel); + initChannelPipeline(pipeline); if (sslEngineSource != null) { LOG.debug("Enabling encryption of traffic from client to proxy"); @@ -426,21 +426,12 @@ public void write(ChannelHandlerContext ctx, public class GlobalStateWrapperEvenLoop extends DefaultEventLoop { - private final Channel channel; - - private final EventExecutor eventLoop; - - GlobalStateWrapperEvenLoop(Channel channel) { - this.channel = channel; - this.eventLoop = channel.eventLoop(); - } - @Override public void execute(Runnable task) { - if (eventLoop.inEventLoop()) { + if (channel.eventLoop().inEventLoop()) { wrapTask(task).run(); } else { - eventLoop.execute(wrapTask(task)); + channel.eventLoop().execute(wrapTask(task)); } } @@ -450,7 +441,7 @@ public boolean inEventLoop() { } } - private Runnable wrapTask(Runnable task) { + Runnable wrapTask(Runnable task) { return () -> { if (proxyServer.getGlobalStateHandler() != null) { try { @@ -891,14 +882,14 @@ protected void exceptionCaught(Throwable cause) { * * @param pipeline */ - private void initChannelPipeline(ChannelPipeline pipeline, Channel channel) { + private void initChannelPipeline(ChannelPipeline pipeline) { LOG.debug("Configuring ChannelPipeline"); if (proxyServer.getRequestTracer() != null) { pipeline.addLast("requestTracerHandler", new RequestTracerHandler(this)); } - EventLoopGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(channel); + EventLoopGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(); pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 2f9856439..7f4fc6936 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -12,6 +12,7 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.udt.nio.NioUdtProvider; import io.netty.handler.codec.http.FullHttpResponse; @@ -255,10 +256,10 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRespons ((ReferenceCounted) resp).retain(); } - executor.execute(() -> { + executor.execute(clientConnection.wrapTask(() -> { currentFilters.serverToProxyResponseReceived(); respondWith(resp); - }); + })); return AWAITING_INITIAL; } } @@ -911,17 +912,14 @@ private void setupConnectionParameters() throws UnknownHostException { */ private void initChannelPipeline(ChannelPipeline pipeline, HttpRequest httpRequest) { - - if (proxyServer.getGlobalStateHandler() != null) { - pipeline.addLast("inboundGlobalStateHandler", new InboundGlobalStateHandler(clientConnection)); - } - if (trafficHandler != null) { pipeline.addLast("global-traffic-shaping", trafficHandler); } - pipeline.addLast("bytesReadMonitor", bytesReadMonitor); - pipeline.addLast("bytesWrittenMonitor", bytesWrittenMonitor); + EventLoopGroup globalStateWrapperEvenLoop = clientConnection.new GlobalStateWrapperEvenLoop(); + + pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); pipeline.addLast("encoder", new HttpRequestEncoder()); pipeline.addLast("decoder", new HeadAwareHttpResponseDecoder( @@ -936,8 +934,8 @@ private void initChannelPipeline(ChannelPipeline pipeline, aggregateContentForFiltering(pipeline, numberOfBytesToBuffer); } - pipeline.addLast("responseReadMonitor", responseReadMonitor); - pipeline.addLast("requestWrittenMonitor", requestWrittenMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "responseReadMonitor", responseReadMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "requestWrittenMonitor", requestWrittenMonitor); // Set idle timeout pipeline.addLast( @@ -945,11 +943,7 @@ private void initChannelPipeline(ChannelPipeline pipeline, new IdleStateHandler(0, 0, proxyServer .getIdleConnectionTimeout())); - if (proxyServer.getGlobalStateHandler() != null) { - pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(clientConnection)); - } - - pipeline.addLast("handler", this); + pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); } /** From bb1e3e9a8309c609befe67736c78fd237176ec0c Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 21:12:20 +0200 Subject: [PATCH 15/43] Fix build --- .../proxy/impl/ClientToProxyConnection.java | 13 ++++++++++--- .../proxy/impl/ProxyToServerConnection.java | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 595cd8548..13372cf80 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -9,6 +9,7 @@ import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.DefaultEventLoop; +import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; @@ -426,12 +427,18 @@ public void write(ChannelHandlerContext ctx, public class GlobalStateWrapperEvenLoop extends DefaultEventLoop { + private final EventLoop eventLoop; + + GlobalStateWrapperEvenLoop(EventLoop eventLoop) { + this.eventLoop = eventLoop; + } + @Override public void execute(Runnable task) { - if (channel.eventLoop().inEventLoop()) { + if (eventLoop.inEventLoop()) { wrapTask(task).run(); } else { - channel.eventLoop().execute(wrapTask(task)); + eventLoop.execute(wrapTask(task)); } } @@ -889,7 +896,7 @@ private void initChannelPipeline(ChannelPipeline pipeline) { pipeline.addLast("requestTracerHandler", new RequestTracerHandler(this)); } - EventLoopGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(); + EventLoopGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(channel.eventLoop()); pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 7f4fc6936..88899b316 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -916,7 +916,7 @@ private void initChannelPipeline(ChannelPipeline pipeline, pipeline.addLast("global-traffic-shaping", trafficHandler); } - EventLoopGroup globalStateWrapperEvenLoop = clientConnection.new GlobalStateWrapperEvenLoop(); + EventLoopGroup globalStateWrapperEvenLoop = clientConnection.new GlobalStateWrapperEvenLoop(channel.eventLoop()); pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); From e9cadd79dc34d80d9125266adc59890943dc86f7 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 4 Jan 2019 22:52:02 +0200 Subject: [PATCH 16/43] build fix --- .../org/littleshoot/proxy/impl/ProxyToServerConnection.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 88899b316..c84395011 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -658,8 +658,8 @@ public Channel newChannel() { cb.handler(new ChannelInitializer() { protected void initChannel(Channel ch) throws Exception { - initChannelPipeline(ch.pipeline(), initialRequest); - }; + initChannelPipeline(ch.pipeline(), ch); + } }); cb.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, proxyServer.getConnectTimeout()); @@ -911,7 +911,7 @@ private void setupConnectionParameters() throws UnknownHostException { * @param httpRequest */ private void initChannelPipeline(ChannelPipeline pipeline, - HttpRequest httpRequest) { + Channel channel) { if (trafficHandler != null) { pipeline.addLast("global-traffic-shaping", trafficHandler); } From 083a300b43f380dde21a9f43f0a1b259f73e5398 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Sat, 5 Jan 2019 00:02:54 +0200 Subject: [PATCH 17/43] remove hardcoded processing executor --- .../proxy/HttpProxyServerBootstrap.java | 9 ++++++++ .../proxy/impl/ClientToProxyConnection.java | 8 ++----- .../proxy/impl/DefaultHttpProxyServer.java | 21 +++++++++++++++++-- .../proxy/impl/ProxyToServerConnection.java | 7 ++----- .../littleshoot/proxy/impl/ServerGroup.java | 21 ++++++++++++++++++- 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/HttpProxyServerBootstrap.java b/src/main/java/org/littleshoot/proxy/HttpProxyServerBootstrap.java index cab76a06a..3bbc6f910 100644 --- a/src/main/java/org/littleshoot/proxy/HttpProxyServerBootstrap.java +++ b/src/main/java/org/littleshoot/proxy/HttpProxyServerBootstrap.java @@ -4,6 +4,7 @@ import org.littleshoot.proxy.ratelimit.RateLimiter; import java.net.InetSocketAddress; +import java.util.concurrent.ExecutorService; /** * Configures and starts an {@link HttpProxyServer}. The HttpProxyServer is @@ -242,6 +243,14 @@ HttpProxyServerBootstrap withProxyToServerExHandler( HttpProxyServerBootstrap withCustomGlobalState( GlobalStateHandler globalStateHandler); + /** + * + * @param payloadProcessorExecutor + * @return + */ + HttpProxyServerBootstrap withPyaloadProcessorExecutor( + ExecutorService payloadProcessorExecutor); + /** *

* Specify a {@link HttpFiltersSource} to use for filtering requests and/or diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 13372cf80..f592a754a 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -28,7 +28,6 @@ import io.netty.handler.timeout.IdleStateHandler; import io.netty.handler.traffic.GlobalTrafficShapingHandler; import io.netty.util.ReferenceCounted; -import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import org.apache.commons.lang3.StringUtils; @@ -54,8 +53,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -354,8 +351,6 @@ public ConnectionState doReadHTTPInitial(HttpRequest httpRequest) { } } - private static final Executor executor = Executors.newCachedThreadPool(); - @Sharable protected class ClientToProxyProcessor extends ChannelDuplexHandler { @@ -372,7 +367,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception if (ProxyUtils.isChunked(httpRequest)) { process(ctx, msg, httpRequest); } else { - executor.execute(wrapTask(() -> process(ctx, msg, httpRequest))); + proxyServer.getPayloadProcessorExecutor() + .execute(wrapTask(() -> process(ctx, msg, httpRequest))); } } diff --git a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java index 6a55f508a..753e4d99e 100644 --- a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java +++ b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java @@ -51,6 +51,7 @@ import java.util.Collection; import java.util.Properties; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -119,6 +120,7 @@ public class DefaultHttpProxyServer implements HttpProxyServer { private final ExceptionHandler proxyToServerExHandler; private final RequestTracer requestTracer; private final GlobalStateHandler globalStateHandler; + private final ExecutorService payloadProcessorExecutor; private final HttpFiltersSource filtersSource; private final FailureHttpResponseComposer unrecoverableFailureHttpResponseComposer; private final boolean transparent; @@ -257,6 +259,7 @@ private DefaultHttpProxyServer(ServerGroup serverGroup, ExceptionHandler proxyToServerExHandler, RequestTracer requestTracer, GlobalStateHandler globalStateHandler, + ExecutorService payloadProcessorExecutor, HttpFiltersSource filtersSource, FailureHttpResponseComposer unrecoverableFailureHttpResponseComposer, boolean transparent, @@ -285,6 +288,7 @@ private DefaultHttpProxyServer(ServerGroup serverGroup, this.proxyToServerExHandler = proxyToServerExHandler; this.requestTracer = requestTracer; this.globalStateHandler = globalStateHandler; + this.payloadProcessorExecutor = payloadProcessorExecutor; this.filtersSource = filtersSource; this.unrecoverableFailureHttpResponseComposer = unrecoverableFailureHttpResponseComposer; this.transparent = transparent; @@ -621,6 +625,10 @@ protected GlobalStateHandler getGlobalStateHandler() { return globalStateHandler; } + protected ExecutorService getPayloadProcessorExecutor() { + return payloadProcessorExecutor; + } + protected RequestTracer getRequestTracer() { return requestTracer; } @@ -671,6 +679,7 @@ private static class DefaultHttpProxyServerBootstrap implements HttpProxyServerB private ExceptionHandler proxyToServerExHandler = null; private RequestTracer requestTracer = null; private GlobalStateHandler globalStateHandler = null; + private ExecutorService payloadProcessorExecutor = null; private HttpFiltersSource filtersSource = new HttpFiltersSourceAdapter(); private FailureHttpResponseComposer unrecoverableFailureHttpResponseComposer = new DefaultFailureHttpResponseComposer(); private boolean transparent = false; @@ -896,6 +905,13 @@ public HttpProxyServerBootstrap withCustomGlobalState( return this; } + @Override + public HttpProxyServerBootstrap withPyaloadProcessorExecutor( + ExecutorService payloadProcessorExecutor) { + this.payloadProcessorExecutor = payloadProcessorExecutor; + return this; + } + @Override public HttpProxyServerBootstrap withFiltersSource( HttpFiltersSource filtersSource) { @@ -1011,14 +1027,15 @@ private DefaultHttpProxyServer build() { serverGroup = this.serverGroup; } else { - serverGroup = new ServerGroup(name, clientToProxyAcceptorThreads, clientToProxyWorkerThreads, proxyToServerWorkerThreads); + serverGroup = new ServerGroup(name, clientToProxyAcceptorThreads, + clientToProxyWorkerThreads, proxyToServerWorkerThreads, payloadProcessorExecutor); } return new DefaultHttpProxyServer(serverGroup, transportProtocol, determineListenAddress(), sslEngineSource, authenticateSslClients, proxyAuthenticator, chainProxyManager, mitmManagerFactory, - clientToProxyExHandler, proxyToServerExHandler, requestTracer, globalStateHandler, + clientToProxyExHandler, proxyToServerExHandler, requestTracer, globalStateHandler, payloadProcessorExecutor, filtersSource, unrecoverableFailureHttpResponseComposer, transparent, idleConnectionTimeout, activityTrackers, connectTimeout, serverResolver, readThrottleBytesPerSecond, writeThrottleBytesPerSecond, diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index c84395011..63b5677bd 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -54,8 +54,6 @@ import java.net.UnknownHostException; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import static org.littleshoot.proxy.impl.ConnectionState.AWAITING_CHUNK; @@ -256,7 +254,8 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRespons ((ReferenceCounted) resp).retain(); } - executor.execute(clientConnection.wrapTask(() -> { + proxyServer.getPayloadProcessorExecutor() + .execute(clientConnection.wrapTask(() -> { currentFilters.serverToProxyResponseReceived(); respondWith(resp); })); @@ -264,8 +263,6 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRespons } } - Executor executor = Executors.newCachedThreadPool(); - @Override protected void readHTTPChunk(HttpContent chunk) { respondWith(chunk); diff --git a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java index d359c7be1..90ad3a3e9 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java +++ b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java @@ -12,6 +12,8 @@ import java.util.ArrayList; import java.util.EnumMap; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -72,6 +74,8 @@ public class ServerGroup { */ private final EnumMap protocolThreadPools = new EnumMap(TransportProtocol.class); + private final ExecutorService payloadProcessingExecutor; + /** * A mapping of selector providers to transport protocols. Avoids special-casing each transport protocol during * transport protocol initialization. @@ -104,12 +108,19 @@ public class ServerGroup { * @param incomingWorkerThreads number of client-to-proxy worker threads per protocol * @param outgoingWorkerThreads number of proxy-to-server worker threads per protocol */ - public ServerGroup(String name, int incomingAcceptorThreads, int incomingWorkerThreads, int outgoingWorkerThreads) { + public ServerGroup(String name, int incomingAcceptorThreads, + int incomingWorkerThreads, int outgoingWorkerThreads, + ExecutorService payloadProcessingExecutor) { this.name = name; this.serverGroupId = serverGroupCount.getAndIncrement(); this.incomingAcceptorThreads = incomingAcceptorThreads; this.incomingWorkerThreads = incomingWorkerThreads; this.outgoingWorkerThreads = outgoingWorkerThreads; + if (payloadProcessingExecutor == null) { + this.payloadProcessingExecutor = Executors.newFixedThreadPool(incomingWorkerThreads); + } else { + this.payloadProcessingExecutor = payloadProcessingExecutor; + } } /** @@ -219,6 +230,14 @@ private void shutdown(boolean graceful) { allEventLoopGroups.addAll(threadPools.getAllEventLoops()); } + payloadProcessingExecutor.shutdown(); + + try { + payloadProcessingExecutor.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.warn("Failed to shutdown payload processing executor properly", e); + } + for (EventLoopGroup group : allEventLoopGroups) { if (graceful) { group.shutdownGracefully(); From 087d4ae74af6c9c4bf248f10f235754e7a725048 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Sat, 5 Jan 2019 00:20:57 +0200 Subject: [PATCH 18/43] build fix --- .../proxy/impl/ClientToProxyConnection.java | 4 +-- .../proxy/impl/DefaultHttpProxyServer.java | 2 +- .../proxy/impl/ProxyConnection.java | 30 ++++++++++--------- .../littleshoot/proxy/impl/ServerGroup.java | 4 +++ 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index f592a754a..19bc26337 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -352,7 +352,7 @@ public ConnectionState doReadHTTPInitial(HttpRequest httpRequest) { } @Sharable - protected class ClientToProxyProcessor extends ChannelDuplexHandler { + protected class ClientPayloadProcessor extends ChannelDuplexHandler { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { @@ -922,7 +922,7 @@ private void initChannelPipeline(ChannelPipeline pipeline) { pipeline.addLast(globalStateWrapperEvenLoop, "handlerBegin", this); - pipeline.addLast(globalStateWrapperEvenLoop, "clientToProxyProcessor", new ClientToProxyProcessor()); + pipeline.addLast(globalStateWrapperEvenLoop, "clientToProxyProcessor", new ClientPayloadProcessor()); pipeline.addLast(globalStateWrapperEvenLoop, "handlerEnd", this); diff --git a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java index 753e4d99e..9f212c9fa 100644 --- a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java +++ b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java @@ -626,7 +626,7 @@ protected GlobalStateHandler getGlobalStateHandler() { } protected ExecutorService getPayloadProcessorExecutor() { - return payloadProcessorExecutor; + return serverGroup.getPayloadProcessingExecutor(); } protected RequestTracer getRequestTracer() { diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java index d31fb48a0..d92f1a473 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java @@ -133,11 +133,7 @@ private void readHTTP(ChannelHandlerContext ctx, HttpObject httpObject) { switch (getCurrentState()) { case AWAITING_INITIAL: if (httpObject instanceof HttpMessage) { - if (ctx.name().equals("handlerEnd")) { - nextState = ((ClientToProxyConnection)this).doReadHTTPInitial((HttpRequest) httpObject); - } else { - nextState = readHTTPInitial(ctx, (I) httpObject); - } + nextState = processPayload(ctx, (I) httpObject); } else { // Similar to the AWAITING_PROXY_AUTHENTICATION case below, we may enter an AWAITING_INITIAL // state if the proxy responded to an earlier request with a 502 or 504 response, or a short-circuit @@ -155,11 +151,7 @@ private void readHTTP(ChannelHandlerContext ctx, HttpObject httpObject) { case AWAITING_PROXY_AUTHENTICATION: if (httpObject instanceof HttpRequest) { // Once we get an HttpRequest, try to process it as usual - if (ctx.name().equals("handlerEnd")) { - nextState = ((ClientToProxyConnection)this).doReadHTTPInitial((HttpRequest) httpObject); - } else { - nextState = readHTTPInitial(ctx, (I) httpObject); - } + nextState = processPayload(ctx, (I) httpObject); } else { // Anything that's not an HttpRequest that came in while // we're pending authentication gets dropped on the floor. This @@ -190,6 +182,20 @@ private void readHTTP(ChannelHandlerContext ctx, HttpObject httpObject) { become(nextState); } + private ConnectionState processPayload(ChannelHandlerContext ctx, I httpObject) { + ConnectionState nextState; + if (afterPayloadProcessor(ctx)) { + nextState = ((ClientToProxyConnection) this).doReadHTTPInitial((HttpRequest) httpObject); + } else { + nextState = readHTTPInitial(ctx, httpObject); + } + return nextState; + } + + private boolean afterPayloadProcessor(ChannelHandlerContext ctx) { + return ctx.name().equals("handlerEnd"); + } + /** * Implement this to handle reading the initial object (e.g. * {@link HttpRequest} or {@link HttpResponse}). @@ -541,10 +547,6 @@ protected void become(ConnectionState state) { this.currentState = state; } - protected ConnectionState state() { - return this.currentState; - } - protected ConnectionState getCurrentState() { return currentState; } diff --git a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java index 90ad3a3e9..929c199f1 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java +++ b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java @@ -300,6 +300,10 @@ public EventLoopGroup getProxyToServerWorkerPoolForTransport(TransportProtocol p return getThreadPoolsForProtocol(protocol).getProxyToServerWorkerPool(); } + public ExecutorService getPayloadProcessingExecutor() { + return payloadProcessingExecutor; + } + /** * @return true if this ServerGroup has already been stopped */ From cddbc1e5feea0ae34c15beb7c479a752c4068727 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Mon, 7 Jan 2019 00:29:10 +0200 Subject: [PATCH 19/43] Cached default executor, correct statee --- .../proxy/impl/ClientToProxyConnection.java | 15 ++++----------- .../proxy/impl/ProxyToServerConnection.java | 7 ++++--- .../org/littleshoot/proxy/impl/ServerGroup.java | 2 +- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 19bc26337..17104b126 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -3,11 +3,10 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; -import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelPipeline; -import io.netty.channel.ChannelPromise; import io.netty.channel.DefaultEventLoop; import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; @@ -145,7 +144,7 @@ public class ClientToProxyConnection extends ProxyConnection { private AtomicBoolean authenticated = new AtomicBoolean(); - public HttpResponse clientToProxyResponse; + private HttpResponse clientToProxyResponse; private final GlobalTrafficShapingHandler globalTrafficShapingHandler; @@ -352,10 +351,10 @@ public ConnectionState doReadHTTPInitial(HttpRequest httpRequest) { } @Sharable - protected class ClientPayloadProcessor extends ChannelDuplexHandler { + protected class ClientPayloadProcessor extends ChannelInboundHandlerAdapter { @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + public void channelRead(ChannelHandlerContext ctx, Object msg) { if (msg instanceof ReferenceCounted) { LOG.debug("Retaining reference counted message"); @@ -413,12 +412,6 @@ private void process(ChannelHandlerContext ctx, Object msg, HttpRequest httpRequ ctx.fireChannelRead(httpRequest); } } - - @Override - public void write(ChannelHandlerContext ctx, - Object msg, ChannelPromise promise) throws Exception { - super.write(ctx, msg, promise); - } } public class GlobalStateWrapperEvenLoop extends DefaultEventLoop { diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 63b5677bd..e41907f7b 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -256,10 +256,11 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRespons proxyServer.getPayloadProcessorExecutor() .execute(clientConnection.wrapTask(() -> { - currentFilters.serverToProxyResponseReceived(); respondWith(resp); - })); - return AWAITING_INITIAL; + currentFilters.serverToProxyResponseReceived(); + super.become(AWAITING_INITIAL); + })); + return getCurrentState(); } } diff --git a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java index 929c199f1..144b0f75e 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java +++ b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java @@ -117,7 +117,7 @@ public ServerGroup(String name, int incomingAcceptorThreads, this.incomingWorkerThreads = incomingWorkerThreads; this.outgoingWorkerThreads = outgoingWorkerThreads; if (payloadProcessingExecutor == null) { - this.payloadProcessingExecutor = Executors.newFixedThreadPool(incomingWorkerThreads); + this.payloadProcessingExecutor = Executors.newCachedThreadPool(); } else { this.payloadProcessingExecutor = payloadProcessingExecutor; } From 35346f92ea6ae3192a84ebbb17e362d045dc80b8 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Mon, 7 Jan 2019 00:49:20 +0200 Subject: [PATCH 20/43] Test build 1 --- .../org/littleshoot/proxy/impl/ServerGroup.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java index 144b0f75e..8dba1af42 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java +++ b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java @@ -230,13 +230,13 @@ private void shutdown(boolean graceful) { allEventLoopGroups.addAll(threadPools.getAllEventLoops()); } - payloadProcessingExecutor.shutdown(); - - try { - payloadProcessingExecutor.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - log.warn("Failed to shutdown payload processing executor properly", e); - } +// payloadProcessingExecutor.shutdown(); +// +// try { +// payloadProcessingExecutor.awaitTermination(10, TimeUnit.SECONDS); +// } catch (InterruptedException e) { +// log.warn("Failed to shutdown payload processing executor properly", e); +// } for (EventLoopGroup group : allEventLoopGroups) { if (graceful) { From beee58a5b50b6518b99abfbbe637dd1e627e52ab Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Mon, 7 Jan 2019 09:49:40 +0200 Subject: [PATCH 21/43] Try fixing build --- .../proxy/impl/ProxyToServerConnection.java | 11 ++++++----- .../org/littleshoot/proxy/impl/ServerGroup.java | 14 +++++++------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index e41907f7b..c3d94722b 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -254,13 +254,14 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRespons ((ReferenceCounted) resp).retain(); } - proxyServer.getPayloadProcessorExecutor() - .execute(clientConnection.wrapTask(() -> { +// proxyServer.getPayloadProcessorExecutor() +// .execute(clientConnection.wrapTask(() -> { respondWith(resp); currentFilters.serverToProxyResponseReceived(); - super.become(AWAITING_INITIAL); - })); - return getCurrentState(); +// super.become(AWAITING_INITIAL); +// })); +// return getCurrentState(); + return AWAITING_INITIAL; } } diff --git a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java index 8dba1af42..144b0f75e 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java +++ b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java @@ -230,13 +230,13 @@ private void shutdown(boolean graceful) { allEventLoopGroups.addAll(threadPools.getAllEventLoops()); } -// payloadProcessingExecutor.shutdown(); -// -// try { -// payloadProcessingExecutor.awaitTermination(10, TimeUnit.SECONDS); -// } catch (InterruptedException e) { -// log.warn("Failed to shutdown payload processing executor properly", e); -// } + payloadProcessingExecutor.shutdown(); + + try { + payloadProcessingExecutor.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.warn("Failed to shutdown payload processing executor properly", e); + } for (EventLoopGroup group : allEventLoopGroups) { if (graceful) { From 44a32786419fbfe0fa960890b50902c2d3563a78 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Mon, 7 Jan 2019 10:26:12 +0200 Subject: [PATCH 22/43] Try fixing build 3 --- .../proxy/impl/ClientToProxyConnection.java | 14 +++++++------- .../proxy/impl/ProxyToServerConnection.java | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 17104b126..88dcdfdbe 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -887,8 +887,8 @@ private void initChannelPipeline(ChannelPipeline pipeline) { EventLoopGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(channel.eventLoop()); - pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); - pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); + pipeline.addLast( "bytesReadMonitor", bytesReadMonitor); + pipeline.addLast( "bytesWrittenMonitor", bytesWrittenMonitor); pipeline.addLast("encoder", new HttpResponseEncoder()); // We want to allow longer request lines, headers, and chunks @@ -905,19 +905,19 @@ private void initChannelPipeline(ChannelPipeline pipeline) { aggregateContentForFiltering(pipeline, numberOfBytesToBuffer); } - pipeline.addLast(globalStateWrapperEvenLoop, "requestReadMonitor", requestReadMonitor); - pipeline.addLast(globalStateWrapperEvenLoop, "responseWrittenMonitor", responseWrittenMonitor); + pipeline.addLast( "requestReadMonitor", requestReadMonitor); + pipeline.addLast( "responseWrittenMonitor", responseWrittenMonitor); pipeline.addLast( "idle", new IdleStateHandler(0, 0, proxyServer .getIdleConnectionTimeout())); - pipeline.addLast(globalStateWrapperEvenLoop, "handlerBegin", this); + pipeline.addLast( "handlerBegin", this); - pipeline.addLast(globalStateWrapperEvenLoop, "clientToProxyProcessor", new ClientPayloadProcessor()); + pipeline.addLast( "clientToProxyProcessor", new ClientPayloadProcessor()); - pipeline.addLast(globalStateWrapperEvenLoop, "handlerEnd", this); + pipeline.addLast( "handlerEnd", this); } diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index c3d94722b..31e794c78 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -917,8 +917,8 @@ private void initChannelPipeline(ChannelPipeline pipeline, EventLoopGroup globalStateWrapperEvenLoop = clientConnection.new GlobalStateWrapperEvenLoop(channel.eventLoop()); - pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); - pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); + pipeline.addLast( "bytesReadMonitor", bytesReadMonitor); + pipeline.addLast( "bytesWrittenMonitor", bytesWrittenMonitor); pipeline.addLast("encoder", new HttpRequestEncoder()); pipeline.addLast("decoder", new HeadAwareHttpResponseDecoder( @@ -933,8 +933,8 @@ private void initChannelPipeline(ChannelPipeline pipeline, aggregateContentForFiltering(pipeline, numberOfBytesToBuffer); } - pipeline.addLast(globalStateWrapperEvenLoop, "responseReadMonitor", responseReadMonitor); - pipeline.addLast(globalStateWrapperEvenLoop, "requestWrittenMonitor", requestWrittenMonitor); + pipeline.addLast( "responseReadMonitor", responseReadMonitor); + pipeline.addLast( "requestWrittenMonitor", requestWrittenMonitor); // Set idle timeout pipeline.addLast( @@ -942,7 +942,7 @@ private void initChannelPipeline(ChannelPipeline pipeline, new IdleStateHandler(0, 0, proxyServer .getIdleConnectionTimeout())); - pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); + pipeline.addLast( "handler", this); } /** From 006935eecec63371ed693f13c8db89bf04f6bd71 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Mon, 7 Jan 2019 10:31:45 +0200 Subject: [PATCH 23/43] Uncomment async response --- .../proxy/impl/ProxyToServerConnection.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 31e794c78..97622a3d5 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -254,14 +254,13 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRespons ((ReferenceCounted) resp).retain(); } -// proxyServer.getPayloadProcessorExecutor() -// .execute(clientConnection.wrapTask(() -> { + proxyServer.getPayloadProcessorExecutor() + .execute(clientConnection.wrapTask(() -> { respondWith(resp); currentFilters.serverToProxyResponseReceived(); -// super.become(AWAITING_INITIAL); -// })); -// return getCurrentState(); - return AWAITING_INITIAL; + super.become(AWAITING_INITIAL); + })); + return getCurrentState(); } } From bfe4ee1be81fd781c49a0706f81a48d53306ca0d Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Mon, 7 Jan 2019 10:46:01 +0200 Subject: [PATCH 24/43] Try adding global state wrapper --- .../littleshoot/proxy/impl/ClientToProxyConnection.java | 8 ++++---- .../littleshoot/proxy/impl/ProxyToServerConnection.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 88dcdfdbe..75868aaae 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -887,8 +887,8 @@ private void initChannelPipeline(ChannelPipeline pipeline) { EventLoopGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(channel.eventLoop()); - pipeline.addLast( "bytesReadMonitor", bytesReadMonitor); - pipeline.addLast( "bytesWrittenMonitor", bytesWrittenMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); pipeline.addLast("encoder", new HttpResponseEncoder()); // We want to allow longer request lines, headers, and chunks @@ -905,8 +905,8 @@ private void initChannelPipeline(ChannelPipeline pipeline) { aggregateContentForFiltering(pipeline, numberOfBytesToBuffer); } - pipeline.addLast( "requestReadMonitor", requestReadMonitor); - pipeline.addLast( "responseWrittenMonitor", responseWrittenMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "requestReadMonitor", requestReadMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "responseWrittenMonitor", responseWrittenMonitor); pipeline.addLast( "idle", diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 97622a3d5..959ad2694 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -916,8 +916,8 @@ private void initChannelPipeline(ChannelPipeline pipeline, EventLoopGroup globalStateWrapperEvenLoop = clientConnection.new GlobalStateWrapperEvenLoop(channel.eventLoop()); - pipeline.addLast( "bytesReadMonitor", bytesReadMonitor); - pipeline.addLast( "bytesWrittenMonitor", bytesWrittenMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); pipeline.addLast("encoder", new HttpRequestEncoder()); pipeline.addLast("decoder", new HeadAwareHttpResponseDecoder( @@ -932,8 +932,8 @@ private void initChannelPipeline(ChannelPipeline pipeline, aggregateContentForFiltering(pipeline, numberOfBytesToBuffer); } - pipeline.addLast( "responseReadMonitor", responseReadMonitor); - pipeline.addLast( "requestWrittenMonitor", requestWrittenMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "responseReadMonitor", responseReadMonitor); + pipeline.addLast(globalStateWrapperEvenLoop, "requestWrittenMonitor", requestWrittenMonitor); // Set idle timeout pipeline.addLast( From 7cf431318dd08331b61f7e062fef1e1734c23d0b Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Mon, 7 Jan 2019 14:34:33 +0200 Subject: [PATCH 25/43] Improve global state wrapper --- .../proxy/impl/ClientToProxyConnection.java | 46 ++--- .../impl/GlobalStateWrapperEvenLoop.java | 187 ++++++++++++++++++ .../proxy/impl/ProxyToServerConnection.java | 15 +- 3 files changed, 214 insertions(+), 34 deletions(-) create mode 100644 src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 75868aaae..603b15293 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -7,9 +7,6 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelPipeline; -import io.netty.channel.DefaultEventLoop; -import io.netty.channel.EventLoop; -import io.netty.channel.EventLoopGroup; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; @@ -27,8 +24,10 @@ import io.netty.handler.timeout.IdleStateHandler; import io.netty.handler.traffic.GlobalTrafficShapingHandler; import io.netty.util.ReferenceCounted; +import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; + import org.apache.commons.lang3.StringUtils; import org.littleshoot.proxy.ActivityTracker; import org.littleshoot.proxy.DefaultFailureHttpResponseComposer; @@ -414,30 +413,7 @@ private void process(ChannelHandlerContext ctx, Object msg, HttpRequest httpRequ } } - public class GlobalStateWrapperEvenLoop extends DefaultEventLoop { - - private final EventLoop eventLoop; - - GlobalStateWrapperEvenLoop(EventLoop eventLoop) { - this.eventLoop = eventLoop; - } - - @Override - public void execute(Runnable task) { - if (eventLoop.inEventLoop()) { - wrapTask(task).run(); - } else { - eventLoop.execute(wrapTask(task)); - } - } - - @Override - public boolean inEventLoop() { - return false; - } - } - - Runnable wrapTask(Runnable task) { + public Runnable wrapTask(Runnable task) { return () -> { if (proxyServer.getGlobalStateHandler() != null) { try { @@ -885,7 +861,11 @@ private void initChannelPipeline(ChannelPipeline pipeline) { pipeline.addLast("requestTracerHandler", new RequestTracerHandler(this)); } - EventLoopGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(channel.eventLoop()); +// if (proxyServer.getGlobalStateHandler() != null) { +// pipeline.addLast("inboundGlobalStateHandler", new InboundGlobalStateHandler(this)); +// } + + EventExecutorGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(this); pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); @@ -913,11 +893,15 @@ private void initChannelPipeline(ChannelPipeline pipeline) { new IdleStateHandler(0, 0, proxyServer .getIdleConnectionTimeout())); - pipeline.addLast( "handlerBegin", this); +// if (proxyServer.getGlobalStateHandler() != null) { +// pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(this)); +// } + + pipeline.addLast(globalStateWrapperEvenLoop, "handlerBegin", this); - pipeline.addLast( "clientToProxyProcessor", new ClientPayloadProcessor()); + pipeline.addLast(globalStateWrapperEvenLoop, "clientToProxyProcessor", new ClientPayloadProcessor()); - pipeline.addLast( "handlerEnd", this); + pipeline.addLast(globalStateWrapperEvenLoop, "handlerEnd", this); } diff --git a/src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java b/src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java new file mode 100644 index 000000000..211f00d4e --- /dev/null +++ b/src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java @@ -0,0 +1,187 @@ +package org.littleshoot.proxy.impl; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.EventExecutorGroup; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.ProgressivePromise; +import io.netty.util.concurrent.Promise; +import io.netty.util.concurrent.ScheduledFuture; + +public class GlobalStateWrapperEvenLoop implements EventExecutor { + + private final ClientToProxyConnection connection; + + private final EventExecutor eventLoop; + + GlobalStateWrapperEvenLoop(ClientToProxyConnection connection) { + this.connection = connection; + this.eventLoop = connection.channel.eventLoop(); + } + + GlobalStateWrapperEvenLoop(ClientToProxyConnection connection, EventExecutor eventLoop) { + this.connection = connection; + this.eventLoop = eventLoop; + } + + @Override + public void execute(Runnable command) { + if (eventLoop.inEventLoop()) { + connection.wrapTask(command).run(); + } else { + eventLoop.execute(connection.wrapTask(command)); + } + } + + @Override + public boolean isShuttingDown() { + return eventLoop.isShuttingDown(); + } + + @Override + public Future shutdownGracefully() { + return eventLoop.shutdownGracefully(); + } + + @Override + public Future shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) { + return eventLoop.shutdownGracefully(quietPeriod, timeout, unit); + } + + @Override + public Future terminationFuture() { + return eventLoop.terminationFuture(); + } + + @Override + public void shutdown() { + eventLoop.terminationFuture(); + } + + @Override + public List shutdownNow() { + return eventLoop.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return eventLoop.isShutdown(); + } + + @Override + public boolean isTerminated() { + return eventLoop.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return eventLoop.awaitTermination(timeout, unit); + } + + @Override + public EventExecutor next() { + return this; + } + + @Override + public Iterator iterator() { + return eventLoop.iterator(); + } + + @Override + public Future submit(Runnable task) { + return eventLoop.submit(connection.wrapTask(task)); + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + return eventLoop.invokeAll(tasks); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { + return eventLoop.invokeAll(tasks, timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return eventLoop.invokeAny(tasks); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return eventLoop.invokeAny(tasks, timeout, unit); + } + + @Override + public Future submit(Runnable task, T result) { + return eventLoop.submit(connection.wrapTask(task), result); + } + + @Override + public Future submit(Callable task) { + return eventLoop.submit(task); + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return eventLoop.schedule(command, delay, unit); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + return eventLoop.schedule(callable, delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + return eventLoop.scheduleAtFixedRate(command, initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + return eventLoop.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + + @Override + public EventExecutorGroup parent() { + return eventLoop.parent(); + } + + @Override + public boolean inEventLoop() { + return false; + } + + @Override + public boolean inEventLoop(Thread thread) { + return eventLoop.inEventLoop(thread); + } + + @Override + public Promise newPromise() { + return eventLoop.newPromise(); + } + + @Override + public ProgressivePromise newProgressivePromise() { + return eventLoop.newProgressivePromise(); + } + + @Override + public Future newSucceededFuture(V result) { + return eventLoop.newSucceededFuture(result); + } + + @Override + public Future newFailedFuture(Throwable cause) { + return eventLoop.newFailedFuture(cause); + } +} \ No newline at end of file diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 959ad2694..36d810856 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -12,7 +12,6 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; -import io.netty.channel.EventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.udt.nio.NioUdtProvider; import io.netty.handler.codec.http.FullHttpResponse; @@ -33,6 +32,7 @@ import io.netty.handler.traffic.GlobalTrafficShapingHandler; import io.netty.util.AttributeKey; import io.netty.util.ReferenceCounted; +import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import org.littleshoot.proxy.ActivityTracker; @@ -910,11 +910,16 @@ private void setupConnectionParameters() throws UnknownHostException { */ private void initChannelPipeline(ChannelPipeline pipeline, Channel channel) { + +// if (proxyServer.getGlobalStateHandler() != null) { +// pipeline.addLast("inboundGlobalStateHandler", new InboundGlobalStateHandler(clientConnection)); +// } + if (trafficHandler != null) { pipeline.addLast("global-traffic-shaping", trafficHandler); } - EventLoopGroup globalStateWrapperEvenLoop = clientConnection.new GlobalStateWrapperEvenLoop(channel.eventLoop()); + EventExecutorGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(clientConnection, channel.eventLoop()); pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); @@ -941,7 +946,11 @@ private void initChannelPipeline(ChannelPipeline pipeline, new IdleStateHandler(0, 0, proxyServer .getIdleConnectionTimeout())); - pipeline.addLast( "handler", this); +// if (proxyServer.getGlobalStateHandler() != null) { +// pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(clientConnection)); +// } + + pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); } /** From c99ca94c1fe77aa510ddb14c1fadeae84d1e16f0 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Mon, 7 Jan 2019 14:47:19 +0200 Subject: [PATCH 26/43] Remove commented code --- .../proxy/impl/ClientToProxyConnection.java | 10 +--------- .../proxy/impl/ProxyToServerConnection.java | 10 +--------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 603b15293..48a26b69b 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -861,11 +861,7 @@ private void initChannelPipeline(ChannelPipeline pipeline) { pipeline.addLast("requestTracerHandler", new RequestTracerHandler(this)); } -// if (proxyServer.getGlobalStateHandler() != null) { -// pipeline.addLast("inboundGlobalStateHandler", new InboundGlobalStateHandler(this)); -// } - - EventExecutorGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(this); + final EventExecutorGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(this); pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); @@ -893,10 +889,6 @@ private void initChannelPipeline(ChannelPipeline pipeline) { new IdleStateHandler(0, 0, proxyServer .getIdleConnectionTimeout())); -// if (proxyServer.getGlobalStateHandler() != null) { -// pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(this)); -// } - pipeline.addLast(globalStateWrapperEvenLoop, "handlerBegin", this); pipeline.addLast(globalStateWrapperEvenLoop, "clientToProxyProcessor", new ClientPayloadProcessor()); diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 36d810856..ecbe40210 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -911,15 +911,11 @@ private void setupConnectionParameters() throws UnknownHostException { private void initChannelPipeline(ChannelPipeline pipeline, Channel channel) { -// if (proxyServer.getGlobalStateHandler() != null) { -// pipeline.addLast("inboundGlobalStateHandler", new InboundGlobalStateHandler(clientConnection)); -// } - if (trafficHandler != null) { pipeline.addLast("global-traffic-shaping", trafficHandler); } - EventExecutorGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(clientConnection, channel.eventLoop()); + final EventExecutorGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(clientConnection, channel.eventLoop()); pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); @@ -946,10 +942,6 @@ private void initChannelPipeline(ChannelPipeline pipeline, new IdleStateHandler(0, 0, proxyServer .getIdleConnectionTimeout())); -// if (proxyServer.getGlobalStateHandler() != null) { -// pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(clientConnection)); -// } - pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); } From ca235d5c18ac08f24b3a2ab5738df037e0bdd83a Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Mon, 7 Jan 2019 20:07:33 +0200 Subject: [PATCH 27/43] Returns InboundGlobalStateHandler back --- .../proxy/impl/ClientToProxyConnection.java | 10 +++++++++- .../proxy/impl/GlobalStateWrapperEvenLoop.java | 8 ++------ .../proxy/impl/ProxyToServerConnection.java | 8 ++++++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 48a26b69b..2819349d6 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -861,7 +861,11 @@ private void initChannelPipeline(ChannelPipeline pipeline) { pipeline.addLast("requestTracerHandler", new RequestTracerHandler(this)); } - final EventExecutorGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(this); + if (proxyServer.getGlobalStateHandler() != null) { + pipeline.addLast("inboundGlobalStateHandler", new InboundGlobalStateHandler(this)); + } + + EventExecutorGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(this); pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor); pipeline.addLast(globalStateWrapperEvenLoop, "bytesWrittenMonitor", bytesWrittenMonitor); @@ -889,6 +893,10 @@ private void initChannelPipeline(ChannelPipeline pipeline) { new IdleStateHandler(0, 0, proxyServer .getIdleConnectionTimeout())); + if (proxyServer.getGlobalStateHandler() != null) { + pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(this)); + } + pipeline.addLast(globalStateWrapperEvenLoop, "handlerBegin", this); pipeline.addLast(globalStateWrapperEvenLoop, "clientToProxyProcessor", new ClientPayloadProcessor()); diff --git a/src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java b/src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java index 211f00d4e..5276275e6 100644 --- a/src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java +++ b/src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java @@ -33,11 +33,7 @@ public class GlobalStateWrapperEvenLoop implements EventExecutor { @Override public void execute(Runnable command) { - if (eventLoop.inEventLoop()) { - connection.wrapTask(command).run(); - } else { - eventLoop.execute(connection.wrapTask(command)); - } + eventLoop.execute(connection.wrapTask(command)); } @Override @@ -157,7 +153,7 @@ public EventExecutorGroup parent() { @Override public boolean inEventLoop() { - return false; + return eventLoop.inEventLoop(); } @Override diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index ecbe40210..77cfab055 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -911,6 +911,10 @@ private void setupConnectionParameters() throws UnknownHostException { private void initChannelPipeline(ChannelPipeline pipeline, Channel channel) { + if (proxyServer.getGlobalStateHandler() != null) { + pipeline.addLast("inboundGlobalStateHandler", new InboundGlobalStateHandler(clientConnection)); + } + if (trafficHandler != null) { pipeline.addLast("global-traffic-shaping", trafficHandler); } @@ -942,6 +946,10 @@ private void initChannelPipeline(ChannelPipeline pipeline, new IdleStateHandler(0, 0, proxyServer .getIdleConnectionTimeout())); + if (proxyServer.getGlobalStateHandler() != null) { + pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(clientConnection)); + } + pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); } From a73d0bf51ddfbff56a253c84baf127415b4ebf91 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 11 Jan 2019 00:31:52 +0200 Subject: [PATCH 28/43] Code refactoring --- .../proxy/HttpProxyServerBootstrap.java | 6 +- .../proxy/impl/ClientToProxyConnection.java | 139 +++++++++--------- .../proxy/impl/DefaultHttpProxyServer.java | 8 +- .../impl/GlobalStateWrapperEvenLoop.java | 2 +- .../proxy/impl/HttpInitialHandler.java | 20 +++ .../proxy/impl/ProxyConnection.java | 22 +-- .../proxy/impl/ProxyToServerConnection.java | 5 +- .../littleshoot/proxy/impl/ServerGroup.java | 2 +- .../proxy/impl/UpstreamConnectionHandler.java | 36 +++++ 9 files changed, 141 insertions(+), 99 deletions(-) create mode 100644 src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java create mode 100644 src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java diff --git a/src/main/java/org/littleshoot/proxy/HttpProxyServerBootstrap.java b/src/main/java/org/littleshoot/proxy/HttpProxyServerBootstrap.java index 3bbc6f910..8764d3521 100644 --- a/src/main/java/org/littleshoot/proxy/HttpProxyServerBootstrap.java +++ b/src/main/java/org/littleshoot/proxy/HttpProxyServerBootstrap.java @@ -245,11 +245,11 @@ HttpProxyServerBootstrap withCustomGlobalState( /** * - * @param payloadProcessorExecutor + * @param messageProcessorExecutor * @return */ - HttpProxyServerBootstrap withPyaloadProcessorExecutor( - ExecutorService payloadProcessorExecutor); + HttpProxyServerBootstrap withMessageProcessingExecutor( + ExecutorService messageProcessorExecutor); /** *

diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 2819349d6..ae7b11af4 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -3,7 +3,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelPipeline; @@ -85,7 +84,6 @@ * . *

*/ -@Sharable public class ClientToProxyConnection extends ProxyConnection { private static final HttpResponseStatus CONNECTION_ESTABLISHED = new HttpResponseStatus( 200, "Connection established"); @@ -143,8 +141,6 @@ public class ClientToProxyConnection extends ProxyConnection { private AtomicBoolean authenticated = new AtomicBoolean(); - private HttpResponse clientToProxyResponse; - private final GlobalTrafficShapingHandler globalTrafficShapingHandler; ClientToProxyConnection( @@ -188,7 +184,8 @@ public void operationComplete( **************************************************************************/ @Override - protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRequest httpRequest) { + protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, Object httpRequestObj) { + HttpRequest httpRequest = (HttpRequest) httpRequestObj; LOG.debug("Received raw request: {}", httpRequest); // if we cannot parse the request, immediately return a 400 and close the connection, since we do not know what state @@ -229,11 +226,11 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpRequest * @param httpRequest * @return */ - public ConnectionState doReadHTTPInitial(HttpRequest httpRequest) { - if (clientToProxyResponse != null) { - LOG.debug("Responding to client with short-circuit response from filter: {}", clientToProxyResponse); + public ConnectionState setupUpstreamConnection(HttpResponse shortCircuitResponse, HttpRequest httpRequest) { + if (shortCircuitResponse != null) { + LOG.debug("Responding to client with short-circuit response from filter: {}", shortCircuitResponse); - boolean keepAlive = respondWithShortCircuitResponse(clientToProxyResponse); + boolean keepAlive = respondWithShortCircuitResponse(shortCircuitResponse); if (keepAlive) { return AWAITING_INITIAL; } else { @@ -350,25 +347,24 @@ public ConnectionState doReadHTTPInitial(HttpRequest httpRequest) { } @Sharable - protected class ClientPayloadProcessor extends ChannelInboundHandlerAdapter { + protected class ClientToProxyMessageProcessor extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { - if (msg instanceof ReferenceCounted) { - LOG.debug("Retaining reference counted message"); - ((ReferenceCounted) msg).retain(); - } - - HttpRequest httpRequest = (HttpRequest) msg; + if (msg instanceof ReferenceCounted) { + LOG.debug("Retaining reference counted message"); + ((ReferenceCounted) msg).retain(); + } - if (ProxyUtils.isChunked(httpRequest)) { - process(ctx, msg, httpRequest); - } else { - proxyServer.getPayloadProcessorExecutor() - .execute(wrapTask(() -> process(ctx, msg, httpRequest))); - } + final HttpRequest httpRequest = (HttpRequest) msg; + if (ProxyUtils.isChunked(httpRequest)) { + process(ctx, msg, httpRequest); + } else { + proxyServer.getPayloadProcessorExecutor() + .execute(wrapTask(() -> process(ctx, msg, httpRequest))); + } } private void process(ChannelHandlerContext ctx, Object msg, HttpRequest httpRequest) { @@ -376,60 +372,59 @@ private void process(ChannelHandlerContext ctx, Object msg, HttpRequest httpRequ boolean authenticationRequired = authenticationRequired(httpRequest); if (authenticationRequired) { - LOG.debug("Not authenticated!!"); - become(AWAITING_PROXY_AUTHENTICATION); + LOG.debug("Not authenticated!!"); + become(AWAITING_PROXY_AUTHENTICATION); } else { - - // Make a copy of the original request - final HttpRequest currentRequest = copy(httpRequest); - - // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response - // should not be filtered), fall back to the default no-op filter source. - HttpFilters filterInstance; - try { - filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); - } finally { - // releasing a copied http request - if (currentRequest instanceof ReferenceCounted) { - ((ReferenceCounted) currentRequest).release(); + // Make a copy of the original request + final HttpRequest currentRequest = copy(httpRequest); + + // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response + // should not be filtered), fall back to the default no-op filter source. + HttpFilters filterInstance; + try { + filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); + } finally { + // releasing a copied http request + if (currentRequest instanceof ReferenceCounted) { + ((ReferenceCounted) currentRequest).release(); + } + } + if (filterInstance != null) { + currentFilters = filterInstance; + } else { + currentFilters = HttpFiltersAdapter.NOOP_FILTER; } - } - if (filterInstance != null) { - currentFilters = filterInstance; - } else { - currentFilters = HttpFiltersAdapter.NOOP_FILTER; - } - // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required - clientToProxyResponse = currentFilters.clientToProxyRequest(httpRequest); + // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required + final HttpResponse shortCircuitResponse = currentFilters.clientToProxyRequest(httpRequest); - if (msg instanceof ReferenceCounted) { - LOG.debug("Retaining reference counted message"); - ((ReferenceCounted) msg).retain(); - } + if (msg instanceof ReferenceCounted) { + LOG.debug("Retaining reference counted message"); + ((ReferenceCounted) msg).retain(); + } - ctx.fireChannelRead(httpRequest); + ctx.fireChannelRead(new UpstreamConnectionHandler.Request(httpRequest, shortCircuitResponse)); } } } - public Runnable wrapTask(Runnable task) { - return () -> { - if (proxyServer.getGlobalStateHandler() != null) { - try { - proxyServer.getGlobalStateHandler().restoreFromChannel(channel); - } finally { - try { - task.run(); - } finally { - proxyServer.getGlobalStateHandler().clear(); - } - } - } else { - task.run(); - } - }; - } + Runnable wrapTask(Runnable task) { + return () -> { + if (proxyServer.getGlobalStateHandler() != null) { + try { + proxyServer.getGlobalStateHandler().restoreFromChannel(channel); + } finally { + try { + task.run(); + } finally { + proxyServer.getGlobalStateHandler().clear(); + } + } + } else { + task.run(); + } + }; + } /** * Returns true if the specified request is a request to an origin server, rather than to a proxy server. If this @@ -897,11 +892,11 @@ private void initChannelPipeline(ChannelPipeline pipeline) { pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(this)); } - pipeline.addLast(globalStateWrapperEvenLoop, "handlerBegin", this); - - pipeline.addLast(globalStateWrapperEvenLoop, "clientToProxyProcessor", new ClientPayloadProcessor()); - - pipeline.addLast(globalStateWrapperEvenLoop, "handlerEnd", this); + pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); + HttpInitialHandler httpInitialHandler = new HttpInitialHandler<>(this); + pipeline.addLast(globalStateWrapperEvenLoop, "httpInitialHandler", httpInitialHandler); + pipeline.addLast(globalStateWrapperEvenLoop, "clientToProxyMessageProcessor", new ClientToProxyMessageProcessor()); + pipeline.addLast(globalStateWrapperEvenLoop, "upstreamConnectionHandler", new UpstreamConnectionHandler(this)); } diff --git a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java index 9f212c9fa..637ff0d7d 100644 --- a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java +++ b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java @@ -626,7 +626,7 @@ protected GlobalStateHandler getGlobalStateHandler() { } protected ExecutorService getPayloadProcessorExecutor() { - return serverGroup.getPayloadProcessingExecutor(); + return serverGroup.getMessageProcessingExecutor(); } protected RequestTracer getRequestTracer() { @@ -906,9 +906,9 @@ public HttpProxyServerBootstrap withCustomGlobalState( } @Override - public HttpProxyServerBootstrap withPyaloadProcessorExecutor( - ExecutorService payloadProcessorExecutor) { - this.payloadProcessorExecutor = payloadProcessorExecutor; + public HttpProxyServerBootstrap withMessageProcessingExecutor( + ExecutorService messageProcessorExecutor) { + this.payloadProcessorExecutor = messageProcessorExecutor; return this; } diff --git a/src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java b/src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java index 5276275e6..f3df53ed9 100644 --- a/src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java +++ b/src/main/java/org/littleshoot/proxy/impl/GlobalStateWrapperEvenLoop.java @@ -58,7 +58,7 @@ public Future terminationFuture() { @Override public void shutdown() { - eventLoop.terminationFuture(); + eventLoop.shutdown(); } @Override diff --git a/src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java b/src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java new file mode 100644 index 000000000..311e11e42 --- /dev/null +++ b/src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java @@ -0,0 +1,20 @@ +package org.littleshoot.proxy.impl; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.HttpObject; + +public class HttpInitialHandler extends SimpleChannelInboundHandler { + + private final ProxyConnection proxyConnection; + + HttpInitialHandler(ProxyConnection proxyConnection) { + this.proxyConnection = proxyConnection; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Object msg) { + final ConnectionState connectionState = proxyConnection.readHTTPInitial(ctx, msg); + proxyConnection.become(connectionState); + } +} diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java index d92f1a473..c79227c1f 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java @@ -129,11 +129,10 @@ protected void read(ChannelHandlerContext ctx, Object msg) { */ @SuppressWarnings("unchecked") private void readHTTP(ChannelHandlerContext ctx, HttpObject httpObject) { - ConnectionState nextState = getCurrentState(); switch (getCurrentState()) { case AWAITING_INITIAL: if (httpObject instanceof HttpMessage) { - nextState = processPayload(ctx, (I) httpObject); + ctx.fireChannelRead(httpObject); } else { // Similar to the AWAITING_PROXY_AUTHENTICATION case below, we may enter an AWAITING_INITIAL // state if the proxy responded to an earlier request with a 502 or 504 response, or a short-circuit @@ -145,13 +144,13 @@ private void readHTTP(ChannelHandlerContext ctx, HttpObject httpObject) { case AWAITING_CHUNK: HttpContent chunk = (HttpContent) httpObject; readHTTPChunk(chunk); - nextState = ProxyUtils.isLastChunk(chunk) ? AWAITING_INITIAL - : AWAITING_CHUNK; + become(ProxyUtils.isLastChunk(chunk) ? AWAITING_INITIAL + : AWAITING_CHUNK); break; case AWAITING_PROXY_AUTHENTICATION: if (httpObject instanceof HttpRequest) { // Once we get an HttpRequest, try to process it as usual - nextState = processPayload(ctx, (I) httpObject); + ctx.fireChannelRead(httpObject); } else { // Anything that's not an HttpRequest that came in while // we're pending authentication gets dropped on the floor. This @@ -179,17 +178,6 @@ private void readHTTP(ChannelHandlerContext ctx, HttpObject httpObject) { LOG.info("Ignoring message since the connection is closed or about to close"); break; } - become(nextState); - } - - private ConnectionState processPayload(ChannelHandlerContext ctx, I httpObject) { - ConnectionState nextState; - if (afterPayloadProcessor(ctx)) { - nextState = ((ClientToProxyConnection) this).doReadHTTPInitial((HttpRequest) httpObject); - } else { - nextState = readHTTPInitial(ctx, httpObject); - } - return nextState; } private boolean afterPayloadProcessor(ChannelHandlerContext ctx) { @@ -203,7 +191,7 @@ private boolean afterPayloadProcessor(ChannelHandlerContext ctx) { * @param httpObject * @return */ - protected abstract ConnectionState readHTTPInitial(ChannelHandlerContext ctx, I httpObject); + protected abstract ConnectionState readHTTPInitial(ChannelHandlerContext ctx, Object httpObject); /** * Implement this to handle reading a chunk in a chunked transfer. diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 77cfab055..c159681f0 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -223,7 +223,8 @@ protected void read(ChannelHandlerContext ctx, Object msg) { } @Override - protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, HttpResponse httpResponse) { + protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, Object httpResponseObj) { + HttpResponse httpResponse = (HttpResponse) httpResponseObj; LOG.debug("Received raw response: {}", httpResponse); if (httpResponse.getDecoderResult().isFailure()) { @@ -951,6 +952,8 @@ private void initChannelPipeline(ChannelPipeline pipeline, } pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); + HttpInitialHandler httpInitialHandler = new HttpInitialHandler<>(this); + pipeline.addLast(globalStateWrapperEvenLoop, "httpInitialHandler", httpInitialHandler); } /** diff --git a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java index 144b0f75e..98ca3eeb3 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java +++ b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java @@ -300,7 +300,7 @@ public EventLoopGroup getProxyToServerWorkerPoolForTransport(TransportProtocol p return getThreadPoolsForProtocol(protocol).getProxyToServerWorkerPool(); } - public ExecutorService getPayloadProcessingExecutor() { + public ExecutorService getMessageProcessingExecutor() { return payloadProcessingExecutor; } diff --git a/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java b/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java new file mode 100644 index 000000000..ca59f5011 --- /dev/null +++ b/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java @@ -0,0 +1,36 @@ +package org.littleshoot.proxy.impl; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; + +public class UpstreamConnectionHandler extends ChannelInboundHandlerAdapter { + + private final ClientToProxyConnection clientToProxyConnection; + + UpstreamConnectionHandler(ClientToProxyConnection clientToProxyConnection) { + this.clientToProxyConnection = clientToProxyConnection; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object request) { + final ConnectionState connectionState = + clientToProxyConnection.setupUpstreamConnection(((Request)request).getShortCircuitResponse(), + ((Request)request).getInitialRequest()); + clientToProxyConnection.become(connectionState); + } + + public static class Request { + private final HttpRequest initialRequest; + private final HttpResponse shortCircuitResponse; + + public Request(HttpRequest initialRequest, HttpResponse shortCircuitResponse) { + this.initialRequest = initialRequest; + this.shortCircuitResponse = shortCircuitResponse; + } + + HttpRequest getInitialRequest() { return initialRequest; } + HttpResponse getShortCircuitResponse() { return shortCircuitResponse; } + } +} From 5066c8e6fd421a41b89ef3607aa24e8c40e314bb Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 11 Jan 2019 00:49:21 +0200 Subject: [PATCH 29/43] Code refactoring 2 --- .../proxy/impl/ProxyConnection.java | 4 -- .../proxy/impl/ProxyToServerConnection.java | 45 +++++++++++-------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java index c79227c1f..4792daf6a 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java @@ -180,10 +180,6 @@ private void readHTTP(ChannelHandlerContext ctx, HttpObject httpObject) { } } - private boolean afterPayloadProcessor(ChannelHandlerContext ctx) { - return ctx.name().equals("handlerEnd"); - } - /** * Implement this to handle reading the initial object (e.g. * {@link HttpRequest} or {@link HttpResponse}). diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index c159681f0..7f406e599 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -12,6 +12,7 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; +import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.udt.nio.NioUdtProvider; import io.netty.handler.codec.http.FullHttpResponse; @@ -244,24 +245,32 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, Object http rememberCurrentResponse(httpResponse); - final HttpResponse resp = httpResponse; + ctx.fireChannelRead(httpResponse); - if (ProxyUtils.isChunked(httpResponse)) { - respondWith(resp); - return AWAITING_CHUNK; - } else { - if (resp instanceof ReferenceCounted) { - LOG.debug("Retaining reference counted message"); - ((ReferenceCounted) resp).retain(); - } + return getCurrentState(); + } - proxyServer.getPayloadProcessorExecutor() - .execute(clientConnection.wrapTask(() -> { - respondWith(resp); - currentFilters.serverToProxyResponseReceived(); - super.become(AWAITING_INITIAL); - })); - return getCurrentState(); + public class RespondToCientHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Object msg) { + final HttpResponse httpResponse = (HttpResponse) msg; + if (ProxyUtils.isChunked(httpResponse)) { + respondWith(httpResponse); + become(AWAITING_CHUNK); + } else { + if (httpResponse instanceof ReferenceCounted) { + LOG.debug("Retaining reference counted message"); + ((ReferenceCounted) httpResponse).retain(); + } + + proxyServer.getPayloadProcessorExecutor() + .execute(clientConnection.wrapTask(() -> { + respondWith(httpResponse); + currentFilters.serverToProxyResponseReceived(); + become(AWAITING_INITIAL); + })); + } } } @@ -952,8 +961,8 @@ private void initChannelPipeline(ChannelPipeline pipeline, } pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); - HttpInitialHandler httpInitialHandler = new HttpInitialHandler<>(this); - pipeline.addLast(globalStateWrapperEvenLoop, "httpInitialHandler", httpInitialHandler); + pipeline.addLast(globalStateWrapperEvenLoop, "httpInitialHandler", new HttpInitialHandler<>(this)); + pipeline.addLast(globalStateWrapperEvenLoop, "respondToClientHandler", new RespondToCientHandler()); } /** From 4fd951cd241cd2d572b72df43b6cbcf2bf92a717 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 11 Jan 2019 01:23:13 +0200 Subject: [PATCH 30/43] Fix ref counting --- .../proxy/impl/ClientToProxyConnection.java | 70 ++++++++++--------- .../proxy/impl/HttpInitialHandler.java | 6 +- .../proxy/impl/ProxyToServerConnection.java | 4 +- .../proxy/impl/UpstreamConnectionHandler.java | 6 +- 4 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index ae7b11af4..4d567e7e7 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -351,58 +351,64 @@ protected class ClientToProxyMessageProcessor extends ChannelInboundHandlerAdapt @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { + final HttpRequest httpRequest = (HttpRequest) msg; - if (msg instanceof ReferenceCounted) { + if (httpRequest instanceof ReferenceCounted) { LOG.debug("Retaining reference counted message"); ((ReferenceCounted) msg).retain(); } - final HttpRequest httpRequest = (HttpRequest) msg; - if (ProxyUtils.isChunked(httpRequest)) { - process(ctx, msg, httpRequest); + process(ctx, httpRequest); } else { proxyServer.getPayloadProcessorExecutor() - .execute(wrapTask(() -> process(ctx, msg, httpRequest))); + .execute(wrapTask(() -> process(ctx, httpRequest))); } } - private void process(ChannelHandlerContext ctx, Object msg, HttpRequest httpRequest) { + private void process(ChannelHandlerContext ctx, HttpRequest httpRequest) { - boolean authenticationRequired = authenticationRequired(httpRequest); + boolean authenticationRequired = false; + HttpResponse shortCircuitResponse = null; + try { + authenticationRequired = authenticationRequired(httpRequest); - if (authenticationRequired) { - LOG.debug("Not authenticated!!"); - become(AWAITING_PROXY_AUTHENTICATION); - } else { - // Make a copy of the original request - final HttpRequest currentRequest = copy(httpRequest); + if (authenticationRequired) { + LOG.debug("Not authenticated!!"); + become(AWAITING_PROXY_AUTHENTICATION); + } else { + // Make a copy of the original request + final HttpRequest currentRequest = copy(httpRequest); - // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response - // should not be filtered), fall back to the default no-op filter source. - HttpFilters filterInstance; - try { - filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); - } finally { - // releasing a copied http request - if (currentRequest instanceof ReferenceCounted) { - ((ReferenceCounted) currentRequest).release(); + // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response + // should not be filtered), fall back to the default no-op filter source. + HttpFilters filterInstance; + try { + filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); + } finally { + // releasing a copied http request + if (currentRequest instanceof ReferenceCounted) { + ((ReferenceCounted) currentRequest).release(); + } + } + if (filterInstance != null) { + currentFilters = filterInstance; + } else { + currentFilters = HttpFiltersAdapter.NOOP_FILTER; } - } - if (filterInstance != null) { - currentFilters = filterInstance; - } else { - currentFilters = HttpFiltersAdapter.NOOP_FILTER; - } - // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required - final HttpResponse shortCircuitResponse = currentFilters.clientToProxyRequest(httpRequest); + // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required + shortCircuitResponse = currentFilters.clientToProxyRequest(httpRequest); - if (msg instanceof ReferenceCounted) { + } + } catch (Exception e) { + if (httpRequest instanceof ReferenceCounted) { LOG.debug("Retaining reference counted message"); - ((ReferenceCounted) msg).retain(); + ((ReferenceCounted) httpRequest).release(); } + } + if (!authenticationRequired) { ctx.fireChannelRead(new UpstreamConnectionHandler.Request(httpRequest, shortCircuitResponse)); } } diff --git a/src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java b/src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java index 311e11e42..58f4d9dcf 100644 --- a/src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java +++ b/src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java @@ -1,10 +1,10 @@ package org.littleshoot.proxy.impl; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpObject; -public class HttpInitialHandler extends SimpleChannelInboundHandler { +public class HttpInitialHandler extends ChannelInboundHandlerAdapter { private final ProxyConnection proxyConnection; @@ -13,7 +13,7 @@ public class HttpInitialHandler extends SimpleChannelInbou } @Override - protected void channelRead0(ChannelHandlerContext ctx, Object msg) { + public void channelRead(ChannelHandlerContext ctx, Object msg) { final ConnectionState connectionState = proxyConnection.readHTTPInitial(ctx, msg); proxyConnection.become(connectionState); } diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 7f406e599..fd6404902 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -250,7 +250,7 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, Object http return getCurrentState(); } - public class RespondToCientHandler extends SimpleChannelInboundHandler { + public class RespondToClientHandler extends SimpleChannelInboundHandler { @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) { @@ -962,7 +962,7 @@ private void initChannelPipeline(ChannelPipeline pipeline, pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); pipeline.addLast(globalStateWrapperEvenLoop, "httpInitialHandler", new HttpInitialHandler<>(this)); - pipeline.addLast(globalStateWrapperEvenLoop, "respondToClientHandler", new RespondToCientHandler()); + pipeline.addLast(globalStateWrapperEvenLoop, "respondToClientHandler", new RespondToClientHandler()); } /** diff --git a/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java b/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java index ca59f5011..f366caaf5 100644 --- a/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java +++ b/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java @@ -1,11 +1,11 @@ package org.littleshoot.proxy.impl; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; -public class UpstreamConnectionHandler extends ChannelInboundHandlerAdapter { +public class UpstreamConnectionHandler extends SimpleChannelInboundHandler { private final ClientToProxyConnection clientToProxyConnection; @@ -14,7 +14,7 @@ public class UpstreamConnectionHandler extends ChannelInboundHandlerAdapter { } @Override - public void channelRead(ChannelHandlerContext ctx, Object request) { + protected void channelRead0(ChannelHandlerContext ctx, Object request) { final ConnectionState connectionState = clientToProxyConnection.setupUpstreamConnection(((Request)request).getShortCircuitResponse(), ((Request)request).getInitialRequest()); From aed95cf7b285a0e7cbbd005266e34e5a8a302a9c Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 11 Jan 2019 09:51:20 +0200 Subject: [PATCH 31/43] Fix the build --- .../proxy/impl/ClientToProxyConnection.java | 1 + .../littleshoot/proxy/impl/HttpInitialHandler.java | 5 +++++ .../proxy/impl/ProxyToServerConnection.java | 11 ++++++++--- .../proxy/impl/UpstreamConnectionHandler.java | 5 +++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 4d567e7e7..e7437bd94 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -406,6 +406,7 @@ private void process(ChannelHandlerContext ctx, HttpRequest httpRequest) { LOG.debug("Retaining reference counted message"); ((ReferenceCounted) httpRequest).release(); } + ctx.fireExceptionCaught(e); } if (!authenticationRequired) { diff --git a/src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java b/src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java index 58f4d9dcf..e715bb8ee 100644 --- a/src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java +++ b/src/main/java/org/littleshoot/proxy/impl/HttpInitialHandler.java @@ -17,4 +17,9 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { final ConnectionState connectionState = proxyConnection.readHTTPInitial(ctx, msg); proxyConnection.become(connectionState); } + + @Override + public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + proxyConnection.exceptionCaught(cause); + } } diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index fd6404902..3794902de 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -9,10 +9,10 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; -import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.udt.nio.NioUdtProvider; import io.netty.handler.codec.http.FullHttpResponse; @@ -250,10 +250,10 @@ protected ConnectionState readHTTPInitial(ChannelHandlerContext ctx, Object http return getCurrentState(); } - public class RespondToClientHandler extends SimpleChannelInboundHandler { + public class RespondToClientHandler extends ChannelInboundHandlerAdapter { @Override - protected void channelRead0(ChannelHandlerContext ctx, Object msg) { + public void channelRead(ChannelHandlerContext ctx, Object msg) { final HttpResponse httpResponse = (HttpResponse) msg; if (ProxyUtils.isChunked(httpResponse)) { respondWith(httpResponse); @@ -272,6 +272,11 @@ protected void channelRead0(ChannelHandlerContext ctx, Object msg) { })); } } + + @Override + public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + serverConnection.exceptionCaught(cause); + } } @Override diff --git a/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java b/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java index f366caaf5..088459fb3 100644 --- a/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java +++ b/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java @@ -21,6 +21,11 @@ protected void channelRead0(ChannelHandlerContext ctx, Object request) { clientToProxyConnection.become(connectionState); } + @Override + public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + clientToProxyConnection.exceptionCaught(cause); + } + public static class Request { private final HttpRequest initialRequest; private final HttpResponse shortCircuitResponse; From a0636c63381b28bf340a19911f5afe227f97bfdd Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 11 Jan 2019 09:53:07 +0200 Subject: [PATCH 32/43] inline handler --- .../org/littleshoot/proxy/impl/ClientToProxyConnection.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index e7437bd94..e9f39bf17 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -900,8 +900,7 @@ private void initChannelPipeline(ChannelPipeline pipeline) { } pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); - HttpInitialHandler httpInitialHandler = new HttpInitialHandler<>(this); - pipeline.addLast(globalStateWrapperEvenLoop, "httpInitialHandler", httpInitialHandler); + pipeline.addLast(globalStateWrapperEvenLoop, "httpInitialHandler", new HttpInitialHandler<>(this)); pipeline.addLast(globalStateWrapperEvenLoop, "clientToProxyMessageProcessor", new ClientToProxyMessageProcessor()); pipeline.addLast(globalStateWrapperEvenLoop, "upstreamConnectionHandler", new UpstreamConnectionHandler(this)); From 62156df481a21bf72dd73273f64418ed67c61808 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 11 Jan 2019 15:00:51 +0200 Subject: [PATCH 33/43] Fix build 2 --- .../org/littleshoot/proxy/impl/ProxyToServerConnection.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 3794902de..68d07ce08 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -857,6 +857,8 @@ protected boolean connectionFailed(Throwable cause) private void resetConnectionForRetry() throws UnknownHostException { // Remove ourselves as handler on the old context this.ctx.pipeline().remove(this); + this.ctx.pipeline().remove("httpInitialHandler"); + this.ctx.pipeline().remove("respondToClientHandler"); this.ctx.close(); this.ctx = null; From 0c9757af270c1f12089aa54b831fce8a66079a0d Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 11 Jan 2019 15:25:39 +0200 Subject: [PATCH 34/43] Cleanup code --- .../littleshoot/proxy/impl/ClientToProxyConnection.java | 4 ++-- .../java/org/littleshoot/proxy/impl/ProxyConnection.java | 8 ++++---- .../littleshoot/proxy/impl/ProxyToServerConnection.java | 7 +++---- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index e9f39bf17..87c9cef93 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -864,7 +864,7 @@ private void initChannelPipeline(ChannelPipeline pipeline) { } if (proxyServer.getGlobalStateHandler() != null) { - pipeline.addLast("inboundGlobalStateHandler", new InboundGlobalStateHandler(this)); + pipeline.addLast("inboundGlobalStateHandler", new InboundGlobalStateHandler(this)); } EventExecutorGroup globalStateWrapperEvenLoop = new GlobalStateWrapperEvenLoop(this); @@ -896,7 +896,7 @@ private void initChannelPipeline(ChannelPipeline pipeline) { .getIdleConnectionTimeout())); if (proxyServer.getGlobalStateHandler() != null) { - pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(this)); + pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(this)); } pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java index 4792daf6a..eed893c22 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java @@ -108,7 +108,7 @@ protected ProxyConnection(ConnectionState initialState, * * @param msg */ - protected void read(ChannelHandlerContext ctx, Object msg) { + protected void read(Object msg) { LOG.debug("Reading: {}", msg); lastReadTime = System.currentTimeMillis(); @@ -118,7 +118,7 @@ protected void read(ChannelHandlerContext ctx, Object msg) { readRaw((ByteBuf) msg); } else { // If not tunneling, then we are always dealing with HttpObjects. - readHTTP(ctx, (HttpObject) msg); + readHTTP((HttpObject) msg); } } @@ -128,7 +128,7 @@ protected void read(ChannelHandlerContext ctx, Object msg) { * @param httpObject */ @SuppressWarnings("unchecked") - private void readHTTP(ChannelHandlerContext ctx, HttpObject httpObject) { + private void readHTTP(HttpObject httpObject) { switch (getCurrentState()) { case AWAITING_INITIAL: if (httpObject instanceof HttpMessage) { @@ -583,7 +583,7 @@ ProxyConnectionLogger getLOG() { @Override protected final void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { - read(ctx, msg); + read(msg); } @Override diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 68d07ce08..3a7499ed8 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -212,14 +212,14 @@ private ProxyToServerConnection( **************************************************************************/ @Override - protected void read(ChannelHandlerContext ctx, Object msg) { + protected void read(Object msg) { if (isConnecting()) { LOG.debug( "In the middle of connecting, forwarding message to connection flow: {}", msg); this.connectionFlow.read(msg); } else { - super.read(ctx, msg); + super.read(msg); } } @@ -925,8 +925,7 @@ private void setupConnectionParameters() throws UnknownHostException { * @param pipeline * @param httpRequest */ - private void initChannelPipeline(ChannelPipeline pipeline, - Channel channel) { + private void initChannelPipeline(ChannelPipeline pipeline, Channel channel) { if (proxyServer.getGlobalStateHandler() != null) { pipeline.addLast("inboundGlobalStateHandler", new InboundGlobalStateHandler(clientConnection)); From c841821b311790f038f9271947200efd33a416b6 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 11 Jan 2019 15:28:10 +0200 Subject: [PATCH 35/43] Return after exception caught --- .../java/org/littleshoot/proxy/impl/ClientToProxyConnection.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 87c9cef93..113226d90 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -407,6 +407,7 @@ private void process(ChannelHandlerContext ctx, HttpRequest httpRequest) { ((ReferenceCounted) httpRequest).release(); } ctx.fireExceptionCaught(e); + return; } if (!authenticationRequired) { From cb078717bbdb428ae108a3c2db1c23d4b1bf83ef Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Tue, 15 Jan 2019 18:08:22 +0200 Subject: [PATCH 36/43] First unit test --- .../littleshoot/proxy/ServerGroupTest.java | 60 +++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/littleshoot/proxy/ServerGroupTest.java b/src/test/java/org/littleshoot/proxy/ServerGroupTest.java index 629a47c2f..14b990c28 100644 --- a/src/test/java/org/littleshoot/proxy/ServerGroupTest.java +++ b/src/test/java/org/littleshoot/proxy/ServerGroupTest.java @@ -4,6 +4,7 @@ import io.netty.handler.codec.http.HttpRequest; import org.apache.http.HttpResponse; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.littleshoot.proxy.impl.DefaultHttpProxyServer; @@ -12,14 +13,17 @@ import org.mockserver.integration.ClientAndServer; import org.mockserver.matchers.Times; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; @@ -52,6 +56,7 @@ public void tearDown() { public void testSingleWorkerThreadPoolConfiguration() throws ExecutionException, InterruptedException { final String firstRequestPath = "/testSingleThreadFirstRequest"; final String secondRequestPath = "/testSingleThreadSecondRequest"; + final String messageProcessingThreadName = UUID.randomUUID().toString(); // set up two server responses that will execute more or less simultaneously. the first request has a small // delay, to reduce the chance that the first request will finish entirely before the second request is finished @@ -88,6 +93,19 @@ public void testSingleWorkerThreadPoolConfiguration() throws ExecutionException, proxyServer = DefaultHttpProxyServer.bootstrap() .withPort(0) .withFiltersSource(new HttpFiltersSourceAdapter() { + + // required so chinks for used + @Override + public int getMaximumRequestBufferSizeInBytes() { + return 8388608 * 2; + } + + @Override + public int getMaximumResponseBufferSizeInBytes() { + return 8388608 * 2; + } + + @Override public HttpFilters filterRequest(HttpRequest originalRequest) { return new HttpFiltersAdapter(originalRequest) { @@ -95,6 +113,13 @@ public HttpFilters filterRequest(HttpRequest originalRequest) { public io.netty.handler.codec.http.HttpResponse clientToProxyRequest(HttpObject httpObject) { if (originalRequest.getUri().endsWith(firstRequestPath)) { firstClientThreadName.set(Thread.currentThread().getName()); + + try { + Thread.sleep(4000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } else if (originalRequest.getUri().endsWith(secondRequestPath)) { secondClientThreadName.set(Thread.currentThread().getName()); } @@ -103,12 +128,13 @@ public io.netty.handler.codec.http.HttpResponse clientToProxyRequest(HttpObject } @Override - public void serverToProxyResponseReceived() { + public HttpObject serverToProxyResponse(HttpObject httpObject) { if (originalRequest.getUri().endsWith(firstRequestPath)) { firstProxyThreadName.set(Thread.currentThread().getName()); } else if (originalRequest.getUri().endsWith(secondRequestPath)) { secondProxyThreadName.set(Thread.currentThread().getName()); } + return httpObject; } }; } @@ -117,6 +143,11 @@ public void serverToProxyResponseReceived() { .withAcceptorThreads(1) .withClientToProxyWorkerThreads(1) .withProxyToServerWorkerThreads(1)) + .withMessageProcessingExecutor(Executors.newFixedThreadPool(2, r -> { + final Thread thread = new Thread(r); + thread.setName(messageProcessingThreadName); + return thread; + })) .start(); // execute both requests in parallel, to increase the chance of blocking due to the single-threaded ThreadPoolConfiguration @@ -139,15 +170,36 @@ public void run() { ExecutorService executor = Executors.newFixedThreadPool(2); Future firstFuture = executor.submit(firstRequest); + Thread.sleep(500); Future secondFuture = executor.submit(secondRequest); - firstFuture.get(); - secondFuture.get(); - Thread.sleep(500); + try { + secondFuture.get(2, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("Second request took longer than expected"); + } + + boolean firstStillExecuting = false; + try { + firstFuture.get(2, TimeUnit.SECONDS); + } catch (TimeoutException e) { + firstStillExecuting = true; + } + + Assert.assertTrue("First request must be still executing", firstStillExecuting); + + try { + firstFuture.get(3, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("First request took longer than expected"); + } assertEquals("Expected clientToProxy filter methods to be executed on the same thread for both requests", firstClientThreadName.get(), secondClientThreadName.get()); assertEquals("Expected serverToProxy filter methods to be executed on the same thread for both requests", firstProxyThreadName.get(), secondProxyThreadName.get()); + + assertEquals(firstClientThreadName.get(), messageProcessingThreadName); + assertEquals(firstProxyThreadName.get(), messageProcessingThreadName); } } From 7f336927baff174c94db3362ae791de1849123a4 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Tue, 15 Jan 2019 20:20:42 +0200 Subject: [PATCH 37/43] Foundation for tests --- .../littleshoot/proxy/ServerGroupTest.java | 235 ++++++++++++------ 1 file changed, 153 insertions(+), 82 deletions(-) diff --git a/src/test/java/org/littleshoot/proxy/ServerGroupTest.java b/src/test/java/org/littleshoot/proxy/ServerGroupTest.java index 14b990c28..b8a51e37c 100644 --- a/src/test/java/org/littleshoot/proxy/ServerGroupTest.java +++ b/src/test/java/org/littleshoot/proxy/ServerGroupTest.java @@ -27,81 +27,128 @@ import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; + +// set up two server responses that will execute more or less simultaneously. the first request has a small +// delay, to reduce the chance that the first request will finish entirely before the second request is finished +// (and thus be somewhat more likely to be serviced by the same thread, even if the ThreadPoolConfiguration is +// not behaving properly). + + +// save the names of the threads that execute the filter methods. filter methods are executed by the worker thread +// handling the request/response, so if there is only one worker thread, the filter methods should be executed +// by the same thread. + public class ServerGroupTest { private ClientAndServer mockServer; private int mockServerPort; - private HttpProxyServer proxyServer; + final String firstRequestPath = "/testSingleThreadFirstRequest"; + final String secondRequestPath = "/testSingleThreadSecondRequest"; + final String messageProcessingThreadName = UUID.randomUUID().toString(); + + final AtomicReference firstClientThreadName = new AtomicReference(); + final AtomicReference secondClientThreadName = new AtomicReference(); + + final AtomicReference firstProxyThreadName = new AtomicReference(); + final AtomicReference secondProxyThreadName = new AtomicReference(); @Before public void setUp() { mockServer = new ClientAndServer(0); mockServerPort = mockServer.getPort(); + + mockServer.when(request() + .withMethod("GET") + .withPath(firstRequestPath), + Times.exactly(1)) + .respond(response() + .withStatusCode(200) + .withBody("first") + .withDelay(TimeUnit.MILLISECONDS, 500) + ); + + mockServer.when(request() + .withMethod("GET") + .withPath(secondRequestPath), + Times.exactly(1)) + .respond(response() + .withStatusCode(200) + .withBody("second") + ); } @After public void tearDown() { - try { - if (mockServer != null) { - mockServer.stop(); - } - } finally { - if (proxyServer != null) { - proxyServer.abort(); - } + if (mockServer != null) { + mockServer.stop(); } } @Test public void testSingleWorkerThreadPoolConfiguration() throws ExecutionException, InterruptedException { - final String firstRequestPath = "/testSingleThreadFirstRequest"; - final String secondRequestPath = "/testSingleThreadSecondRequest"; - final String messageProcessingThreadName = UUID.randomUUID().toString(); - - // set up two server responses that will execute more or less simultaneously. the first request has a small - // delay, to reduce the chance that the first request will finish entirely before the second request is finished - // (and thus be somewhat more likely to be serviced by the same thread, even if the ThreadPoolConfiguration is - // not behaving properly). - mockServer.when(request() - .withMethod("GET") - .withPath(firstRequestPath), - Times.exactly(1)) - .respond(response() - .withStatusCode(200) - .withBody("first") - .withDelay(TimeUnit.MILLISECONDS, 500) - ); - mockServer.when(request() - .withMethod("GET") - .withPath(secondRequestPath), - Times.exactly(1)) - .respond(response() - .withStatusCode(200) - .withBody("second") - ); - - // save the names of the threads that execute the filter methods. filter methods are executed by the worker thread - // handling the request/response, so if there is only one worker thread, the filter methods should be executed - // by the same thread. - final AtomicReference firstClientThreadName = new AtomicReference(); - final AtomicReference secondClientThreadName = new AtomicReference(); - - final AtomicReference firstProxyThreadName = new AtomicReference(); - final AtomicReference secondProxyThreadName = new AtomicReference(); - - proxyServer = DefaultHttpProxyServer.bootstrap() + final HttpProxyServer proxyServer = getProxy(2, true, + false, false, false, false, + false, false, false, false); + + final Futures futures = runTwoRequests(proxyServer); + + try { + futures.getSecondFuture().get(2, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("Second request took longer than expected"); + } + + boolean firstStillExecuting = false; + try { + futures.getFirstFuture().get(2, TimeUnit.SECONDS); + } catch (TimeoutException e) { + firstStillExecuting = true; + } + + Assert.assertTrue("First request must be still executing", firstStillExecuting); + + try { + futures.getFirstFuture().get(3, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("First request took longer than expected"); + } + + assertEquals("Expected clientToProxy filter methods to be executed on the same thread for both requests", firstClientThreadName.get(), secondClientThreadName.get()); + assertEquals("Expected serverToProxy filter methods to be executed on the same thread for both requests", firstProxyThreadName.get(), secondProxyThreadName.get()); + + assertEquals(firstClientThreadName.get(), messageProcessingThreadName); + assertEquals(firstProxyThreadName.get(), messageProcessingThreadName); + } + + private HttpProxyServer getProxy(int processingThreads, + boolean blockFirstRequest, + boolean blockSecondRequest, + boolean blockFirstResponse, + boolean blockSecondResponse, + boolean throwFirstRequestException, + boolean throwSecondRequestException, + boolean throwFirstResponseException, + boolean throwSecondResponseException, + boolean withChunks) { + return DefaultHttpProxyServer.bootstrap() .withPort(0) .withFiltersSource(new HttpFiltersSourceAdapter() { // required so chinks for used @Override public int getMaximumRequestBufferSizeInBytes() { + if (withChunks) { + return 0; + } return 8388608 * 2; } @Override public int getMaximumResponseBufferSizeInBytes() { + if (withChunks) { + return 0; + } return 8388608 * 2; } @@ -114,14 +161,24 @@ public io.netty.handler.codec.http.HttpResponse clientToProxyRequest(HttpObject if (originalRequest.getUri().endsWith(firstRequestPath)) { firstClientThreadName.set(Thread.currentThread().getName()); - try { - Thread.sleep(4000); - } catch (InterruptedException e) { - e.printStackTrace(); + if (throwFirstRequestException) { + throw new RuntimeException("first-request"); + } + + if (blockFirstRequest) { + block(); } } else if (originalRequest.getUri().endsWith(secondRequestPath)) { secondClientThreadName.set(Thread.currentThread().getName()); + + if (throwSecondRequestException) { + throw new RuntimeException("second-request"); + } + + if (blockSecondRequest) { + block(); + } } return super.clientToProxyRequest(httpObject); @@ -131,8 +188,25 @@ public io.netty.handler.codec.http.HttpResponse clientToProxyRequest(HttpObject public HttpObject serverToProxyResponse(HttpObject httpObject) { if (originalRequest.getUri().endsWith(firstRequestPath)) { firstProxyThreadName.set(Thread.currentThread().getName()); + + if (throwFirstResponseException) { + throw new RuntimeException("first-response"); + } + + if (blockFirstResponse) { + block(); + } + } else if (originalRequest.getUri().endsWith(secondRequestPath)) { secondProxyThreadName.set(Thread.currentThread().getName()); + + if (throwSecondResponseException) { + throw new RuntimeException("second-response"); + } + + if (blockSecondResponse) { + block(); + } } return httpObject; } @@ -143,29 +217,33 @@ public HttpObject serverToProxyResponse(HttpObject httpObject) { .withAcceptorThreads(1) .withClientToProxyWorkerThreads(1) .withProxyToServerWorkerThreads(1)) - .withMessageProcessingExecutor(Executors.newFixedThreadPool(2, r -> { + .withMessageProcessingExecutor(Executors.newFixedThreadPool(processingThreads, r -> { final Thread thread = new Thread(r); thread.setName(messageProcessingThreadName); return thread; })) .start(); + } + + private void block() { + try { + Thread.sleep(4000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + private Futures runTwoRequests(HttpProxyServer proxyServer) throws InterruptedException { // execute both requests in parallel, to increase the chance of blocking due to the single-threaded ThreadPoolConfiguration - Runnable firstRequest = new Runnable() { - @Override - public void run() { - HttpResponse response = HttpClientUtil.performHttpGet("http://localhost:" + mockServerPort + firstRequestPath, proxyServer); - assertEquals(200, response.getStatusLine().getStatusCode()); - } + final Runnable firstRequest = () -> { + HttpResponse response = HttpClientUtil.performHttpGet("http://localhost:" + mockServerPort + firstRequestPath, proxyServer); + assertEquals(200, response.getStatusLine().getStatusCode()); }; - Runnable secondRequest = new Runnable () { - @Override - public void run() { - HttpResponse response = HttpClientUtil.performHttpGet("http://localhost:" + mockServerPort + secondRequestPath, proxyServer); - assertEquals(200, response.getStatusLine().getStatusCode()); - } + final Runnable secondRequest = () -> { + HttpResponse response = HttpClientUtil.performHttpGet("http://localhost:" + mockServerPort + secondRequestPath, proxyServer); + assertEquals(200, response.getStatusLine().getStatusCode()); }; ExecutorService executor = Executors.newFixedThreadPool(2); @@ -173,33 +251,26 @@ public void run() { Thread.sleep(500); Future secondFuture = executor.submit(secondRequest); + return new Futures(firstFuture, secondFuture); + } - try { - secondFuture.get(2, TimeUnit.SECONDS); - } catch (TimeoutException e) { - fail("Second request took longer than expected"); + private static class Futures { + Future getFirstFuture() { + return firstFuture; } - boolean firstStillExecuting = false; - try { - firstFuture.get(2, TimeUnit.SECONDS); - } catch (TimeoutException e) { - firstStillExecuting = true; + Future getSecondFuture() { + return secondFuture; } - Assert.assertTrue("First request must be still executing", firstStillExecuting); + final Future firstFuture; + final Future secondFuture; - try { - firstFuture.get(3, TimeUnit.SECONDS); - } catch (TimeoutException e) { - fail("First request took longer than expected"); + private Futures(Future firstFuture, Future secondFuture) { + this.firstFuture = firstFuture; + this.secondFuture = secondFuture; } - - assertEquals("Expected clientToProxy filter methods to be executed on the same thread for both requests", firstClientThreadName.get(), secondClientThreadName.get()); - assertEquals("Expected serverToProxy filter methods to be executed on the same thread for both requests", firstProxyThreadName.get(), secondProxyThreadName.get()); - - assertEquals(firstClientThreadName.get(), messageProcessingThreadName); - assertEquals(firstProxyThreadName.get(), messageProcessingThreadName); } + } From 33ec33e50bcd49db0123c64fa9da8ecd036b66cd Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Tue, 15 Jan 2019 21:34:19 +0200 Subject: [PATCH 38/43] More tests --- .../littleshoot/proxy/ServerGroupTest.java | 122 +++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/littleshoot/proxy/ServerGroupTest.java b/src/test/java/org/littleshoot/proxy/ServerGroupTest.java index b8a51e37c..f76f4fef2 100644 --- a/src/test/java/org/littleshoot/proxy/ServerGroupTest.java +++ b/src/test/java/org/littleshoot/proxy/ServerGroupTest.java @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; @@ -79,13 +80,36 @@ public void setUp() { @After public void tearDown() { + firstClientThreadName.set(null); + secondClientThreadName.set(null); + firstProxyThreadName.set(null); + secondProxyThreadName.set(null); if (mockServer != null) { mockServer.stop(); } } @Test - public void testSingleWorkerThreadPoolConfiguration() throws ExecutionException, InterruptedException { + public void testChunkedRequest() throws ExecutionException, InterruptedException { + + final HttpProxyServer proxyServer = getProxy(2, false, + false, false, false, false, + false, false, false, true); + + final Futures futures = runTwoRequests(proxyServer); + + futures.getFirstFuture().get(); + futures.getSecondFuture().get(); + + assertEquals("Expected clientToProxy filter methods to be executed on the same thread for both requests", firstClientThreadName.get(), secondClientThreadName.get()); + assertEquals("Expected serverToProxy filter methods to be executed on the same thread for both requests", firstProxyThreadName.get(), secondProxyThreadName.get()); + + assertNotEquals(firstClientThreadName.get(), messageProcessingThreadName); + assertNotEquals(firstProxyThreadName.get(), messageProcessingThreadName); + } + + @Test + public void testBlockFirstRequest() throws ExecutionException, InterruptedException { final HttpProxyServer proxyServer = getProxy(2, true, false, false, false, false, @@ -121,6 +145,102 @@ public void testSingleWorkerThreadPoolConfiguration() throws ExecutionException, assertEquals(firstProxyThreadName.get(), messageProcessingThreadName); } + @Test + public void testBlockFirstResponse() throws ExecutionException, InterruptedException { + + final HttpProxyServer proxyServer = getProxy(2, false, + false, true, false, false, + false, false, false, false); + + final Futures futures = runTwoRequests(proxyServer); + + try { + futures.getSecondFuture().get(2, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("Second request took longer than expected"); + } + + boolean firstStillExecuting = false; + try { + futures.getFirstFuture().get(2, TimeUnit.SECONDS); + } catch (TimeoutException e) { + firstStillExecuting = true; + } + + Assert.assertTrue("First request must be still executing", firstStillExecuting); + + try { + futures.getFirstFuture().get(3, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("First request took longer than expected"); + } + + assertEquals("Expected clientToProxy filter methods to be executed on the same thread for both requests", firstClientThreadName.get(), secondClientThreadName.get()); + assertEquals("Expected serverToProxy filter methods to be executed on the same thread for both requests", firstProxyThreadName.get(), secondProxyThreadName.get()); + + assertEquals(firstClientThreadName.get(), messageProcessingThreadName); + assertEquals(firstProxyThreadName.get(), messageProcessingThreadName); + } + + @Test(expected = ExecutionException.class) + public void testExceptionFirstRequest() throws ExecutionException, InterruptedException { + + final HttpProxyServer proxyServer = getProxy(2, false, + false, false, false, true, + false, false, false, false); + + final Futures futures = runTwoRequests(proxyServer); + + futures.getFirstFuture().get(); + futures.getSecondFuture().get(); + } + + @Test + public void testBlockFirstRequestSingleProcessingThread() throws ExecutionException, InterruptedException { + + final HttpProxyServer proxyServer = getProxy(1, true, + false, false, false, false, + false, false, false, false); + + final Futures futures = runTwoRequests(proxyServer); + + boolean secondStillExecuting = false; + try { + futures.getSecondFuture().get(2, TimeUnit.SECONDS); + } catch (TimeoutException e) { + secondStillExecuting = true; + } + + Assert.assertTrue("Second request must be still executing", secondStillExecuting); + + boolean firstStillExecuting = false; + try { + futures.getFirstFuture().get(2, TimeUnit.SECONDS); + } catch (TimeoutException e) { + firstStillExecuting = true; + } + + Assert.assertTrue("First request must be still executing", firstStillExecuting); + + try { + futures.getFirstFuture().get(3, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("First request took longer than expected"); + } + + try { + futures.getSecondFuture().get(3, TimeUnit.SECONDS); + } catch (TimeoutException e) { + fail("Second request took longer than expected"); + } + + assertEquals("Expected clientToProxy filter methods to be executed on the same thread for both requests", firstClientThreadName.get(), secondClientThreadName.get()); + assertEquals("Expected serverToProxy filter methods to be executed on the same thread for both requests", firstProxyThreadName.get(), secondProxyThreadName.get()); + + assertEquals(firstClientThreadName.get(), messageProcessingThreadName); + assertEquals(firstProxyThreadName.get(), messageProcessingThreadName); + } + private HttpProxyServer getProxy(int processingThreads, boolean blockFirstRequest, boolean blockSecondRequest, From adea07c03d72335c9d20b38adcd1ca4e75bdd3ae Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Tue, 15 Jan 2019 21:49:49 +0200 Subject: [PATCH 39/43] Renames handler --- .../org/littleshoot/proxy/impl/ClientToProxyConnection.java | 2 +- .../org/littleshoot/proxy/impl/ProxyToServerConnection.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 113226d90..5f25be2cb 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -900,7 +900,7 @@ private void initChannelPipeline(ChannelPipeline pipeline) { pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(this)); } - pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); + pipeline.addLast(globalStateWrapperEvenLoop, "router", this); pipeline.addLast(globalStateWrapperEvenLoop, "httpInitialHandler", new HttpInitialHandler<>(this)); pipeline.addLast(globalStateWrapperEvenLoop, "clientToProxyMessageProcessor", new ClientToProxyMessageProcessor()); pipeline.addLast(globalStateWrapperEvenLoop, "upstreamConnectionHandler", new UpstreamConnectionHandler(this)); diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 3a7499ed8..99f817869 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -966,7 +966,7 @@ private void initChannelPipeline(ChannelPipeline pipeline, Channel channel) { pipeline.addLast("outboundGlobalStateHandler", new OutboundGlobalStateHandler(clientConnection)); } - pipeline.addLast(globalStateWrapperEvenLoop, "handler", this); + pipeline.addLast(globalStateWrapperEvenLoop, "router", this); pipeline.addLast(globalStateWrapperEvenLoop, "httpInitialHandler", new HttpInitialHandler<>(this)); pipeline.addLast(globalStateWrapperEvenLoop, "respondToClientHandler", new RespondToClientHandler()); } From 32b75728370511c57f1e13deb7bf0d67930d23bb Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Wed, 16 Jan 2019 13:23:01 +0200 Subject: [PATCH 40/43] Handle proxy to client exception --- .../proxy/impl/ProxyToServerConnection.java | 8 ++++++++ .../org/littleshoot/proxy/ServerGroupTest.java | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 99f817869..1b2c025c0 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -266,9 +266,17 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { proxyServer.getPayloadProcessorExecutor() .execute(clientConnection.wrapTask(() -> { + try { respondWith(httpResponse); currentFilters.serverToProxyResponseReceived(); become(AWAITING_INITIAL); + } catch (Exception e) { + if (httpResponse instanceof ReferenceCounted) { + LOG.debug("Retaining reference counted message"); + ((ReferenceCounted) httpResponse).release(); + } + exceptionCaught(ctx, e); + } })); } } diff --git a/src/test/java/org/littleshoot/proxy/ServerGroupTest.java b/src/test/java/org/littleshoot/proxy/ServerGroupTest.java index f76f4fef2..e236e4b67 100644 --- a/src/test/java/org/littleshoot/proxy/ServerGroupTest.java +++ b/src/test/java/org/littleshoot/proxy/ServerGroupTest.java @@ -6,6 +6,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.littleshoot.proxy.impl.DefaultHttpProxyServer; import org.littleshoot.proxy.impl.ThreadPoolConfiguration; @@ -195,6 +196,20 @@ public void testExceptionFirstRequest() throws ExecutionException, InterruptedEx futures.getSecondFuture().get(); } + @Test(expected = ExecutionException.class) + @Ignore // for some reason the test hangs even with original logic + public void testExceptionFirstResponse() throws ExecutionException, InterruptedException { + + final HttpProxyServer proxyServer = getProxy(2, false, + false, false, false, false, + false, true, false, false); + + final Futures futures = runTwoRequests(proxyServer); + + futures.getFirstFuture().get(); + futures.getSecondFuture().get(); + } + @Test public void testBlockFirstRequestSingleProcessingThread() throws ExecutionException, InterruptedException { From 9d700f4482ce6704e07b19a3c7c8f68eafd5a4a6 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Fri, 18 Jan 2019 21:56:44 +0200 Subject: [PATCH 41/43] Fix reference counting --- .../proxy/impl/ClientToProxyConnection.java | 83 ++++++++++--------- .../proxy/impl/ProxyToServerConnection.java | 17 ++-- .../proxy/impl/UpstreamConnectionHandler.java | 19 +++-- 3 files changed, 66 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index 5f25be2cb..ff30b5608 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -353,16 +353,27 @@ protected class ClientToProxyMessageProcessor extends ChannelInboundHandlerAdapt public void channelRead(ChannelHandlerContext ctx, Object msg) { final HttpRequest httpRequest = (HttpRequest) msg; - if (httpRequest instanceof ReferenceCounted) { - LOG.debug("Retaining reference counted message"); - ((ReferenceCounted) msg).retain(); - } - if (ProxyUtils.isChunked(httpRequest)) { process(ctx, httpRequest); } else { + if (httpRequest instanceof ReferenceCounted) { + LOG.debug("Retaining reference counted message"); + ((ReferenceCounted) msg).retain(); + } + proxyServer.getPayloadProcessorExecutor() - .execute(wrapTask(() -> process(ctx, httpRequest))); + .execute(wrapTask(() -> { + try { + process(ctx, httpRequest); + } catch (Exception e) { + ctx.fireExceptionCaught(e); + } finally { + if (httpRequest instanceof ReferenceCounted) { + LOG.debug("Retaining reference counted message"); + ((ReferenceCounted) httpRequest).release(); + } + } + })); } } @@ -370,47 +381,43 @@ private void process(ChannelHandlerContext ctx, HttpRequest httpRequest) { boolean authenticationRequired = false; HttpResponse shortCircuitResponse = null; - try { - authenticationRequired = authenticationRequired(httpRequest); + authenticationRequired = authenticationRequired(httpRequest); - if (authenticationRequired) { - LOG.debug("Not authenticated!!"); - become(AWAITING_PROXY_AUTHENTICATION); - } else { - // Make a copy of the original request - final HttpRequest currentRequest = copy(httpRequest); + if (authenticationRequired) { + LOG.debug("Not authenticated!!"); + become(AWAITING_PROXY_AUTHENTICATION); + } else { + // Make a copy of the original request + final HttpRequest currentRequest = copy(httpRequest); - // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response - // should not be filtered), fall back to the default no-op filter source. - HttpFilters filterInstance; - try { - filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); - } finally { - // releasing a copied http request - if (currentRequest instanceof ReferenceCounted) { - ((ReferenceCounted) currentRequest).release(); - } - } - if (filterInstance != null) { - currentFilters = filterInstance; - } else { - currentFilters = HttpFiltersAdapter.NOOP_FILTER; + // Set up our filters based on the original request. If the HttpFiltersSource returns null (meaning the request/response + // should not be filtered), fall back to the default no-op filter source. + HttpFilters filterInstance; + try { + filterInstance = proxyServer.getFiltersSource().filterRequest(currentRequest, ctx); + } finally { + // releasing a copied http request + if (currentRequest instanceof ReferenceCounted) { + ((ReferenceCounted) currentRequest).release(); } + } + if (filterInstance != null) { + currentFilters = filterInstance; + } else { + currentFilters = HttpFiltersAdapter.NOOP_FILTER; + } - // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required - shortCircuitResponse = currentFilters.clientToProxyRequest(httpRequest); + // Send the request through the clientToProxyRequest filter, and respond with the short-circuit response if required + shortCircuitResponse = currentFilters.clientToProxyRequest(httpRequest); - } - } catch (Exception e) { + } + + if (!authenticationRequired) { if (httpRequest instanceof ReferenceCounted) { LOG.debug("Retaining reference counted message"); - ((ReferenceCounted) httpRequest).release(); + ((ReferenceCounted) httpRequest).retain(); } - ctx.fireExceptionCaught(e); - return; - } - if (!authenticationRequired) { ctx.fireChannelRead(new UpstreamConnectionHandler.Request(httpRequest, shortCircuitResponse)); } } diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index 1b2c025c0..dcc99d673 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -267,15 +267,16 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { proxyServer.getPayloadProcessorExecutor() .execute(clientConnection.wrapTask(() -> { try { - respondWith(httpResponse); - currentFilters.serverToProxyResponseReceived(); - become(AWAITING_INITIAL); + respondWith(httpResponse); + currentFilters.serverToProxyResponseReceived(); + become(AWAITING_INITIAL); } catch (Exception e) { - if (httpResponse instanceof ReferenceCounted) { - LOG.debug("Retaining reference counted message"); - ((ReferenceCounted) httpResponse).release(); - } - exceptionCaught(ctx, e); + exceptionCaught(ctx, e); + } finally { + if (httpResponse instanceof ReferenceCounted) { + LOG.debug("Retaining reference counted message"); + ((ReferenceCounted) httpResponse).release(); + } } })); } diff --git a/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java b/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java index 088459fb3..612384d95 100644 --- a/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java +++ b/src/main/java/org/littleshoot/proxy/impl/UpstreamConnectionHandler.java @@ -1,11 +1,12 @@ package org.littleshoot.proxy.impl; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; +import io.netty.util.ReferenceCountUtil; -public class UpstreamConnectionHandler extends SimpleChannelInboundHandler { +public class UpstreamConnectionHandler extends ChannelInboundHandlerAdapter { private final ClientToProxyConnection clientToProxyConnection; @@ -14,11 +15,15 @@ public class UpstreamConnectionHandler extends SimpleChannelInboundHandler Date: Mon, 4 Feb 2019 14:58:17 +0200 Subject: [PATCH 42/43] Code cleanup --- .../proxy/impl/ClientToProxyConnection.java | 26 ++++++++----------- .../proxy/impl/DefaultHttpProxyServer.java | 14 ++++------ .../proxy/impl/ProxyToServerConnection.java | 2 +- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index ff30b5608..4c030e9b0 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -32,6 +32,7 @@ import org.littleshoot.proxy.DefaultFailureHttpResponseComposer; import org.littleshoot.proxy.ExceptionHandler; import org.littleshoot.proxy.FailureHttpResponseComposer; +import org.littleshoot.proxy.GlobalStateHandler; import org.littleshoot.proxy.ratelimit.RateLimiter; import org.littleshoot.proxy.FlowContext; import org.littleshoot.proxy.FullFlowContext; @@ -48,6 +49,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.RejectedExecutionException; @@ -147,18 +149,17 @@ public class ClientToProxyConnection extends ProxyConnection { final DefaultHttpProxyServer proxyServer, SslEngineSource sslEngineSource, boolean authenticateClients, - ChannelPipeline pipeline, GlobalTrafficShapingHandler globalTrafficShapingHandler, Channel channel) { super(AWAITING_INITIAL, proxyServer, false); this.channel = channel; - initChannelPipeline(pipeline); + initChannelPipeline(channel.pipeline()); if (sslEngineSource != null) { LOG.debug("Enabling encryption of traffic from client to proxy"); - encrypt(pipeline, sslEngineSource.newSslEngine(), + encrypt(channel.pipeline(), sslEngineSource.newSslEngine(), authenticateClients) .addListener( new GenericFutureListener>() { @@ -361,7 +362,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { ((ReferenceCounted) msg).retain(); } - proxyServer.getPayloadProcessorExecutor() + proxyServer.getMessageProcessingExecutor() .execute(wrapTask(() -> { try { process(ctx, httpRequest); @@ -425,18 +426,13 @@ private void process(ChannelHandlerContext ctx, HttpRequest httpRequest) { Runnable wrapTask(Runnable task) { return () -> { - if (proxyServer.getGlobalStateHandler() != null) { - try { - proxyServer.getGlobalStateHandler().restoreFromChannel(channel); - } finally { - try { - task.run(); - } finally { - proxyServer.getGlobalStateHandler().clear(); - } - } - } else { + final Optional globalStateHandler = + Optional.ofNullable(proxyServer.getGlobalStateHandler()); + try { + globalStateHandler.ifPresent(it -> it.restoreFromChannel(channel)); task.run(); + } finally { + globalStateHandler.ifPresent(GlobalStateHandler::clear); } }; } diff --git a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java index 637ff0d7d..65a9a9ce4 100644 --- a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java +++ b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java @@ -120,7 +120,6 @@ public class DefaultHttpProxyServer implements HttpProxyServer { private final ExceptionHandler proxyToServerExHandler; private final RequestTracer requestTracer; private final GlobalStateHandler globalStateHandler; - private final ExecutorService payloadProcessorExecutor; private final HttpFiltersSource filtersSource; private final FailureHttpResponseComposer unrecoverableFailureHttpResponseComposer; private final boolean transparent; @@ -259,7 +258,6 @@ private DefaultHttpProxyServer(ServerGroup serverGroup, ExceptionHandler proxyToServerExHandler, RequestTracer requestTracer, GlobalStateHandler globalStateHandler, - ExecutorService payloadProcessorExecutor, HttpFiltersSource filtersSource, FailureHttpResponseComposer unrecoverableFailureHttpResponseComposer, boolean transparent, @@ -288,7 +286,6 @@ private DefaultHttpProxyServer(ServerGroup serverGroup, this.proxyToServerExHandler = proxyToServerExHandler; this.requestTracer = requestTracer; this.globalStateHandler = globalStateHandler; - this.payloadProcessorExecutor = payloadProcessorExecutor; this.filtersSource = filtersSource; this.unrecoverableFailureHttpResponseComposer = unrecoverableFailureHttpResponseComposer; this.transparent = transparent; @@ -555,7 +552,6 @@ protected void initChannel(Channel ch) throws Exception { DefaultHttpProxyServer.this, sslEngineSource, authenticateSslClients, - ch.pipeline(), globalTrafficShapingHandler, ch); }; @@ -625,7 +621,7 @@ protected GlobalStateHandler getGlobalStateHandler() { return globalStateHandler; } - protected ExecutorService getPayloadProcessorExecutor() { + protected ExecutorService getMessageProcessingExecutor() { return serverGroup.getMessageProcessingExecutor(); } @@ -679,7 +675,7 @@ private static class DefaultHttpProxyServerBootstrap implements HttpProxyServerB private ExceptionHandler proxyToServerExHandler = null; private RequestTracer requestTracer = null; private GlobalStateHandler globalStateHandler = null; - private ExecutorService payloadProcessorExecutor = null; + private ExecutorService messageProcessorExecutor = null; private HttpFiltersSource filtersSource = new HttpFiltersSourceAdapter(); private FailureHttpResponseComposer unrecoverableFailureHttpResponseComposer = new DefaultFailureHttpResponseComposer(); private boolean transparent = false; @@ -908,7 +904,7 @@ public HttpProxyServerBootstrap withCustomGlobalState( @Override public HttpProxyServerBootstrap withMessageProcessingExecutor( ExecutorService messageProcessorExecutor) { - this.payloadProcessorExecutor = messageProcessorExecutor; + this.messageProcessorExecutor = messageProcessorExecutor; return this; } @@ -1028,14 +1024,14 @@ private DefaultHttpProxyServer build() { } else { serverGroup = new ServerGroup(name, clientToProxyAcceptorThreads, - clientToProxyWorkerThreads, proxyToServerWorkerThreads, payloadProcessorExecutor); + clientToProxyWorkerThreads, proxyToServerWorkerThreads, messageProcessorExecutor); } return new DefaultHttpProxyServer(serverGroup, transportProtocol, determineListenAddress(), sslEngineSource, authenticateSslClients, proxyAuthenticator, chainProxyManager, mitmManagerFactory, - clientToProxyExHandler, proxyToServerExHandler, requestTracer, globalStateHandler, payloadProcessorExecutor, + clientToProxyExHandler, proxyToServerExHandler, requestTracer, globalStateHandler, filtersSource, unrecoverableFailureHttpResponseComposer, transparent, idleConnectionTimeout, activityTrackers, connectTimeout, serverResolver, readThrottleBytesPerSecond, writeThrottleBytesPerSecond, diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java index dcc99d673..6a1c0cf02 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java @@ -264,7 +264,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { ((ReferenceCounted) httpResponse).retain(); } - proxyServer.getPayloadProcessorExecutor() + proxyServer.getMessageProcessingExecutor() .execute(clientConnection.wrapTask(() -> { try { respondWith(httpResponse); From ed3d90a91f829451e283fd1a337cc15256e957b9 Mon Sep 17 00:00:00 2001 From: Slava Fomin Date: Mon, 4 Feb 2019 18:23:19 +0200 Subject: [PATCH 43/43] Fix executor service shutdown --- .../littleshoot/proxy/impl/ServerGroup.java | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java index 98ca3eeb3..dbd898305 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java +++ b/src/main/java/org/littleshoot/proxy/impl/ServerGroup.java @@ -74,7 +74,7 @@ public class ServerGroup { */ private final EnumMap protocolThreadPools = new EnumMap(TransportProtocol.class); - private final ExecutorService payloadProcessingExecutor; + private final ExecutorService messageProcessingExecutor; /** * A mapping of selector providers to transport protocols. Avoids special-casing each transport protocol during @@ -110,16 +110,16 @@ public class ServerGroup { */ public ServerGroup(String name, int incomingAcceptorThreads, int incomingWorkerThreads, int outgoingWorkerThreads, - ExecutorService payloadProcessingExecutor) { + ExecutorService messageProcessingExecutor) { this.name = name; this.serverGroupId = serverGroupCount.getAndIncrement(); this.incomingAcceptorThreads = incomingAcceptorThreads; this.incomingWorkerThreads = incomingWorkerThreads; this.outgoingWorkerThreads = outgoingWorkerThreads; - if (payloadProcessingExecutor == null) { - this.payloadProcessingExecutor = Executors.newCachedThreadPool(); + if (messageProcessingExecutor == null) { + this.messageProcessingExecutor = Executors.newCachedThreadPool(); } else { - this.payloadProcessingExecutor = payloadProcessingExecutor; + this.messageProcessingExecutor = messageProcessingExecutor; } } @@ -230,13 +230,7 @@ private void shutdown(boolean graceful) { allEventLoopGroups.addAll(threadPools.getAllEventLoops()); } - payloadProcessingExecutor.shutdown(); - - try { - payloadProcessingExecutor.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - log.warn("Failed to shutdown payload processing executor properly", e); - } + shutdownAndAwaitTermination(messageProcessingExecutor); for (EventLoopGroup group : allEventLoopGroups) { if (graceful) { @@ -261,6 +255,24 @@ private void shutdown(boolean graceful) { log.debug("Done shutting down server group"); } + private void shutdownAndAwaitTermination(ExecutorService pool) { + pool.shutdown(); // Disable new tasks from being submitted + try { + // Wait a while for existing tasks to terminate + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { + pool.shutdownNow(); // Cancel currently executing tasks + // Wait a while for tasks to respond to being cancelled + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) + log.warn("Pool did not terminate"); + } + } catch (InterruptedException ie) { + // (Re-)Cancel if current thread also interrupted + pool.shutdownNow(); + // Preserve interrupt status + Thread.currentThread().interrupt(); + } + } + /** * Retrieves the client-to-proxy acceptor thread pool for the specified protocol. Initializes the pool if it has not * yet been initialized. @@ -301,7 +313,7 @@ public EventLoopGroup getProxyToServerWorkerPoolForTransport(TransportProtocol p } public ExecutorService getMessageProcessingExecutor() { - return payloadProcessingExecutor; + return messageProcessingExecutor; } /**