在 servlet 请求上调用 startAsync() 会在由 Jetty 9 的 ProxyServlet 终止的过滤器链中抛出 IllegalStateException
Calling startAsync() on a servlet request throws IllegalStateException in a filter chain terminated by Jetty 9's ProxyServlet
环境:
- 在我的环境中,Kibana 4.5.2 运行 位于 "reverse proxy servlet" 之后,它是通过扩展 Jetty 的 "ProxyServlet" class.
创建的
- 这样做是为了可以使用 URL https://Jetty_Server_IP:8443/visual-analytics/proxy/... Requests for this URL are intercepted by the reverse proxy running in the Jetty Server and redirected to https://localhost:5601/... 访问 Kibana Web 界面,即与 运行 在同一台机器上的 Kibana 服务器Jetty 服务器。
- Kibana 服务器然后处理由 Jetty 服务器转发的请求,并returns响应返回到 Web 浏览器。
- 注意: Jetty 在我的应用程序中 "embedded mode" 运行。
问题:
将"reverse proxy servlet"映射到URL“/visual-analytics/proxy/*”。
还有另一个 "filter" 映射到 URL “/visual-analytics/proxy/elasticsearch/.kibana/search/*”,其中 ContinuationListener 正在按指示使用通过以下代码片段:
ContinuationSupport.getContinuation(myRequestWrapper).addContinuationListener(new ContinuationListener() {
@Override
public void onTimeout(Continuation continuation) {
logger.log(Level.WARNING, "Request timeout...");
}
@Override
public void onComplete(Continuation continuation) {
HttpServletResponse httpResponse = (HttpServletResponse)continuation.getServletResponse();
if (httpResponse.getStatus() == HttpServletResponse.SC_OK || httpResponse.getStatus() == HttpServletResponse.SC_CREATED ) {
//some business logic
}
}
});
chain.doFilter(myRequestWrapper, response);
上面的 ContinuationListener 在 Jetty 版本 8.1.15.v20140411 下运行良好,并且正在调用侦听器的 onComplete() 方法。但是在将 Jetty 版本升级到 9.3.14.v20161028 后,ContinuationListener 不再工作,即既没有调用监听器的 onComplete() 方法,也没有调用 onTimeout() 方法。
由于 ContinuationListener 不再工作,我将其替换为 "javax.servlet.AsyncListener" 实现,如以下代码片段所示:
AsyncContext asyncContext = myRequestWrapper.startAsync();
asyncContext.addListener(new AsyncListener() {
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
logger.log(Level.WARNING, "Async timeout...");
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
logger.log(Level.INFO, "Async start...");
}
@Override
public void onError(AsyncEvent event) throws IOException
{
logger.log(Level.SEVERE, "Async error...");
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse();
//HttpServletResponse httpResponse = (HttpServletResponse) event.getAsyncContext().getResponse();
if (httpResponse.getStatus() == HttpServletResponse.SC_OK || httpResponse.getStatus() == HttpServletResponse.SC_CREATED ) {
//some business logic
}
}
}, myRequestWrapper, httpServletResponse);
chain.doFilter(myRequestWrapper, response);
但是添加上面的 "AsyncListener" 实现导致以下 "IllegalStateException":
https://164.99.175.139:8443/visual-analytics/proxy/elasticsearch/.kibana/search/test1?op_type=create
java.lang.IllegalStateException: s=DISPATCHED i=true a=STARTED
at org.eclipse.jetty.server.HttpChannelState.startAsync(HttpChannelState.java:264)
at org.eclipse.jetty.server.Request.startAsync(Request.java:2235)
at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:432)
at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:432)
at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:432)
at org.eclipse.jetty.proxy.ProxyServlet.service(ProxyServlet.java:88)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:845)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1712)
at com.netiq.sentinel.kibana.search.KibanaSearchFilter.doFilter(KibanaSearchFilter.java:249)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1699)
at com.netiq.sentinel.kibana.proxy.AcceptEncodingHeaderModificationFilter.doFilter(AcceptEncodingHeaderModificationFilter.java:37)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1699)
at com.netiq.sentinel.kibana.proxy.SecurityFilter.doFilter(SecurityFilter.java:41)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1699)
at com.netiq.sentinel.kibana.proxy.AuditFilter.doFilter(AuditFilter.java:104)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1699)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:582)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1180)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:512)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1112)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:213)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:119)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
at org.eclipse.jetty.server.Server.handle(Server.java:534)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:320)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:273)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95)
at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:202)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:273)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95)
at org.eclipse.jetty.io.SelectChannelEndPoint.run(SelectChannelEndPoint.java:93)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.executeProduceConsume(ExecuteProduceConsume.java:303)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceConsume(ExecuteProduceConsume.java:148)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:136)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:671)
at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:589)
at java.lang.Thread.run(Thread.java:745)
上述堆栈跟踪中提到的 "ProxyServlet.java" 中的第 88 行具有以下代码:
final AsyncContext asyncContext = request.startAsync();
抛出 "IllegalStateException" 是因为 startAsync() 方法在同一个请求中被调用两次 - 第一次是在我的过滤器中,第二次是在 Jetty ProxyServlet 的 service() 方法中?如果是,是否无法在由 Jetty 的 ProxyServlet 终止的过滤器链中使用 "AsyncListener"?
任何关于如何进一步进行的指示将不胜感激。期待任何回应...
仅供参考,我按如下方式解决了这个问题:
- 我没有使用 Jetty 的 "ContinuationListener",而是使用了 Servlet 3.0 的 "AsyncListener",因为 Jetty 的 "ContinuationListener" 在升级到 Jetty 9.3 后似乎不起作用。 14.v20161028。这可能是 Jetty 错误。
- 侦听器相关代码段位于过滤器的“chain.doFilter()”调用之前。我将此代码段移至过滤器的 "chain.doFilter()" 调用之后的位置,以便我可以在 Jetty ProxyServlet 的 service() 方法中检索 "already created" 的 AsyncContext。然后我可以将 AsyncListener 添加到检索到的 AsyncContext。
- 我将过滤器中的“request.startAsync()”调用更改为“request.getAsyncContext()”,这样我就不会启动导致 IllegalStateException 的新 AsyncContext,而只会检索已在 Jetty 的 ProxyServlet 中创建的 AsyncContext。
因此更新后的代码段如下所示:
chain.doFilter(myRequestWrapper, response);
AsyncContext asyncContext = myRequestWrapper.getAsyncContext();
asyncContext.addListener(new AsyncListener() {
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
logger.log(Level.WARNING, "Async timeout...");
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
logger.log(Level.INFO, "Async start...");
}
@Override
public void onError(AsyncEvent event) throws IOException
{
logger.log(Level.SEVERE, "Async error...");
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse();
//HttpServletResponse httpResponse = (HttpServletResponse) event.getAsyncContext().getResponse();
if (httpResponse.getStatus() == HttpServletResponse.SC_OK || httpResponse.getStatus() == HttpServletResponse.SC_CREATED ) {
//some business logic
}
}
}, myRequestWrapper, httpServletResponse);
环境:
- 在我的环境中,Kibana 4.5.2 运行 位于 "reverse proxy servlet" 之后,它是通过扩展 Jetty 的 "ProxyServlet" class. 创建的
- 这样做是为了可以使用 URL https://Jetty_Server_IP:8443/visual-analytics/proxy/... Requests for this URL are intercepted by the reverse proxy running in the Jetty Server and redirected to https://localhost:5601/... 访问 Kibana Web 界面,即与 运行 在同一台机器上的 Kibana 服务器Jetty 服务器。
- Kibana 服务器然后处理由 Jetty 服务器转发的请求,并returns响应返回到 Web 浏览器。
- 注意: Jetty 在我的应用程序中 "embedded mode" 运行。
问题:
将"reverse proxy servlet"映射到URL“/visual-analytics/proxy/*”。
还有另一个 "filter" 映射到 URL “/visual-analytics/proxy/elasticsearch/.kibana/search/*”,其中 ContinuationListener 正在按指示使用通过以下代码片段:
ContinuationSupport.getContinuation(myRequestWrapper).addContinuationListener(new ContinuationListener() {
@Override
public void onTimeout(Continuation continuation) {
logger.log(Level.WARNING, "Request timeout...");
}
@Override
public void onComplete(Continuation continuation) {
HttpServletResponse httpResponse = (HttpServletResponse)continuation.getServletResponse();
if (httpResponse.getStatus() == HttpServletResponse.SC_OK || httpResponse.getStatus() == HttpServletResponse.SC_CREATED ) {
//some business logic
}
}
});
chain.doFilter(myRequestWrapper, response);
上面的 ContinuationListener 在 Jetty 版本 8.1.15.v20140411 下运行良好,并且正在调用侦听器的 onComplete() 方法。但是在将 Jetty 版本升级到 9.3.14.v20161028 后,ContinuationListener 不再工作,即既没有调用监听器的 onComplete() 方法,也没有调用 onTimeout() 方法。
由于 ContinuationListener 不再工作,我将其替换为 "javax.servlet.AsyncListener" 实现,如以下代码片段所示:
AsyncContext asyncContext = myRequestWrapper.startAsync();
asyncContext.addListener(new AsyncListener() {
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
logger.log(Level.WARNING, "Async timeout...");
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
logger.log(Level.INFO, "Async start...");
}
@Override
public void onError(AsyncEvent event) throws IOException
{
logger.log(Level.SEVERE, "Async error...");
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse();
//HttpServletResponse httpResponse = (HttpServletResponse) event.getAsyncContext().getResponse();
if (httpResponse.getStatus() == HttpServletResponse.SC_OK || httpResponse.getStatus() == HttpServletResponse.SC_CREATED ) {
//some business logic
}
}
}, myRequestWrapper, httpServletResponse);
chain.doFilter(myRequestWrapper, response);
但是添加上面的 "AsyncListener" 实现导致以下 "IllegalStateException":
https://164.99.175.139:8443/visual-analytics/proxy/elasticsearch/.kibana/search/test1?op_type=create java.lang.IllegalStateException: s=DISPATCHED i=true a=STARTED at org.eclipse.jetty.server.HttpChannelState.startAsync(HttpChannelState.java:264) at org.eclipse.jetty.server.Request.startAsync(Request.java:2235) at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:432) at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:432) at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:432) at org.eclipse.jetty.proxy.ProxyServlet.service(ProxyServlet.java:88) at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:845) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1712) at com.netiq.sentinel.kibana.search.KibanaSearchFilter.doFilter(KibanaSearchFilter.java:249) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1699) at com.netiq.sentinel.kibana.proxy.AcceptEncodingHeaderModificationFilter.doFilter(AcceptEncodingHeaderModificationFilter.java:37) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1699) at com.netiq.sentinel.kibana.proxy.SecurityFilter.doFilter(SecurityFilter.java:41) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1699) at com.netiq.sentinel.kibana.proxy.AuditFilter.doFilter(AuditFilter.java:104) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1699) at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:582) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143) at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548) at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226) at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1180) at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:512) at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185) at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1112) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:213) at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:119) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134) at org.eclipse.jetty.server.Server.handle(Server.java:534) at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:320) at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251) at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:273) at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95) at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:202) at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:273) at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95) at org.eclipse.jetty.io.SelectChannelEndPoint.run(SelectChannelEndPoint.java:93) at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.executeProduceConsume(ExecuteProduceConsume.java:303) at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceConsume(ExecuteProduceConsume.java:148) at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:136) at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:671) at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:589) at java.lang.Thread.run(Thread.java:745)
上述堆栈跟踪中提到的 "ProxyServlet.java" 中的第 88 行具有以下代码:
final AsyncContext asyncContext = request.startAsync();
抛出 "IllegalStateException" 是因为 startAsync() 方法在同一个请求中被调用两次 - 第一次是在我的过滤器中,第二次是在 Jetty ProxyServlet 的 service() 方法中?如果是,是否无法在由 Jetty 的 ProxyServlet 终止的过滤器链中使用 "AsyncListener"?
任何关于如何进一步进行的指示将不胜感激。期待任何回应...
仅供参考,我按如下方式解决了这个问题:
- 我没有使用 Jetty 的 "ContinuationListener",而是使用了 Servlet 3.0 的 "AsyncListener",因为 Jetty 的 "ContinuationListener" 在升级到 Jetty 9.3 后似乎不起作用。 14.v20161028。这可能是 Jetty 错误。
- 侦听器相关代码段位于过滤器的“chain.doFilter()”调用之前。我将此代码段移至过滤器的 "chain.doFilter()" 调用之后的位置,以便我可以在 Jetty ProxyServlet 的 service() 方法中检索 "already created" 的 AsyncContext。然后我可以将 AsyncListener 添加到检索到的 AsyncContext。
- 我将过滤器中的“request.startAsync()”调用更改为“request.getAsyncContext()”,这样我就不会启动导致 IllegalStateException 的新 AsyncContext,而只会检索已在 Jetty 的 ProxyServlet 中创建的 AsyncContext。
因此更新后的代码段如下所示:
chain.doFilter(myRequestWrapper, response);
AsyncContext asyncContext = myRequestWrapper.getAsyncContext();
asyncContext.addListener(new AsyncListener() {
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
logger.log(Level.WARNING, "Async timeout...");
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
logger.log(Level.INFO, "Async start...");
}
@Override
public void onError(AsyncEvent event) throws IOException
{
logger.log(Level.SEVERE, "Async error...");
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse();
//HttpServletResponse httpResponse = (HttpServletResponse) event.getAsyncContext().getResponse();
if (httpResponse.getStatus() == HttpServletResponse.SC_OK || httpResponse.getStatus() == HttpServletResponse.SC_CREATED ) {
//some business logic
}
}
}, myRequestWrapper, httpServletResponse);