Servlet过滤器"代理"仅作用于来自远程端点

时间:2017-01-18 10:36:07

标签: java spring proxy servlet-filters

我需要将某些HTTP请求重定向到Spring Boot Web应用程序/服务,但在请求方面,Spring应用程序不执行任何操作,并充当HTTP客户端(另一个服务)之间的直通和请求的真实目的地。但是当响应回到Spring应用程序(来自该目的地)时,我需要Spring应用程序能够检查响应,并在需要时可能对其采取措施。所以:

  1. HTTP客户端向http://someapi.example.com
  2. 发出请求
  3. Network magic将请求路由到我的Spring应用程序,例如http://myproxy.example.com
  4. 在请求中,此应用/代理不执行任何操作,因此请求将在http://someapi.example.com上转发
  5. http://someapi.example.com处的服务端点将HTTP响应返回给代理
  6. http://myproxy.example.com处的代理检查此响应,并可能在将响应返回给原始客户端之前发送警报
  7. 基本上,一个过滤器充当请求的传递,并且只有在远程服务执行并返回响应后才真正做任何事情。

    到目前为止,我最好的尝试是设置一个servlet过滤器:

    @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
    

    有什么想法吗?

2 个答案:

答案 0 :(得分:5)

根据Servlet 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();
                }
            };
        }
    }
}

答案 1 :(得分:2)

使用过滤器和响应包装器可以轻松地操作/替换/扩展响应。

在调用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);
    }
}

测试Servlet:

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

测试网址:

有关过滤器的更多信息和示例:
  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/