1 2 Previous Next

swchan2

19 posts
Server-Sent Events (SSE) is part of HTML5. SSE is a simple, undirectional communication from server to browser. It allows server to push data to client once a connection is established. The entire point of SSE is to make it easy for the server to push messages to the browser, once the browser has first established a connection to the server. These messages are of the form "field: value" with each message separated by a newline. The field in the messages could be one of the following: event,data, id, retry. On the client side JavaScript, the EventSource interface is used to process SSE. And it is supported by most of the popular browsers. In this blog entry, we will illustrate how to use SSE with Servlets by example. In this first example, we will have a very simple Servlet sending an SSE. In the second example, we will show a Servlet sending SSE using the Servlet 3.0 Async API and Java EE 7 Concurrency Utilities. 

Example 1

We will look at a very simple Servlet sending a SSE. There are two steps: 
  1. set the content type to text/event-stream
  2. write the SSE data (remember the last new line!)
The code is as follows. Please see comments inside the code.@WebServlet(urlPatterns={"/simplesse"}) public class SSEEchoServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { // set content type res.setContentType("text/event-stream"); res.setCharacterEncoding("UTF-8"); String msg = req.getParameter("msg"); PrintWriter writer = res.getWriter(); // send SSE writer.write("data: " + msg + "\n\n"); } } The client side JavaScript will setup aEventSource as follows: <html> <body> <script> function setupEventSource() { var output = document.getElementById("output"); if (typeof(EventSource) !== "undefined") { var msg = document.getElementById("textID").value; var source = new EventSource("simplesse?msg=" + msg); source.onmessage = function(event) { output.innerHTML += event.data + "<br>"; }; } else { output.innerHTML = "Sorry, Server-Sent Event is not supported in your browser"; } return false; } </script> <h2>Simple SSE Echo Demo</h2> <div> <input type="text" id="textID" name="message" value="Hello World"> <input type="button" id="sendID" value="Send" onclick="setupEventSource()"/> </div> <hr/> <div id="output"></div> </body> </html> In this example, the client will reconnect automatically. 

Example 2

It is very common for server to push data to the client asynchronously. In this example, Servlet Async API and Java EE concurrent Utitlities are used to push data asynchronously.// enable async in the servlet @WebServlet(urlPatterns={"/sseasync"}, asyncSupported=true) public class SSEAsyncServlet extends HttpServlet { @Resource private ManagedExecutorService managedExecutorService; @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { // set content type res.setContentType("text/event-stream"); res.setCharacterEncoding("UTF-8"); final String msg = req.getParameter("msg"); // start async final AsyncContext ac = req.startAsync(); final PrintWriter writer = res.getWriter(); Runnable runnable = new Runnable() { @Override public void run() { // echo msg 5 times for (int i = 0; i < 5; i++) { if (i == 4) { // last // SSE event field writer.write("event: close\n"); } // SSE data field // last field with blank new line writer.write("data: " + msg + "\n\n"); writer.flush(); try { Thread.sleep(2000); } catch(InterruptedException iex) { iex.printStackTrace(); } } // complete async ac.complete(); } }; // submit the runnable to managedExecutorService managedExecutorService.submit(runnable); } } In above example, the last SSE has a eventname, close. This information is used on the client side. A special handler is registered for the event with nameclose. The JavaScript is as follows:<html> <body> <script> function setupEventSource() { var output = document.getElementById("output"); if (typeof(EventSource) !== "undefined") { var msg = document.getElementById("textID").value; var source = new EventSource("sseasync?msg=" + msg); source.onmessage = function(event) { output.innerHTML += event.data + "<br>"; }; source.addEventListener('close', function(event) { output.innerHTML += event.data + "<hr/>"; source.close(); }, false); } else { output.innerHTML = "Sorry, Server-Sent Events are not supported in your browser"; } return false; } </script> <h2>SSE Echo Demo</h2> <div> <input type="text" id="textID" name="message" value="Hello World"> <input type="button" id="sendID" value="Send" onclick="setupEventSource()"/> </div> <hr/> <div id="output"></div> </body> </html> Note that source.close() is invoked so that the client will not reconnect to server after close event.  
Servlet 3.1 (JSR 340) is final and is part of Java EE 7. I and Rajiv had presented the session CON 4854, "What's New in JSR 340, Servlet 3.1?", on September 23 at JavaOne 2013, San Francisco. It was at 3:00pm. Most of the audiences are quite familiar with Servlet technology and we had a good Q&A during the talk. I have attached the pdf of the presentation so that it can shared with wider audiences.  
javax.servlet.http.HttpSession provides a way to identify an user across multiple HTTP requests and to store user specified information. In other words, it provides a support of stateful communications with the stateless HTTP protocol. For security and memory management, sessions need to be invalidated at a certain time. There are two related methods inHttpSession

HttpSession.invalidate()

By invoking invalidate(), the session will be invalidated immediately. This is useful for the case such as logout. 

HttpSession.setMaxInactiveInterval(int interval)

The method setMaxInactiveInterval(int interval) allows us to configure the time (in seconds) between client requestsbefore the servlet container will invalidate the session. That is, an idle session will be invalidated afterthe specified time. In GlassFish 4.0 (and earlier versions), there is a reaper thread for cleaning invalidated HttpSession periodically with a specific reap interval (reapInterval). Let rtpt(s) be the time interval between the reaper process starts and the given sessions is processed. Then we have the following inequalities: maxInactiveInterval + rtpt(s) <= invalidatedTime(s) <= maxInactiveInterval + reapInterval + rtpt(s) In general, rtpt(s) is small. We would like to configure the reap interval. By default, the reap interval (reapInterval) is 60 seconds and it can be configured as follows: 
  • in server level: asadmin set configs.config.server-config.web-container.session-config.session-manager.manager-properties.reap-interval-in-seconds=10
  • in application level: We can specify a configuration inglassfish-web.xml as follows: <!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd"> <glassfish-web-app> <session-config> <session-manager> <manager-properties> <property name="reapIntervalSeconds" value="10"/> </manager-properties> </session-manager> </session-config> </glassfish-web-app>
swchan2

Broadcasting in WebSocket Blog

Posted by swchan2 Aug 13, 2013
Java API for WebSocket is a new JSR to Java EE 7. It provides a stardard Java API for creating WebSocket applications. This gives web applications the ability to push data. In Java EE 6 samples, we added a chat room sample to illustrate how to use of Servlet 3.0 asynchronous operation. In that sample, the servlet code did the following: 
  • keep track of AsyncContext in aQueue
  • create a thread to process the message
  • has code to workaround browser specific issue
In this blog, I will rewrite the sample using broadcasting API in WebSocket. Also, a "logout" feature is added to the sample. The Java code for WebSocket server endpoint is as follow:@ServerEndpoint(value="/chat") public class ChatServer { @OnMessage public void chat(String chatMsg, Session session) throws IOException { String message = ""; boolean isLogout = false; String user = getUser(chatMsg); if (chatMsg != null && chatMsg.length() >= 2) { char first = chatMsg.charAt(0); char second = chatMsg.charAt(1); if (second == ':') { switch(first) { case 'i': message = "System Message:" + user + " has joined."; break; case 'o': isLogout = true; message = "System Message:" + user + " has left."; break; default: // 'c' message = chatMsg.substring(2); break; } } } for (Session s : session.getOpenSessions()) { if (s.isOpen()) { send(s, message); } } if (isLogout) { session.close(new CloseReason( CloseReason.CloseCodes.NORMAL_CLOSURE, user + " leaves")); } } private void send(Session session, String message) { try { session.getBasicRemote().sendObject(message); } catch(Exception ex) { System.err.println("Error in sending message to " + session + ": " + ex); } } private String getUser(String chatMsg) { String user = ""; if (chatMsg != null && chatMsg.length() > 1 && chatMsg.charAt(1) == ':') { String content = chatMsg.substring(2); int ind = content.indexOf(':'); user = ((ind > 0) ? content.substring(0, ind) : content); } return user; } } The annotation @ServerEndpoint indicates that the class is a WebSocket server endpoint and@OnMessage indicates that the method chatwill be invoked when a message arrived. Thesession.getOpenSessions() returns all the open sessions for the given WebSocket at that moment. This allows us to do send messages to others. Note that when we decide to do any operations on returned open sessions, we need to checks.isOpen() as the session s may be closed after session.getOpenSessions() is called. Note that there is no workaround for browser issue in the Java code. With the new HTML5 JavaScript API, we simplify the corresponding JavaScript from about 2000 lines to less than 150 lines. You can download the maven project here and run it on GlassFish 4.0.  

WebSocket is a bi-directional, full-duplex, TCP based messaging protocol. It is originally proposed as part of HTML5 and is a IETF-defined Protocol (RFC 6455). And W3C has defined JavaScript API for WebSocket which is in candidate recommendation since 2012-09-20. JSR 356: Java API for WebSocket provides a standard Java API for creating WebSocket Applications. The specification provides an API for creating client and server endpoints. The reference implementation is integrated and available in GlassFish 4.0. By default, a new instance of server endpoint is instantiated for each client connection (section 3.1.7 of JSR 356 specification). In other words, by default, WebSocket is "Single Thread Model". This is different from the default in Servlet (cf. javax.servlet.SingleThreadModel). If we want to use a single instance of server endpoint for all client connections, then we can override the ServerEndpointConfig.Configurator#getEndpointInstance method. The following sample code illustrate how to achieve this. First, we define a WebSocket endpoint with a custom configurator. import javax.websocket.OnMessage; import javax.websocket.server.ServerEndpoint; @ServerEndpoint(value="/secho", configurator=SEchoConfigurator.class) public class SEchoServerEndpoint { @OnMessage public String echo(String message) { return message; } } Second, we define a ServerEndpointConfig.Configurator and override the getEndpointInstance method. import javax.websocket.server.ServerEndpointConfig; public class SEchoConfigurator extends ServerEndpointConfig.Configurator { private static SEchoServerEndpoint theSEchoServerEndpoint = new SEchoServerEndpoint(); @Override public T getEndpointInstance(Class endpointClass) throws InstantiationException { if (SEchoServerEndpoint.class.equals(endpointClass)) { return (T)theSEchoServerEndpoint; } else { throw new InstantiationException(); } } }

Expression Language (EL) was first introduced as part of JSTL 1.0, was then moved JSP 2.0 and was unified with JSF 1.2 in JSP 2.1. In Java EE 7, EL is a new separate JSR, JSR 341. Many new features are introduced in EL 3.0. This blog shows how to use new following new features of EL 3.0:

  • Standalone environment
  • Lambda expression (section 1.20 of EL 3.0 spec)
  • The new operator ;to separate statements
  • Retrieval of a Stream from a Collection(section 2.3.1 of EL 3.0 spec)
  • The use of a static field and method, for instance, Math.sqrt

This blog also shows how to use new operations to calculate a standard deviationin the following different ways:

  • Using count, sum and map
  • Using forEach
  • Using reduce (see section 2.3.17 of EL 3.0 spec)

I would like to thank Kinman Chung, the EL specification lead, for useful discussion. The following example shows some of the new EL features. public class ELTestServlet extends HttpServlet { @Override public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { List list = new ArrayList<>(); list.add(Double.valueOf(1.0)); list.add(Double.valueOf(2.0)); list.add(Double.valueOf(3.0)); ELProcessor elp = new ELProcessor(); elp.defineBean("data", list); Object message = (Double)elp.eval( "n = data.stream().count(); s = data.stream().sum(); sq = data.stream().map(i -> i*i).sum(); Math.sqrt(sq/n - Math.pow(s/n, 2))"); res.getWriter().println(message); message = (Double)elp.eval( "n = 0; s = 0; sq = 0; data.stream().forEach(d -> (n = n + 1; s = s + d; sq = sq + d*d)); Math.sqrt(sq/n - Math.pow(s/n, 2))"); res.getWriter().println(message); message = (Double)elp.eval( "n = 0; s = 0; sq = data.stream().reduce(0, (a, i) -> (n = n + 1; s = s + i; a + i*i)); Math.sqrt(sq/n - Math.pow(s/n, 2))"); res.getWriter().println(message); } } We will use EL in a servlet as a standalone environment. In this case, an ELProcessor is created and beans are defined as follows: ELProcessor elp = new ELProcessor(); elp.defineBean("data", list); An EL can be evaluated by using elp.eval(...).

EL Example 1: Using count, sum and map

n = data.stream().count(); s = data.stream().sum(); sq = data.stream().map(i -> i*i).sum(); Math.sqrt(sq/n - Math.pow(s/n, 2)) For a given collection, stream() allows access to the corresponding Stream. Then count() and sum() can be called on the Stream to count the number of elements and compute the sum of elements in the associated collection. For data.stream().map(i -> i*i), a Lambda expression is used. In this case, the expression changes each value in the Streamto its square.

EL Example 2: Using forEach

n = 0; s = 0; sq = 0; data.stream().forEach(d -> (n = n + 1; s = s + d; sq = sq + d*d)); Math.sqrt(sq/n - Math.pow(s/n, 2)) This EL uses forEach for a stream. The count, sum and sum of squares are computed at the same time. Note that the variables n, s and sq are defined in EL Example 1. We need to reset them to initial values in the given ELProcessor.

EL Example 3: Using reduce

n = 0; s = 0; sq = data.stream().reduce(0, (a, i) -> (n = n + 1; s = s + i; a + i*i)); Math.sqrt(sq/n - Math.pow(s/n, 2)) In this example, reduce is used to compute the count, sum and sum of squares. In the second line, the first argument of reduce is the initial value of the seed variable a. The result of the expression is assigned back to the variable sq for subsequent computation. We can learn more about EL 3.0 by looking at the specification in JSR 341. EL 3.0 is available at java.net and is part of GlassFish 4.0.

Asynchronous operation was introduced in Servlet 3.0. ServletRequest#startAsync is used to put the request into asynchronous mode. A thread need to be created implicitly or explicitly (see here for an example). Servlet 3.1, JSR 340 includes clarifications in asynchronous area. Besides Servlet 3,1, Concurreny Utilities for Java EE 1.0, JSR 236 is introduced in Java EE 7. JSR 236 provides a portable way to access managed thread pools and ExecutorService in a Java EE container. In this blog, I will illustrate how to achieve this in a servlet asynchronous environment. 

javax.enterprise.concurrent.ManagedThreadFactory#newThread

A ManagedThreadFactory can be accessed through JNDI resource lookup, which can be achieved through the@Resource annotation. A thread can be created from the factory as usual. @WebServlet(urlPatterns="/test", asyncSupported=true) public class TestAsyncMTFServlet extends HttpServlet { @Resource private ManagedThreadFactory managedThreadFactory; @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { final AsyncContext asyncContext = req.startAsync(); final PrintWriter writer = res.getWriter(); Runnable runnable = new Runnable() { @Override public void run() { writer.println("Done"); asyncContext.complete(); } }; Thread thread = managedThreadFactory.newThread(runnable); thread.start(); } } 

javax.enterprise.concurrent.ManagedExecutorService#submit, javax.enterprise.concurrent.ManagedScheduledExecutorService#schedule

Instead of accessing thread factory directly,ManagedExecutorService andManagedScheduledExecutorService are also available through JNDI resource lookup. The following example illustrates the usage of the former. @WebServlet(urlPatterns="/test2", asyncSupported=true) public class TestAsyncMESServlet extends HttpServlet { @Resource private ManagedExecutorService managedExecutorService; @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { final AsyncContext asyncContext = req.startAsync(); final PrintWriter writer = res.getWriter(); Runnable runnable = new Runnable() { @Override public void run() { writer.println("Done"); asyncContext.complete(); } }; managedExecutorService.submit(runnable); } } Servlet 3.1 and Concurrency Utilities for Java EE 1.0 are available in GlassFish 4.0.  

Update: In Servlet 3.0, the behavior of using response is undefined after invoking #complete or #dispatch. In Servlet 3.1, it is clarified that AsyncContext#getResponse will throw IllegalStateException. The blog has been updated for this.

Asynchronous operation is supported in Servlet 3.0. I have discussed startAsync in my previous blog, startAsync in Servlet 3.0. In this blog, I will discussAsyncContext#complete. The javadoc of AsyncContext#complete has the following:

Completes the asynchronous operation that was started on the request that was used to initialize this AsyncContext, closing the response that was used to initialize this AsyncContext. ... If this method is called before the container-initiated dispatch that called startAsync has returned to the container, then the call will not take effect (and any invocations of AsyncListener#onComplete(AsyncEvent) will be delayed) until after the container-initiated dispatch has returned to the container.

Is it "AB" or "BA"?

Let us take a look at the following example. What is the message logged here? import java.io.*; import javax.servlet.*; import javax.servlet.annotation.*; import javax.servlet.http.*; @WebServlet(urlPatterns={"/test"}, asyncSupported=true) public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { final AsyncContext asyncContext = req.startAsync(); asyncContext.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { log("A"); } @Override public void onTimeout(AsyncEvent event) throws IOException { // do nothing } @Override public void onError(AsyncEvent event) throws IOException { // do nothing } @Override public void onStartAsync(AsyncEvent event) throws IOException { // do nothing } }); new Thread() { @Override public void run() { asyncContext.complete(); } }.start(); try { Thread.sleep(5000); } catch (InterruptedException e) { // ignore } log("B"); res.getWriter().write("Done"); } } The answer is "BA" even though there is a sleep of 5 seconds there. In this case, the async-complete only take effects after leaving theHttpServlet#service method. Asynchronous operation in Servlets is implemented in GlassFish. You can download the latest GlassFish 4.0 from here.  

Update: Invoke WebConnection#close when there is an error.

Servlet 3.1 Specification (JSR 340) is almost ready for the release. One of the new features is the support for protocol upgrade. HTTP protocol upgrade was introduced in HTTP 1.1 (RFC 2616):

The Upgrade general-header allows the client to specify what additional communication protocols it supports and would like to use if the server finds it appropriate to switch protocols. The server MUST use the Upgrade header field within a 101 (Switching Protocols) response to indicate which protocol(s) are being switched.
And Web Socket is an example of protocol upgrade. In Servlet 3.1,javax.servlet.http.HttpUpgradeHandler is introduced to allow the protocol upgrade processing. In this blog, I will illustrate the use of the new API with a hypothetical protocol of checking ISBN number as follows: 
Client: (a_isbn_to_be_verified)*|EXIT and tokens can be separated by " \t\n\r\f". Server: (a_previous_isbn (true|false) CRLF)*
In this example, TestServlet decides to upgrade to a "isbn" protocol. @WebServlet("/test") public class TestServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { // checking the upgrade header and decide to upgrade if ("isbn".equals(req.getHeader("Upgrade"))) { // send 101 status code and corresponding headers res.setStatus(101); res.setHeader("Upgrade", "isbn"); res.setHeader("Connection", "Upgrade"); ISBNHttpUpgradeHandler handler = req.upgrade(ISBNHttpUpgradeHandler.class); // we can custom the http upgrade handler handler.setDebug(true); } else { res.getWriter().println("No upgrade: " + req.getHeader("Upgrade")); } } } Note that we can use CDI injection and JNDI lookup in methods of HttpUpgradeHandler. The implementation ofISBNHttpUpgradeHandler is as follows: public class ISBNHttpUpgradeHandler implements HttpUpgradeHandler { // we can use CDI injection in HttpUpgradeHandler! @Inject private ISBNValidator isbnValidator; private boolean debug = false; public ISBNHttpUpgradeHandler() { } @Override public void init(WebConnection wc) { System.out.println("ISBNHttpUpgradeHandler.init"); try { if (debug) { // testing: JNDI lookup works in here, too InitialContext initialContext = new InitialContext();
Servlet 3.1 Specification (JSR 340) and Java Authorization Contract for Containers (JSR 115) MR3 are almost ready for release. Besides "*", the role-name "**" is introduced in the above two specifications. In a nutshell, "*" means any role defined in web.xml and "**" means any authenticated user. Prior to Servlet 3.1, web containers use proprietary mechanisms to add security-constraints for any authenticated user. For instance, GlassFish v1 achieves this through the use of assign-groups. Let us look at an example of how to use "**" to have a security-constraint in Servlet 3.1. Suppose we have three servlets with a snapshot of web.xml in a web application as follows:<security-constraint> <web-resource-collection> <web-resource-name>forFooServlet</web-resource-name> <url-pattern>/foo</url-pattern> </web-resource-collection> <auth-constraint> <role-name>**</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>forBarServlet</web-resource-name> <url-pattern>/bar</url-pattern> </web-resource-collection> <auth-constraint> <role-name>*</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>forBazServlet</web-resource-name> <url-pattern>/baz</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>staff</role-name> </security-role> In this case, only "admin" and "staff" roles are defined. Suppose we have the followingsecurity-role-mapping inglassfish-web.xml. Note that group contractor does not map to any role below. <security-role-mapping> <role-name>admin</role-name> <group-name>manager</group-name> </security-role-mapping> <security-role-mapping> <role-name>staff</role-name> <group-name>staff</group-name> </security-role-mapping> Suppose Alice, Bob and Carol are authenticated users for the web application. The following table summarizes the behavior of "*" and "**".                                     
usergrouprole/foo ("**")/bar ("*")/baz ("admin")
Alicemanageradminokokok
Bobstaffstaffokokdeny
Carolcontractor okdenydeny
The feature "**" has been implemented in GlassFish 4.0. You can download it from here.  
Servlet 3.1 Specification (JSR 340) is almost ready for the release. Several new security features have been added in this version of Servlet specification. In this blog, I will explain one of the security features, namely deny-uncovered-http-methods. Let us take a look at a simple security-constraint inweb.xml as follows: <web-app xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"> <servlet> <servlet-name>TestServlet</servlet-name> <servlet-class>TestServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>TestServlet</servlet-name> <url-pattern>/myurl</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <web-resource-name>protected</web-resource-name> <url-pattern>/*</url-pattern> <http-method>GET</http-method> </web-resource-collection> <auth-constraint> <role-name>javaee</role-name> </auth-constraint> </security-constraint> </web-app> The above snapshot of web.xml indicates that when theurl-pattern /* and http-method is GET, it is accessible only by the user with role-name "javaee". The abovesecurity-constraint does not specify the behavior ofhttp-method other than GET, hence those will be accessible by everyone. Is it what we want? If a war with the web.xml above is deployed in GlassFish 4.0, the following log message will be seen in the server.log: 
    JACC: For the URL pattern /*, all but the following methods were uncovered: GET
Suppose we don't want any users accessing http-method other than GET. Then there are two ways to resolve this. 
  1. We can add another security-constraint for the above url-pattern by defining the behaviors of all except GET http-method using http-method-omission as follows: <security-constraint> <web-resource-collection> <web-resource-name>protected</web-resource-name> <url-pattern>/*</url-pattern> <http-method-omission>GET</http-method-omission> </web-resource-collection> <auth-constraint/> </security-constraint> This method will work for Servlet 3.0 applications.
  2. In Servlet 3.1, we can definedeny-uncovered-http-methods in web.xml(not in web-fragment.xml) as follows:<deny-uncovered-http-methods/>

Update: One should not use response in AsyncListener#onComplete. Only print debug in this example.

Servlet 3.1 (JSR 340) is almost ready for the release. One of the new features is the support for non-blocking IO. ReadListener and WriteListener are introduced to allow non-blocking processing in Servlet. Non-blocking IO can only be used in async (defined in Servlet 3.0) or the upgrade mode. We can set the async in a servlet with @WebServlet annotation. In this blog, we will illustrate the use of the new API with an example having non-blocking read and then non-blocking write. See the comments in the code. // enable async in the servlet @WebServlet(urlPatterns="/test", asyncSupported=true) public class TestServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { // start async AsyncContext ac = req.startAsync(); // set up async listener ac.addListener(new AsyncListener() { public void onComplete(AsyncEvent event) throws IOException { System.out.println("Complete"); } public void onError(AsyncEvent event) { System.out.println(event.getThrowable()); } public void onStartAsync(AsyncEvent event) { } public void onTimeout(AsyncEvent event) { System.out.println("my asyncListener.onTimeout"); } }); // set up ReadListener to read data for processing ServletInputStream input = req.getInputStream(); ReadListener readListener = new ReadListenerImpl(input, res, ac); input.setReadListener(readListener); } } All the read operation has been delegated to ReadListenerImpl. The data will be read in non-blocking mode. After ServletInputStream#setReadListener is called, ReadListener#onDataAvailable will be invoked immediately if there is data to read. Otherwise, it will be invoked once the data is ready. The implementation is as follows: class ReadListenerImpl implements ReadListener { private ServletInputStream input = null; private HttpServletResponse res = null; private AsyncContext ac = null; // store the processed data to be sent back to client later private Queue queue = new LinkedBlockingQueue(); ReadListenerImpl(ServletInputStream in, HttpServletResponse r, AsyncContext c) { input = in; res = r; ac = c; } public void onDataAvailable() throws IOException { StringBuilder sb = new StringBuilder(); int len = -1; byte b[] = new byte[1024]; // We need to check input#isReady before reading data. // The ReadListener will be invoked again when // the input#isReady is changed from false to true while (input.isReady() && (len = input.read(b)) != -1) { String data = new String(b, 0, len); sb.append(data); } queue.add(sb.toString()); } public void onAllDataRead() throws IOException { // now all data are read, set up a WriteListener to write ServletOutputStream output = res.getOutputStream(); WriteListener writeListener = new WriteListenerImpl(output, queue, ac); output.setWriteListener(writeListener); } public void onError(final Throwable t) { ac.complete(); t.printStackTrace(); } } When all the data is read, ReadListenerImpl#onAllDataRead sets up a WriteListenerImpl for writing data in a non-blocking mode. class WriteListenerImpl implements WriteListener { private ServletOutputStream output = null; private Queue queue = null; private AsyncContext ac = null; WriteListenerImpl(ServletOutputStream sos, Queue q, AsyncContext c) { output = sos; queue = q; ac = c; } public void onWritePossible() throws IOException { // write while there is data and is ready to write while (queue.peek() != null && output.isReady()) { String data = queue.poll(); output.print(data); } // complete the async process when there is no more data to write if (queue.peek() == null) { ac.complete(); } } public void onError(final Throwable t) { ac.complete(); t.printStackTrace(); } }

Servlet 3.1 was in Public Review in Janurary 2013. And it is in Proposed Final Draft now. Most of the new features are related to security. In this following, I will highlight features since Servlet 3.1 Public Review
  • add new APIjavax.servlet.http.Part#getSubmittedFileName
  • add new APIjavax.servlet.ServletContext#getVirtualServerName
    This API allows a JASPIC module to be registered in a Servlet container in a portable way.
  • default deny semantic
    Prior to Servlet 3.1, if the given HTTP methods are not covered by the given security constraint for given URL patterns, then the HTTP methods are not protected for the corresponding URL patterns. This may not be the desired behavior. A new elementdeny-uncovered-http-methods is added toweb.xml so that the behavior of those HTTP methods for the given URL patterns can be configured easily.
  • authenticated role, **
    If the role name ** is not explictly defined inweb.xml, it is used to denote the role name of all authenticated users. The role name ** can be used in defining an security-constraint inweb.xml and as argument ofHttpServletRequest#isUSerInRole.
More details in security can be found in Chapter 13 of Servlet 3.1 Proposed Final Draft. There are other clarifications, too. The spec and javadoc for Servlet 3.1 can be downloaded from jcp.org.  
Servlet 3.1 is in Public Review now. New features in Servlet 3.1 and changes since the EDR are listed below: 
  • support Non Blocking IO
    ReadListener, WriteListener are added to handle the non-blocking IO. And the corresponding setters are added to ServletInputStream andServletOutputStream respectively. Futhermore,ServletInputStream#isFinished, #isReady andServletOutputStream#isReady are added.
    Changes since EDR: 
    • Rename ServletOutputStream#canWrite toServletOutputStream#isReady.
    • Remove the requirement to read/write data as much as possible before Read/WriteListener is invoked.
    • Non Blocking I/O will only work in Async or Upgrade scenarios. This will simplify the understanding and usage of the API.
  • support upgrading HTTP protocol
    The allows the integration of web socket provider with servlet container provider.
    Changes since EDR: 
    • Rename ProtocolHandler toHttpUpgradeHandler.
    • Add the following method to HttpUpgradeHandler 
      public void destroy();
      This allows the handler clean up resources during destroy.
    • the upgrade API will take a class rather than an instance. This allows the container to do CDI if necessary.
      In HttpServletRequest, in EDR, we have 
      public void upgrade(ProtocolHandler handler) throws IOException;
      In Public Review, we have 
      public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException;
  • run-as clarification
    run-as will also apply to Servlet#init and#destroy now.
  • Add new API to change session id
    The Session fixation attack is a security vulnerability to web application. One way to resolve this is to change the session id during authentication. Container providers has achieved this by using proprietary API. A new method is added to HttpServletRequest as follows: 
    public String changeSessionId();
    And a new HttpSessionIdListener is added for the corresponding event. This allows JASPIC (JSR 196) auth modules to be written in a portable way.
  • Clarification on reset character encoding (more details in presentation)
And there are other bug fixes and clarifications. If you want to learn more about the Servlet 3.1, you can downloadthe spec and javadoc from jcp.org.  

Prior to Servlet 3.0, a servlet may need to wait for a long operation to complete and can cause thread starvation in web container. In Servlet 3.0, asynchronous processing is introduced to handle this situation.

There is a lot of information about asynchronous processing in Servlet 3.0. In this blog, we will take a look at two aspects of startAsync.

When willjavax.servlet.AsyncListener#onStartAsync(AsyncEvent event)method be invoked?

Let us take a look at a simple example.

    AsyncContext asyncContext = httpServletRequest.startAsync();     AsyncListener myAsyncListener = new AsyncListener() { .... };     asyncContext.addListener(myAsyncListener);

Notice thatmyAsyncListener.onStartAsync(AsyncEvent) method is not invoked in above example because asynchronous processing has already been started. When will it be called? According to Servlet 3.0 spec, AsyncListener#onStartAsync(AsyncEvent) will be invoked when the AsyncContext is reinitialized, for instance, when AsyncContext#dispatch() is invoked. As a remark, all the asyncListeners added will be removed during reinitialization and one has to add async listeners if necessary.

In a servlet with given request and response, are the AsyncContext request.startAsync() andrequest.startAsync(request, response) the same?

The above two AsyncContext are almost, but not the same. The difference is in the behavior of AsynContext#dispatch(). From javadoc of AsynContext#dispatch(),

If the asynchronous cycle was started withServletRequest#startAsync(ServletRequest, ServletResponse), and the request passed is an instance of HttpServletRequest, then the dispatch is to the URI returned byHttpServletRequest.getRequestURI().
Otherwise, the dispatch is to the URI of the request when it was last dispatched by the container.

The following example illustrate what it means. Suppose we have three HTTP servlets: A, B, C with resource paths /a, /b, /c (relative to context root) respectively.
In Servlet A, we have
    request.getRequestDispatcher("/b").forward(request, response);

In Servlet B, we have
    request.getRequestDispatcher("/c").forward(request, response);

In Servlet C, we have following two cases:

  1. request.startAsync().dispatch(); // to /b
    It will dispatch to the last dispatched URI, which corresponds to /b.
  2. request.startAsync(request, response).dispatch(); // to /c
    It will dispatch to request.getRequestURI(), which corresponds to /c.

Filter Blog

By date: