adamfisk/LittleProxy

Handle exceptions in filter implementations

robfletcher opened this issue · 12 comments

If I run into an unrecoverable error in a filter is there a way for me to hand off to an exception handler so that the proxy returns an error response? This is easy enough to do in requestPre and requestPost since I can construct and return my own response but I can't see a way to do it in responsePre or responsePost.

Oh yeah, that would be useful, wouldn't it?

What if I changed the signature of responsePre and responsePost to allow you to return an HttpObject?

That would work for me

I've deployed beta4-SNAPSHOT, so this should be available now.

I'm not sure this is really working for me. I'm most likely to be handling an error when dealing with a LastHttpContent instance and by then I will have processed and returned the initial HttpResponse and potentially some HttpContent instances as well. What happens to them? Have they not already been sent back to the client at that point? Should I instead aggregate everything and return a single FullHttpResponse?

What I'm seeing in one test is a 200 response and in another an exception is thrown inside LittleProxy that I've pasted in below. In both cases the behavior should really be the same as I'm testing two variations of the same condition so there's more than likely something I'm doing incorrectly.

java.lang.ClassCastException: io.netty.handler.codec.http.LastHttpContent$1 cannot be cast to io.netty.handler.codec.http.HttpRequest
    at org.littleshoot.proxy.impl.ClientToProxyConnection.readHTTPInitial(ClientToProxyConnection.java:1)
    at org.littleshoot.proxy.impl.ProxyConnection.readHTTP(ProxyConnection.java:143)
    at org.littleshoot.proxy.impl.ProxyConnection.read(ProxyConnection.java:130)
    at org.littleshoot.proxy.impl.ProxyConnection.channelRead0(ProxyConnection.java:563)
    at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:98)
    at io.netty.channel.DefaultChannelHandlerContext.invokeChannelRead(DefaultChannelHandlerContext.java:337)
    at io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:323)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:253)
    at io.netty.channel.DefaultChannelHandlerContext.invokeChannelRead(DefaultChannelHandlerContext.java:337)
    at io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:323)
    at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86)
    at org.littleshoot.proxy.impl.ProxyConnection$RequestReadMonitor.channelRead(ProxyConnection.java:691)
    at io.netty.channel.DefaultChannelHandlerContext.invokeChannelRead(DefaultChannelHandlerContext.java:337)
    at io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:323)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:173)
    at io.netty.channel.DefaultChannelHandlerContext.invokeChannelRead(DefaultChannelHandlerContext.java:337)
    at io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:323)
    at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86)
    at org.littleshoot.proxy.impl.ProxyConnection$BytesReadMonitor.channelRead(ProxyConnection.java:668)
    at io.netty.chan

Can you tell me a little bit more about your use case please? If you detect an error condition on the LastHttpContent, do you want to have sent anything to the client? If not, then you are correct that you'll need to aggregate everything before filtering. This is already supported in LittleProxy by having your HttpFiltersSource.getMaximumResponseBufferSizeInBytes() method return a positive value. This will cause LittleProxy to decompress and aggregate the response so that by the time it gets to your filter, you're dealing with a FullHttpResponse.

In my case if anything goes wrong during response processing it's catastrophic. All I'd really like is to get a 502 back to say the proxy has experienced an error. Right now the connection aborts.

In that case, I think HttpFiltersSource.getMaximumResponseBufferSizeInBytes() is what you need. Just set that to something bigger than any response that you anticipate. In your filter, you should then receive a FullHttpResponse, which you can then replace with a 502.

@robfletcher Let me know if this gets you what you need. If not, let's figure out what will work.

@oxtoacart Not sure if my request is exactly the same but I did not want to create a duplicate. For the project I'm working on I would like to modify the responses so that I can insert JavaScript into HTML from a server. Would this be possible with this fix? I don't see a way of doing this with the latest release. Let me know If I have to file a separate issue. I could also try to provide a fix if needed, although that would take some time. Thanks!

@alexnederlof Yes, this should work for you.

In your HttpFiltersSource, implement getMaximumResponseBufferSize() and have it return a value large enough to hold your largest responses in memory.

Then, in your HttpFilters implementation, implement responsePre(). If you set a non-zero response buffer size, the HttpObject handed to your responsePre method will be a FullHttpResponse, whose body you can access using the content() method. You can then either manipulate the content ByteBuf directly or better yet, just create a new DefaultFullHttpResponse with your modified content.

Nice! I'll give it a shot. Thanks for the quick reply!

If you're using Selenium, or any browser for that matter, you can just pass the PAC file name into the Selenium config or the browser startup switches and use a basic JS function to ignore the Websockets connections (e.g. URL starts with wss: or ws:) that LittleProxy doesn't support and can cause this issue.