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 JSON
DataFilter
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.
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.