Server-Side Filters (with Java)

Our liveblog application will allow any number of clients to connect and view the content in realtime, but it also will allow any number of people to publish content. We don’t want to rely on the hope that the users will never find our content creation URL, so we’re going to have to lock it down. This is a great opportunity for us to test out some Java code. If you’re not familiar with Java, don’t worry. It won’t hurt a bit.

To limit posting access to authorized users, we’re going to require a password to be sent along with each publish request. If the password is correct, we’ll publish the content; if it’s wrong, we’ll silently ignore the request. On the server side we can check for this password in a number of places, but we’re going to do it from within the server-side content filter.

Server-side filters are Java-based classes that work very simply. As messages get sent to specific channels on the server, the server checks to see whether those channels have any filters set up. If a filter is configured, the request is sent to the filter before doing any other processing on the request. Inside the filter class, the data may be modified and returned to be operated on by the server or passed back to the clients. But if the filter returns null, the message is dropped and not delivered any further up the chain, and certainly not back to clients who have subscribed to the channel. This makes it a perfect place for us to check for the correct password.

The first thing we need to do is set up the filter configuration file. These files are just JSON data structures linking channels to Java classes, stored near the configuration file in the WEB-INF folder of the servlet. Open the file apps/src/main/webapp/WEB-INF/filters.json and add the following data structure:

[
    {
        "channels": "/river/**",
        "filter"  : "com.tedroden.realtime.FilterPasswordCheck",
        "init"    : { "required_password": "12345" }
    }
]

This file is pretty straightforward. While this example has only one filter in it, the data structure is set up as an array, so as you add more filters, simply append them as JSON objects into the existing array. The fields in the object are as follows:

channels

The channel name (or names) that this filter applies to. The channel names follow the same conventions as everywhere else, and we can use a channel glob as we’ve done here. This will allow us to filter any content sent to any /river channel.

filter

This is the actual Java classname that is used as the filter. When a request matches the channel listed in that field, the request is sent through this class as soon as it is received.

init

This JSON object gets sent to the filter class listed in the previous field upon initialization. You can use this space to pass any variables to the class that apply to this specific instance of the filter.

Next, we need to create the filter class that is called when a publish request is made to /river/flow. Open the file apps/src/main/java/com/tedroden/realtime/FilterPasswordCheck.java and add this:

package com.tedroden.realtime;

import java.util.Map;
import org.cometd.Client;
import org.cometd.Channel;
import org.cometd.server.filter.JSONDataFilter;

public class FilterPasswordCheck extends JSONDataFilter
{

	String required_password;
    @Override
	public void init(Object init)
    {
        super.init(init);
		required_password = (String) ((Map)init).get("required_password");
    }

    @Override
	public Object filter(Client from, Channel to, Object data)
    {
		try {
			if(((Map)data).get("password").equals(required_password)) 
				return data;
			else  
				return null;
		}
		catch (NullPointerException e) {
			return null;
		}
	}
}

The top of this file just declares that it’s part of the com.tedroden.realtime package, or whichever namespace you’ve decided to use. After that, we import a few Java libraries that this file uses. Then, we just create our filter class, which extends the JSONDataFilter class provided by the cometd distribution.

When we set up the filters.json file, we specified certain data that got passed to the init function of the filter. As you can see, it’s the first and only parameter this init function accepts. We pass it along to the parent class and then grab the required_password, which will be used for the lifetime of this filter.

The only other function in this file is the actual filter function. On top of the data parameter, which is the JSON data passed in from the client, there are also two other parameters. The first parameter is the Client object, otherwise known as the sender of the message. The second parameter is an object representation of the destination channel.

The data parameter is the JSON object we pass in from JavaScript when we publish to the server. All we do is check to see that the provided password matches the required_password that we set up in the init function. If it does, we return the data; otherwise, we return null. This is a very basic filter that does one of two things. It either blocks the data from getting through to the client or passes it along unchanged.

Before this filter is picked up by the server, we need to tell the apps/src/main/webapp/WEB-INF/web.xml file about it. Open up that file and add the filters parameter to the <servlet> section:

<servlet-name>cometd</servlet-name>
        <servlet-class>
		  org.cometd.server.continuation.ContinuationCometdServlet
		</servlet-class>
        <init-param>
            <param-name>filters</param-name>
            <param-value>/WEB-INF/filters.json</param-value>
        </init-param>
...

Finally, we need to collect the password from the author and pass it along with the Bayeux publish request. We need to make two minor changes to get this to work. First, let’s add the password field to apps/src/main/webapp/river-post.html:

<p>
	<label for="author">Author</label> <br />
	<input type="text" id="author" value="" placeholder="Your Name" />
</p>
<p>
	<label for="password">Password</label> <br />
	<input type="text" id="password" value="" placeholder="Password (12345)" />
</p>

Then, inside apps/src/main/webapp/river.js, we add one additional line to send the password to the server:

function submitPost(e) {
	dojox.cometd.publish('/river/flow', {
		'content': dojo.byId('content').value,
		'password': dojo.byId('password').value,
		'author': (dojo.byId('author').value ?
				   dojo.byId('author').value :
				   'Anonymous')
...

We’ve added all of the code needed to secure this form with a password, so now we need to start the server and test it out. From the apps directory, instruct Maven to start the server:

~ cometd-java/apps $ mvn jetty:run
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'jetty'.
...

This will compile the newly created filter and then start the server. When you point your web browser to the same river-post.html, you’ll notice the newly added password field (see Figure 4-8). The password we set in the filters.json file was “12345”. Try submitting the form both with and without the correct password. When the correct password is supplied, the form submits like normal. If the password is wrong, the publish request is silently ignored.

The form with its password field

Figure 4-8. The form with its password field

Get Building the Realtime User Experience now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.