Java – servlet filter “proxy”, which only acts on the response of the remote endpoint
I need that some HTTP requests must be redirected to the spring boot web application / service, but on the requester side, the spring application does nothing and can also be used as the HTTP client (another service) and the real delivery destination of the request But when the response returns to the spring application (from the destination), I need the spring application to check the response and take action if necessary So:
>HTTP client requests, for example http://someapi.example.com >Network magic passes the request to my spring application, such as http://myproxy.example.com >According to the request, this application / agent does not perform any operation, so the request will be http://someapi.example.com Forward > http://someapi.example.com The service endpoint at returns the HTTP response to the proxy > http://myproxy.example.com The agent at checks this response and may send an alert before returning the response to the original client
So essentially, a filter as a request delivery can do anything and return a response only after the remote service is executed
My best attempt is to set up a servlet filter:
@Override void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException { chain.doFilter(request,response) // How and where do I put my code? if(responseContainsFizz(response)) { // Send an alert (don't worry about this code) } }
Can this be done? If so, where will I check the code and respond? Take my code as an example. When I try to click the controller from the browser, I will receive an exception:
java.lang.IllegalStateException: STREAM at org.eclipse.jetty.server.Response.getWriter(Response.java:910) ~[jetty-server-9.2.16.v20160414.jar:9.2.16.v20160414] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_92] rest of stack trace omitted for brevity
Any ideas?
Solution
According to the servlet API documentation, the reason you get the IllegalStateException is because you are in servletresponse Attempt to call servletresponse after getoutputstream has been called getWriter. So it seems that the method you need to call is servletresponse getOutputStream().
However, if you try to access the body of the response, the best solution is to wrap the response in a servletresponsewrapper so that data can be captured:
public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } @Override public void doFilter(ServletRequest request,FilterChain chain) throws IOException,ServletException { MyServletResponseWrapper responseWrapper = new MyServletResponseWrapper((HttpServletResponse) response); chain.doFilter(request,responseWrapper); if (evaluateResponse(responseWrapper)) { // Send an alert } } private boolean evaluateResponse(MyServletResponseWrapper responseWrapper) throws IOException { String body = responseWrapper.getResponseBodyAsText(); // Perform business logic on the body text return true; } private static class MyServletResponseWrapper extends HttpServletResponseWrapper { private ByteArrayOutputStream copyOutputStream; private ServletOutputStream wrappedOutputStream; public MyServletResponseWrapper(HttpServletResponse response) { super(response); } public String getResponseBodyAsText() throws IOException { String encoding = getResponse().getCharacterEncoding(); return copyOutputStream.toString(encoding); } @Override public ServletOutputStream getOutputStream() throws IOException { if (wrappedOutputStream == null) { wrappedOutputStream = getResponse().getOutputStream(); copyOutputStream = new ByteArrayOutputStream(); } return new ServletOutputStream() { @Override public boolean isReady() { return wrappedOutputStream.isReady(); } @Override public void setWriteListener(WriteListener listener) { wrappedOutputStream.setWriteListener(listener); } @Override public void write(int b) throws IOException { wrappedOutputStream.write(b); copyOutputStream.write(b); } @Override public void close() throws IOException { wrappedOutputStream.close(); copyOutputStream.close(); } }; } } }