Сервлет-фильтр "прокси", который действует только на ответ от удаленной конечной точки

Мне нужно, чтобы некоторые HTTP-запросы были перенаправлены на веб-приложение/службу загрузки Spring Boot, а приложение-запрос Spring ничего не делает и действует как пересылка между HTTP-клиентом ( другая услуга) и запрос истинного адресата. Но когда ответ возвращается в приложение Spring (от этого адресата), мне нужно приложение Spring, чтобы иметь возможность проверить ответ и, возможно, предпринять какие-либо действия по нему. Итак:

  • HTTP-клиент делает запрос, например, http://someapi.example.com
  • Магия сети направляет запрос в мое приложение Spring, например, http://myproxy.example.com
  • В запросе это приложение/прокси ничего не делает, поэтому запрос перенаправляется на http://someapi.example.com
  • Конечная точка службы в http://someapi.example.com возвращает ответ HTTP на прокси-сервер
  • Прокси-сервер в http://myproxy.example.com проверяет этот ответ и, возможно, отправляет предупреждение перед возвратом ответа исходному клиенту

Итак, по существу, фильтр, который действует как сквозной запрос, и только действительно делает что-либо после выполнения удаленной службы и возвращает ответ.

Моя лучшая попытка до сих пор заключалась в настройке фильтра сервлета:

@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)
    }
}

Можно ли это сделать? Если да, где я могу поместить код, который проверяет и реагирует на ответ? С моим кодом, как это происходит, я получаю исключения, возникающие при попытке нажать контроллер из браузера:

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

Любые идеи?

Ответ 1

В документации API сервлета причина, по которой вы получаете IllegalStateException, связана с тем, что вы пытаетесь вызвать ServletResponse.getWriter после того, как ServletResponse.getOutputStream уже был вызван в ответ. Таким образом, кажется, что метод, который вам нужно вызвать, это ServletResponse.getOutputStream().

Однако, если вы пытаетесь получить доступ к телу ответа, лучшим решением является обернуть ответ в ServletResponseWrapper, чтобы вы могли захватить данные:

public class MyFilter implements Filter
{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {

    }

    @Override
    public void destroy()
    {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 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();
                }
            };
        }
    }
}

Ответ 2

Ответ можно легко манипулировать/заменить/расширять e с помощью фильтра и оболочки ответа.

В фильтре перед вызовом chain.doFilter(request, wrapper) вы создаете PrintWriter для нового содержимого ответа и объекта-обертки.

После вызова chain.doFilter(request, wrapper) выполняется манипуляция с активацией.

Оболочка используется для получения доступа к ответу как String.

Фильтр:

@WebFilter(filterName = "ResponseAnalysisFilter", urlPatterns = { "/ResponseFilterTest/*" })
public class ResponseFilter implements Filter {
    public ResponseFilter() {}

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void destroy() {}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        PrintWriter out = response.getWriter();
        CharResponseWrapper wrapper = new CharResponseWrapper((HttpServletResponse) response);
        chain.doFilter(request, wrapper);

        String oldResponseString = wrapper.toString();

        if (oldResponseString.contains("Fizz")) { 
            // replace something
            String newResponseString = oldResponseString.replaceAll("Fizz", "Cheers");
            // show alert with a javascript appended in the head tag
            newResponseString = newResponseString.replace("</head>", 
               "<script>alert('Found Fizz, replaced with Cheers');</script></head>");

            out.write(newResponseString);
            response.setContentLength(newResponseString.length());
        } 
        else { //not changed
            out.write(oldResponseString);
        }
        // the above if-else block could be replaced with the code you need.
        // for example: sending notification, writing log, etc.

        out.close();
    }
}

Обертка ответа:

public class CharResponseWrapper extends HttpServletResponseWrapper {
    private CharArrayWriter output;

    public String toString() {
        return output.toString();
    }

    public CharResponseWrapper(HttpServletResponse response) {
        super(response);
        output = new CharArrayWriter();
    }

    public PrintWriter getWriter() {
        return new PrintWriter(output);
    }
}

Тестовый сервлет:

@WebServlet("/ResponseFilterTest/*")
public class ResponseFilterTest extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");

        response.getWriter().append(
           "<html><head><title>replaceResponse filter</title></head><body>");

        if (request.getRequestURI().contains("Fizz")) {
            response.getWriter().append("Fizz");
        }
        else {
            response.getWriter().append("Limo");
        }

        response.getWriter().append("</body></html>");
    }
}

Тестовые URL:

Дополнительная информация и примеры об фильтрах:
http://www.oracle.com/technetwork/java/filters-137243.html#72674
http://www.leveluplunch.com/java/tutorials/034-modify-html-response-using-filter/
https://punekaramit.wordpress.com/2010/03/16/intercepting-http-response-using-servlet-filter/