HTTP Request and Extensions integration

In this new recipe we’re going to take a look at how we can integrate regular HTTP calls with the SmartFoxServer runtime and specifically how to communicate with Extension code via HTTP GET/POST requests.

Common applications of the HTTP/Extension interoperability are debugging interfaces and administration UIs. With this approach developers can easily build a simple web interface that reports that game state, monitors data structures, users etc… allowing to quickly debug problems while testing, triggering events and so on.

» A working example

Let’s take a look at a simple use case that illustrates how to work with servlets (Jetty) and extensions (SFS2X). We will start by building our simple SFS2X Extension and implementing the handleInternalMessage(…) method, which is at the bridge between the two worlds.

package sfs2x.test.http.ext;

import com.smartfoxserver.v2.extensions.SFSExtension;

public class SimpleSumExtension extends SFSExtension
{
	@Override
	public void init()
	{
		trace("Simple Sum Extension -- Ready");
	}

	@Override
	public Object handleInternalMessage(String cmdName, Object params)
	{
		Object result = null;

		trace(String.format("Called by: %s, CMD: %s, Params: %s", Thread.currentThread().getName(), cmdName, params));

		if (cmdName.equals("numbers"))
		{
			String[] nums = ((String) params).split("\\,");

			int sum = 0;

			for (String item : nums)
			{
				try
				{
					sum += Integer.parseInt(item);
				}
				catch(NumberFormatException nfe)
				{
					// Skip and continue
				}
			}

			result = sum;
		}

		return result;
	}
}

The handleInternalMessage(…) method is very generic, allowing us to send a “command” string and a parameter object. The latter could be anything from a number to an array or collection of specific objects. Similarly the return value could be any object.

In our case we define a command called “numbers” and expect a String containing comma separated numbers. We’ll take these numbers, sum them all together and return the result back to the servlet.

This will allow us to use a simple url request such as

http://localhost:8080/servlet?numbers=1,2,3,4

send the data to the Extension, obtain the result back and output it to our HTML page, integrating it with other dynamic data, JSP pages, template engines etc…

In order to build the Extension we will export a jar file from our IDE of choice and deploy it under extensions/HTTPSumExtension/SumExtension.jar

Then we open the AdminTool > Zone Configurator, select our Zone of choice (e.g. BasicExamples) and activate the Extension:

SumExt

» The servlet

Next we proceed with the creation of the simple servlet that will act as the bridge between the browsers’s requests and the SFS2X’s Extension.

package sfs2x.test.http;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.smartfoxserver.v2.SmartFoxServer;
import com.smartfoxserver.v2.extensions.ISFSExtension;

@SuppressWarnings("serial")
public class ExtensionHTTPFacade extends HttpServlet
{
	private final static String CMD_NUMBERS = "numbers";

	private SmartFoxServer sfs;
	private ISFSExtension myExtension;

	@Override
	public void init() throws ServletException
	{
		sfs = SmartFoxServer.getInstance();
		myExtension = sfs.getZoneManager().getZoneByName("BasicExamples").getExtension();
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
	{
		Object result = null;
		String numbers = req.getParameter(CMD_NUMBERS);

		if (numbers != null)
			result = "RES: " + myExtension.handleInternalMessage(CMD_NUMBERS, numbers);
		else
			result = "Please pass a list of comma separated values called 'numbers'. Example ?numbers=1,2,3,4";

		resp.getWriter().write(result.toString());
	}
}

All of the “magic” happens in the init() method were we obtain a reference to the SmartFoxServer instance. From there we proceed by accessing the Zone we want to work with.

As you may already know, each Zone represents a different application running in the server so the Zone is the top level object from which we can access the user’s list, room lists, the Zone Extension, the database manager etc…

In this case we just reference the Zone’s Extension so that we can later invoke its handleInternalMessage(…) method.

The doGet(…) implementation simply acts as a paper-pusher by taking the “numbers” parameter, handing it over to the Extension and finally outputting the result to the client.

» Putting it all together

Before we can test the solution we need to deploy the servlet. If you’re using an IDE such as Neatbeans or Eclipse you can generate a .war file with a few clicks and deploy it under SFS2X/www/. Otherwise you can do the same manually by creating this folder structure under SFS2X/www/ 

servlet-test-deploy

The ExtensionTest folder must contain the usual WEB-INF/ with the compiled classes and the deployment descriptor (web,xml), which looks like this:

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app
   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
   metadata-complete="false"
   version="3.1">

	<display-name>SFS2X Test</display-name>

	<servlet>
		<servlet-name>ExtensionTest</servlet-name>
		<servlet-class>sfs2x.test.http.ExtensionHTTPFacade</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>ExtensionTest</servlet-name>
		<url-pattern>/ExtensionTest</url-pattern>
	</servlet-mapping>
</web-app>

Time to restart the server and test locally in the browser with this url:

http://localhost:8080/servlet?numbers=100,60,50,112,88

 which will yield this:

serlvet-res

 » Class loader woes

The simple example shown so far uses simple objects found in the JDK such as String, Integer etc… no big deal. When working with custom data, however, you may encounter class loading issues due to the fact that classes defined in an Extension are visible only under their class loader.

Attempting to pass these objects between servlet and Extension will not work out of the box, and depending on the specific case it will require special attention at the deployment process.

We have already discussed in many articles various ways to deploy Extension classes that are visible at the top class loader. Here are some links to learn more:

Finally there can be situations in which it is more convenient to serialize the objects that need to be passed around, avoiding more complex deployment schemes.

» When this is not a good idea

We mentioned in the opening that there are can be a few concerns when exposing Extension calls via HTTP, namely:

  1. Security: SmartFoxServer always knows who is sending a request because clients are recognized via their TCP socket. Using HTTP removes this identification leaving ample room for identity spoof attacks.
  2. Scalability: HTTP is much slower and heavier than the SFS2X protocol. If the HTTP requests being sent are going to be fast and copious it is likely that we’ll hit a bottleneck at relatively low CCU counts.

While the former problem can be solved in several ways, the latter is much more of a concern and a reminder that this feature should be used sparingly and for debugging/monitoring purposes more than anything else.

In regards to issue #1 a good way to strengthen the security would be to send the unique session token obtained by the User after connecting to SmartFoxServer. The token can be used to identify the sender and also to double check his identity against the HTTP request’s source address.

By matching this two pieces of data we can be reasonably sure that no spoofing is going on.

The session token can be obtained from the client side via the SmartFox.SessionToken property, available across all supported languages.