仅作用于远程端点响应的 Servlet 过滤器"proxy"



我需要将某些HTTP请求重定向到Spring Boot web应用程序/服务,但在请求端,Spring应用程序什么都不做,充当HTTP客户端(另一个服务)和请求的真正目的地之间的传递。但是当响应返回到Spring应用程序(从该目的地)时,我需要Spring应用程序能够检查响应,并可能在需要时对其采取行动

  1. HTTP客户端请求,http://someapi.example.com
  2. Network magic将请求路由到我的Spring应用程序,例如,http://myproxy.example.com
  3. 在请求时,此应用程序/代理不执行任何操作,因此请求在上转发http://someapi.example.com
  4. 位于的服务终结点http://someapi.example.com将HTTP响应返回给代理
  5. 位于的代理http://myproxy.example.com检查此响应,并可能在将响应返回到原始客户端之前发送警报

因此,本质上,一个过滤器充当请求的传递,并且只有在远程服务执行并返回响应后才真正执行

到目前为止,我最好的尝试是设置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

有什么想法吗?

根据Servlet API文档,获得IllegalStateException的原因是因为在响应上已经调用ServletResponse.getOutputStream之后,您正试图调用ServletResponse.getWriter。因此,您需要调用的方法似乎是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();
}
};
}
}
}

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

在调用chain.doFilter(request, wrapper)之前的筛选器中,为新的响应内容和包装器对象准备一个PrintWriter

调用后chain.doFilter(request, wrapper)是实际的响应操作。

包装器用于以字符串的形式访问响应。

过滤器:

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

测试URL:

  • https://yourHost:8181/contextPath/ResponseFilterTest/Fizz(触发器响应替换)
  • https://yourHost:8181/contextPath/ResponseFilterTest/(回复不变)

有关筛选器的详细信息和示例:
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/

最新更新