startAsyncmethod in the
javax.servlet.ServletRequestclass. This returns an instance of the
javax.servlet.AsyncContextinterface which has lifecycle methods such as
complete, gives you a hook back to the request and response, and lets you register an
javax.servlet.AsyncListener. You call the
startmethod passing in a
Runnableto do the asynchronous work. Using this approach frees up server resources instead of blocking, which increases scalability since you can handle more concurrent requests.
async-supportedattribute set to true. And Servlet3AsyncWebXmlProcessor adds
<async-supported>true</async-supported>to all filter declarations in web.xml after it’s generated. So that’s covered for you; there is no required web.xml configuration on your part.
grails.servlet.versionto “3.0? from the default value of “2.5?. Note that there is a legacy setting in application.properties with the name
app.servlet.version; you should delete this line from your application.properties file since its value is ignored and overridden at runtime by the value from BuildConfig.groovy.
startAsyncon the request from a controller though; call
startAsyncdirectly on the controller. This method is added as a controller method (wired in as part of the controllers’ AST transforms from ControllersAsyncApi (by ControllerAsyncTransformer if you’re curious)). It’s important to call the controller’s
startAsyncmethod because it does all of the standard work but also adds Grails integration. This includes adding the logic to integrate all registered PersistenceContextInterceptor instances, e.g. to bind a Hibernate Session to the thread, flush when finished, etc., and also integrates with Sitemesh. This is implemented by returning an instance of
org.apache.catalina.core.AsyncContextImplin Tomcat) for the rest.
StockControlleris very simple. It just has a single action and suspends to look up the current stock price for the requested stock ticker. It does this asynchronously but it’s typically very fast, so you probably won’t see a real difference from the serial approach. But this pattern can be generalized to doing more time-consuming tasks.
ChatManagerclass (registered as a Spring bean in resources.groovy) to handle client registration, message queueing and dispatching, and associated error handling.
ChatManagerthen cycles through each registered
AsyncContextand sends JSONP to the iframe which updates a text area in the main page with incoming messages.
flush()calls on the response writer being ignored. Since we need responsive updates and aren’t rendering a large page of html, I added code to find the real response that’s wrapped by the Grails code and send directly to that.
- All of the client logic is in web-app/js/chat.js
- grails-app/views/chat/index.gsp is the main page; it creates the text area to display messages and the hidden iframe to stay connected and listen for messages
- This requires a servlet container that implements the 3.0 spec. The version of Tomcat provided by the tomcat plugin and used by run-app does, and all 7.x versions of Tomcat do.
- I ran
install-templatesand edited web.xml to add
metadata-complete="true"to keep Tomcat from scanning all jar files for annotated classes – this can cause an OOME due to a bug that’s fixed in version 7.0.26
- Since the chat part is based on older code it uses Prototype but it could easily use jQuery
You can download the sample application code here.