Home > Atmosphere, Comet, JQuery, Websocket > Friday’s Trick #5: Per Request Filtering of WebSocket/Comet Server Side Events

Friday’s Trick #5: Per Request Filtering of WebSocket/Comet Server Side Events

This week I will describe how you can filter, transform and aggregate WebSocket/Comet’s server side events per request using the Atmosphere Framework.

As you may know, Atmosphere already support global filtering for a set of WebSocket/Comet connection that are associated with a Broadcaster. Those filters are called BroadcastFilter and are applied every time a server side event (SSE) is broadcasted.  In that case, all connections associated with the Broadcaster, which include WebSocket, long-polling and http streaming connections will receive a SSE that may have been transformed by a BroadcastFilter. As an example, in order to support http streaming in Internet Explorer, the following BroadcastFilter needs to be added (in web.xml or programmatically):

public class JavaScriptClientFilter implements BroadcastFilter {
    private final AtomicInteger uniqueScriptToken = new AtomicInteger();

    @Override
    public BroadcastAction filter(Object message) {
        if (message instanceof String) {
            StringBuilder sb = new StringBuilder("<script id=\"atmosphere_")
                    .append(uniqueScriptToken.getAndIncrement())
                    .append("\">")
                    .append("parent.callback")
                    .append("('")
                    .append(message.toString())
                    .append("');</script>");
            message = sb.toString();
        }
        return new BroadcastAction(BroadcastAction.ACTION.CONTINUE, message);
    }
}

If we use the Atmosphere JQuery PubSub sample, which is defined as:

@Path("/pubsub/{topic}")
@Produces("text/html;charset=ISO-8859-1")
public class JQueryPubSub {

    private @PathParam("topic") Broadcaster topic;

    @GET
    public SuspendResponse subscribe() {
        return new SuspendResponse.SuspendResponseBuilder()
                .broadcaster(topic)
                .outputComments(true)
                .addListener(new EventsLogger())
                .build();
    }

    @POST
    @Broadcast
    public Broadcastable publish(@FormParam("message") String message) {
        return new Broadcastable(message, "", topic);
    }

BroadcastFilter will be invoked after the publish method return. Hence the browse generates SSE “hello sent using websocket” to be broadcasted to all connections associated with the Broadcaster’s “topic”, the remote browser will receive the transformed message

<script id="atmosphere_0">parent.callback('hello sent using websocket');</script> 

If you use the Atmosphere JQuery Plugin, the IE specific code for supporting http streaming will be executed. But that solution is far from optimal because we do process every message independently of the browser user-agent, which is a waste of resource. The JQuery Plugin will have no problem parsing the message, but it would be far more optimal if we could transform SSE only when IE is used. This is where the PerRequestBroadcastFilter comes into the picture. To implements a per request transformation, all we need to do is:

public class JavascriptClientFilter implements PerRequestBroadcastFilter {

    private final AtomicInteger uniqueScriptToken = new AtomicInteger();

    @Override
    public BroadcastFilter.BroadcastAction filter
                   (HttpServletRequest request, Object message) {

        if (request.getHeader("User-Agent") != null) {
            String userAgent = request.getHeader("User-Agent").toLowerCase();
            if (userAgent != null && userAgent.indexOf("MSIE") != -1
                    && message instanceof String) {
                StringBuilder sb = new StringBuilder
                    ("<script id=\"atmosphere_")
                    .append(uniqueScriptToken.getAndIncrement())
                    .append("\">")
                    .append("parent.callback")
                    .append("('")
                    .append(message.toString())
                    .append("');</script>");
                message = sb.toString();
            }
        }
        return new BroadcastFilter.BroadcastAction
            (BroadcastFilter.BroadcastAction.ACTION.CONTINUE, message);
    }

Now we talk! With the above, our SSE is only transformed when the IE browser is used. Filtering WebSocket’s message can also be transformed using the same technique. If you have play/read with the JQuery PubSub, the sample can be used with WebSocket/Comet and the JQueryPubSub code described above. When the browser send form param via a Websocket connection, we need to extract the form data in order to broadcast it back. A simple way to do it is by:

public class FormParamFilter implements PerRequestBroadcastFilter{

    @Override
    public BroadcastFilter.BroadcastAction filter
          (HttpServletRequest request, Object message) {

        if (request.getHeaders("X-Atmosphere-Transport").equals("WebSocket")) {
            if ( (message instanceof String) 
               && ((String) message).indexOf("=") != -1) {
                message =  message.toString().split("=")[1];
            }
        }
        return new BroadcastFilter.BroadcastAction
             (BroadcastFilter.BroadcastAction.ACTION.CONTINUE, message);
    }
}

With the above filter we are parsing the form param value only when Websocket is used, in order to extract the SSE we want to broadcast to all other connections. Of course, you can do much more complex operations inside filters!

For any questions or to download Atmosphere Client and Server Framework, go to our main site, use our Nabble forum, follow the team or myself and tweet your questions there! You can also checkout the code on Github.

About these ads
Categories: Atmosphere, Comet, JQuery, Websocket
  1. August 24, 2011 at 9:55 pm

    Salut JeanFrancois,

    Thank you for the great work you’ve done on Atmosphere : this framework is simply amazing !

    I have one question after reading this blog, can you confirm the following statement (just to be sure I understand your explanations) :
    The BroadcastFilter is executed once. Its commit flushes the broadcast to all clients (ie. suspended connexions)
    The PerRequestBroadcastFilter is executed for each clients, after any BroadcastFilter. its commit flushes the broadcast for one client only (the one that done the request)

    So according to what is said below, we can imagine extend the request context of each client with customs headers ? For instance, when something happen client side (preferences or filters set by the user), can we force the jquery atmosphere plugin subscribe again (do another request) with some updated extra header data, that could finally be used in any implemented PerRequestBroadcastFilter ?

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 51 other followers

%d bloggers like this: