仅对来自远程端点的响应起作用的 Servlet 过滤器“代理”
Posted
技术标签:
【中文标题】仅对来自远程端点的响应起作用的 Servlet 过滤器“代理”【英文标题】:Servlet filter "proxy" that only acts on response from remote endpoint 【发布时间】:2017-06-02 16:39:12 【问题描述】:我需要某些 HTTP 请求必须重定向到 Spring Boot Web 应用程序/服务,但在请求端,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 的代理检查此响应,并可能在将响应返回给原始客户端之前发送警报
本质上,过滤器充当请求的传递,并且仅在远程服务执行并返回响应之后真正执行任何操作。
到目前为止,我最好的尝试是设置一个 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
有什么想法吗?
【问题讨论】:
Spring AOP Aspect/Pointcut 可以做到这一点(例如:***.com/questions/3310115/…) 感谢@MikeM (+1) - 郑重声明,我不反对 AOP 方法,但仅 如果它是真的不可能使用过滤器来做到这一点!再次感谢! 我会尝试的另一个想法是编写自定义拦截器并使用其afterActionCompletion()
方法。见:docs.spring.io/spring/docs/4.3.x/javadoc-api/org/…
您是否确定了过滤器单独起作用的事实?即不尝试访问来自目的地的响应。
是的,我可以验证我是否正确设置了过滤器,并且如果我在chain.doFilter(...)
之前添加打印/日志语句,它们会按预期执行。
【参考方案1】:
根据 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();
;
【讨论】:
【参考方案2】:可以使用过滤器和响应包装器轻松操作/替换/扩展响应。
在调用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>");
测试网址:
https://yourHost:8181/contextPath/ResponseFilterTest/Fizz(触发响应替换) https://yourHost:8181/contextPath/ResponseFilterTest/(回复不变)有关过滤器的更多信息和示例:http://www.oracle.com/technetwork/java/filters-137243.html#72674http://www.leveluplunch.com/java/tutorials/034-modify-html-response-using-filter/https://punekaramit.wordpress.com/2010/03/16/intercepting-http-response-using-servlet-filter/
【讨论】:
以上是关于仅对来自远程端点的响应起作用的 Servlet 过滤器“代理”的主要内容,如果未能解决你的问题,请参考以下文章
如何使用来自远程服务器的 net.tcp 端点 ping 或检查 WCF 服务的状态?