Search code examples
asynchronousjettyservlet-3.0ssi

How do I modify proxy responses using asynchronous servlets?


I'm trying to implement an asynchronous servlet, that will receive an end user's request, pass it on to a backend server and grab the response.

So far, this sounds like a pretty standard application of Jetty's ProxyServlet.

Now, the twist is that my backend will respond with include statements (kind of like Server-Side-Includes), which I need to parse, query the backend for and insert at the appropriate place in the end user's response stream.

How would you go about implementing this? I'm specifically curious about parsing and firing off the intermediate requests during the onResponseContent callback, which I understand may or may not contain a full response (and thus may only contain parts of the include statement).

To illustrate, here's what I currently have:

@WebServlet(name = "MyServlet",
        urlPatterns = {"/my/outbounduri/*"},
        initParams = {
                @WebInitParam(name = "proxyTo", value = "/servlet/backend")
        }
)
public class MyHandler extends ProxyServlet {
    @Override
    protected void customizeProxyRequest(HttpServletRequest srequest, 
            HttpServletResponse sresponse ) {
        // add custom headers for the backend
    }

    @Override
    protected void onResponseContent(HttpServletRequest request, 
            HttpServletResponse response, Response proxyResponse, 
            byte[] buffer, int offset, int length, Callback callback) {
        try {
            // instead of passing the content on, we need to catch 
            // include statements
            // response.getOutputStream().write(buffer, offset, length);
            callback.succeeded();
        } catch (Throwable x) {
            callback.failed(x);
        }
    }

    @Override
    protected void onResponseSuccess(HttpServletRequest request, 
            HttpServletResponse response, Response proxyResponse) {
        AsyncContext asyncContext = request.getAsyncContext();
        asyncContext.complete();
    }
}

Solution

  • If you are modifying content during the proxying of that content, then use Jetty 10.x (or newer) and its org.eclipse.jetty.proxy.AsyncMiddleManServlet.

    Note: this is brand new functionality, and there is bound to be some warts in the implementation.

    This is a specialized AsyncProxyServlet designed to ease the complexities of modifying the request content from the client and/or the response content from the remote server. (This is especially complicated when the 2 sides of the proxy have different Transfer-Encoding)

    In your use case, start with overriding AsyncMiddleManServlet.newServerResponseContentTransformer(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse) and have it return a new instance of a ContentTransformer of your design that does the logic you need for this transformation.

    Implement your own ContentTransformer which does what it needs to do in the .transform(ByteBuffer input, boolean finished, List<ByteBuffer> output)

    Read what you can from input, once you have something to write, do a output.add(modified). Pay attention to the finished flag, as that lets you know when the last bit of input content has been received.

    Of other note, if you need to control the differences in the URL from the client request side, to the remote server side, override the String rewriteTarget(HttpServletRequest clientRequest) method, reading the client request information and returning a modified remote URL string.