Skip to content

SystemController Filters

In the SmartFoxServer architecture Controllers are responsible for handling client requests:

  • SystemController: deals with all system calls such as Login, Join, CreateRoom, PublicMessage, SetUserVariables, etc. In other words it manages all client API calls.

  • AudioController: deals with all of the audio streaming operations

  • ExtensionController: deals exclusively with calls to custom Extensions, dispatching each request to the proper handler.

In this article we are going to describe the filters that can be applied to the SystemController to customize the behavior of certain requests.

Filtering the SystemController requests

The diagram 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.

While developers have full control over the Extension requests, the amount of control over the SystemController is limited to the server-side events it generates: for example the ROOM_JOIN or PUBLIC_MESSAGE events, to which an Extension can react, but that cannot modify or interact with.

controllers architecture

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

  • bypass Public/Private/Buddy Message filtering, adding your own custom filter or a third party solution
  • bypass the Anti-Flood Filter with a custom or third party implementation
  • add custom logic on top of requests such as JoinRoom and CreateRoom
  • add custom filtering or logic to requests such as SetUserVariables and SetRoomVariables

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 sfs.extension.filters. This class 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.getString(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.putString(GenericMessage.KEY_MESSAGE, message.toString());
        return FilterAction.CONTINUE;
    }
}

The handleClientRequest() method always returns a FilterAction object which can express two states:

  • FilterAction.CONTINUE: allows the next filter in the chain to continue its execution flow down until hitting the SystemController
  • FilterAction.HALT: execution stops here without propagating further and the System Controller will thus drop the request

Now that we have prepared the filter we can plug it into the SystemController via our Extension. We recommend doing 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();

        var filterChain = new SysControllerFilterChain();
        filterChain.addFilter("numberFilter", new PublicMessageNumberFilter());

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

    @Override
    public void destroy()
    {
        super.destroy();
    }
}

Clearing all previous filters is an important operation because we might reload the Extension several times during testing and we don't want duplicate filters. Then we proceed with creating a new filter chain, we add our PublicMessageNumberFilter and finally we register it with the PublicMessage request. This is all that's needed to filter SystemController requests.

Order is important

NOTE: using a filter chain you can add any number of filters in the order of execution that you prefer, keeping in mind that the order in which they are added is the order in which they will execute (first come, first served)

Deploying filters

The easiest way to deploy your own filters is to bundle them in your Extension .jar file. This ensures 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 to be deployed in extensions/_shared/ so that multiple Extensions can access it. In this case a hot-reload of the Extension will not update the filters. This means that shared filter do not benefit from hot-reloading and a server restart will be required to reload everything correctly.

Finally, for third-party filters developers 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. This way the Extension developer would only need to call CustomFilterSetup.initialize(...) in his init() method.

Where exactly filters are applied?

To give you a better idea of how the SystemController works and how Filters interact, check the following diagram:

system controller flow

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

  • Formal parameter validation: any request lacking the essential parameters expected by the server API is dropped with an error message in the log files.
  • Anti-Flood Filter: if this filter is turned on each request is checked against the configured rules and discarded in case a flooding attempt is detected.
  • User Permission Check: the request is also validated against the sender's permission profile and discarded in case the user is not allowed to execute such command.

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.

Mind the word filter

NOTE: In the case of messages such as Public/Private/Buddy messages the BadWords Filter is applied after the SystemController Filter. If you are substituting 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