I am currently trying to configure a REST application to enable Server-side events to be sent to a client using EventSource
. Currently I am running into an IllegalStateException
when attempting to open an asynchronous connection to a REST request which appears to be the result of the server not being configured to allow asynchronous requests.
Some code
@RequireBootstrapWebResource(resource="css/bootstrap.css")
@RequireWebServerExtender
@RequireHttpImplementation
@RequireConfigurerExtender
@Component(name="my.web", property={"debug=true"})
public final class MyApplication implements REST
{
public void getBatching(RESTRequest request)
{
User user = authenticate(request);
HttpServletResponse response = request._response();
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("text/event-stream");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
try
{
AsyncContext async = (AsyncContext)request._request().startAsync();
async.addListener(new InternalAsyncListener());
sseConnectionMap.put(UUID.randomUUID(), async);
response.flushBuffer();
}
catch (Exception e)
{
System.out.println("Message=" + e.getMessage());
e.printStackTrace();
}
}
@SuppressWarnings("unused")
@Activate
void activate(ComponentContext cc, BundleContext bc, Map<String, Object> config)
{
System.out.println(this.getClass() + " started");
if ("true".equals(config.get("debug")))
{
debug = true;
}
System.out.println("Link Start");
}
}
Currently that prints the following:
Message=null
java.lang.IllegalStateException
at org.apache.felix.http.base.internal.dispatch.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:314)
at my.application.MyApplication.getBatching(WebApplication.java:399)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at osgi.enroute.rest.simple.provider.Function.invoke(Function.java:203)
at osgi.enroute.rest.simple.provider.RestMapper.execute(RestMapper.java:333)
at osgi.enroute.rest.simple.provider.RestServlet.service(RestServlet.java:66)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.apache.felix.http.base.internal.handler.ServletHandler.handle(ServletHandler.java:85)
at org.apache.felix.http.base.internal.dispatch.InvocationChain.doFilter(InvocationChain.java:79)
at my.application.CORSFilter.doFilter(CORSFilter.java:75)
at org.apache.felix.http.base.internal.handler.FilterHandler.handle(FilterHandler.java:135)
at org.apache.felix.http.base.internal.dispatch.InvocationChain.doFilter(InvocationChain.java:74)
at org.apache.felix.http.base.internal.dispatch.Dispatcher.dispatch(Dispatcher.java:124)
at org.apache.felix.http.base.internal.DispatcherServlet.service(DispatcherServlet.java:61)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:845)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:583)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:224)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1160)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:511)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1092)
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.HandlerWrapper.handle(HandlerWrapper.java:134)
at org.eclipse.jetty.server.Server.handle(Server.java:518)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:308)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:244)
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$2.run(SelectChannelEndPoint.java:93)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceAndRun(ExecuteProduceConsume.java:246)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:156)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:654)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:572)
at java.lang.Thread.run(Unknown Source)
Where authenticate()
currently modifies headers when in debug mode to allow for CORS while developing the application and attempts to obtain a User
from a token passed in the Authorization
header... however even if I put the startAsync()
line above that I still get the same IllegalStateException
so I don't think it's an issue of header modification.
Some googling has brought up that the ISE on that particular line ServletRequestWrapper.java:314
indicates that the underlying Felix/Jetty server is not set up to allow asynchronous requests.
I also tried to simply set the header to text/event-stream
and continue to write to the response but of course it was closed upon exiting the REST method.
Further investigation has provided other solutions, including running a second servlet which is configured for handling asynchronous requests and binding that to a separate URL pattern. That is, of course, it's own effort, and I'm unsure of how well multiple servlets will play together. Preferably I would like to be able to just open asynchronous responses from the one Enroute application.
If it is possible to configure the enroute application to allow asynchronous responses, what is the setting I am messing?