• Examples (iOS)
• Examples (Android)
• Examples (C++)
Server API Documentation

 

» SystemController Filters

Since SFS2X version 2.3.0 we have introduced a new set of custom filters called SystemController Filters which can be used by developers for additional control of client requests.

» What are Controllers, exactly?

Before describing how filters work, let's backup for a moment and provide an exact definition of a Controller. Under the SFS2X architecture Controllers are responsible for handling every client request, in particular:

In the previous chapters of this section we have already explained how the ExtensionController works and how filters can be applied to Extension code. In this article we will concentrate on filters that can be applied to the SystemController.

» Filtering the SystemController requests

The image below shows in detail the path that a TCP/UDP request takes from the bottom network I/O layer up to one of the Controllers we have just described (by the way, SFS2X is not just limited to 2 controllers, it can actually use up to 256 simultaneously).

While developers have full control over the Extension request, the amount of control over the SystemController is limited to the events fired at the Extension: for example the ROOM_JOIN or PUBLIC_MESSAGE event, to which he can react to, but cannot modify or interact with.

controllers

That is exactly what SystemController Filters provide. Filters can be easily plugged (via code) into the SystemController and they can interact with each client request allowing you to filter parameters, add extra logic and validation, etc. Here are a few example use cases:

» How it works

Let's take the first use case and see how we can create a filter that interacts with the PublicMessage request, for example by removing all instances of numeric characters in every message.

We start by creating a new Class called PublicMessageNumberFilter extending the base class SysControllerFilter, from package sfs2x.extension.filters. This is a very simple class that requires only one method to be overridden:

public class PublicMessageNumberFilter extends SysControllerFilter
{
	@Override
	public FilterAction handleClientRequest(User sender, ISFSObject params) throws SFSException
	{
		StringBuilder message = new StringBuilder(params.getUtfString(GenericMessage.KEY_MESSAGE));
        
        // Character pointer
		int p = 0;
		
		while (p < message.length())
		{
			char ch = message.charAt(p); 
			
			// Remove numeric characters
			if (ch >= 0x30 && ch <= 0x39)
				message.deleteCharAt(p);
			else
				p++;
		}
		
		// Store new message in parameters list 
		params.putUtfString(GenericMessage.KEY_MESSAGE, message.toString());
	    return FilterAction.CONTINUE;
	}
}

It is interesting to note that the handleClientRequest() method always returns a FilterAction object. When we return FilterAction.CONTINUE we allow the next filter in the chain to continue its execution flow down until the SystemController. However if any of the filters returns a FilterAction.HALT, the execution will stop there without propagating any further.

Now that we have prepared the filter we can plug it into the SystemController via our Extension. Normally we suggest to do this in the init() method of your Zone-level Extension:

public class FilteringExtension extends SFSExtension
{
	@Override
	public void init()
	{
		// Reset filter chain to clean previous filters
		getParentZone().resetSystemFilterChain();
		
		ISystemFilterChain filterChain = new SysControllerFilterChain();
		filterChain.addFilter("numberFilter", new PublicMessageNumberFilter());
		
        // Plug the filter chain	
		getParentZone().setFilterChain(SystemRequest.PublicMessage, filterChain);
	}
	
	@Override
	public void destroy()
	{
	    super.destroy();
	}
}

The first line of code under the init() method clears all previous filters we might have applied. This is an important operation because we might reload the Extension several times during testing and even in production and we don't want duplicate filters. Then we proceed with creating a new filter chain, we add our PublicMessageNumberFilter class and finally we register it with the PublicMessage request. This is all we need to filter the public chat message, or any other API request in a similar way.

Finally, please note that by using a filter chain you can add any number of filters in the order of execution that you prefer, keeping in mind that the first filter added to the chain is the first to be executed and so on and so forth.

» Deploying filters

The easiest way to deploy your own filters is to bundle them in your main Zone Extension .jar file. This will ensure that filters will get hot-reloaded with your Extension code when you re-deploy it.

However, if you are planning to provide filters as a third party solution (for example a custom Bad Words Filter) you might want to provide a separate .jar file which can be deployed to the extensions/__lib__/ folder so that any Extension can access it. In this case a hot-reload of the Extension will not update the filters, so in case of filter update a server restart will be necessary. If this is not wanted the filters can be deployed in the same folder as the Extension that it is using it.

Finally, for third-party filters developer it would be convenient to provide a class with a static method that takes care of setting up all necessary filters, hiding it from the developer's Extension code. Here's an example template:

public class CustomFilterSetup
{
	public static void initialize(Zone targetZone)
    {
    	// Reset filter chain to clean previous filters
		targetZone.resetSystemFilterChain();
		
		ISystemFilterChain filterChain = new SysControllerFilterChain();
		filterChain.addFilter("numberFilter", new PublicMessageNumberFilter());

        // Plug the filter chain	
		targetZone.setFilterChain(SystemRequest.PublicMessage, filterChain);
    }
}

The above example is based on our previous PublicMessageNumberFilter example. The Extension developer will only need to call CustomFilterSetup.initialize(...) in his init() method.

» Threading recommendations

All filters are executed in the SystemController thread(s). Normally, all System Request handlers are executed very fast without blocking or long-lasting operations, therefore the Controller can operate with just one thread, which is recommended.

If you plan to run blocking or long-lasting operations such as socket calls, DB queries, HTTP requests, etc, you should be aware that you might slow down the Controller significantly, which in turn might result in slow responses to the clients. In this case we recommend to resize the SystemController thread pool or, if necessary, run a dedicated thread pool for the "slow" operations (the SystemController thread pool can be configured from the AdminTool).

» Where exactly filters are applied?

To give you a better idea of how the SystemController works and how the Filters operate take a look at the following diagram:

flow

Before the client request reaches the filter code, there are several checks that are applied to ensure that the request is valid:

If any of these validations fail the message will never reach the filter code, so if you're not receiving particular messages you should double check the settings for the Anti-Flood Filter and Permission Profiles.

PLEASE NOTE
In the case of messages such as Public/Private/Buddy messages the BadWords Filter is applied after the SystemController Filter. If you are substituing this feature with a custom solution please make sure to turn off the BadWords Filter in the Zone configuration.

» Supported SystemController requests

This is a list of the requests currently supported for filtering and the relative parameters for each request:

  • JoinRoom
  • CreateRoom
  • ObjectMessage
  • SetRoomVariables
  • SetUserVariables
  • LeaveRoom
  • SubscribeRoomGroup
  • UnsubscribeRoomGroup
  • PublicMessage
  • PrivateMessage
  • ModeratorMessage
  • AdminMessage
  • KickUser
  • BanUser
  • SetUserPosition (*)
  • AddBuddy (*)
  • BlockBuddy (*)
  • RemoveBuddy (*)
  • SetBuddyVariables (*)
  • BuddyMessage (*)
  • InviteUser (*)
  • InvitationReply (*)
  • QuickJoinGame (*)
  • (*) = available in version 2.8.x and higher