IoFilters

IoFilter is one of the Mina.NET core constructs that serves a very important role. It filters all I/O events and requests between IoService and IoHandler. Many out-of-the-box filters are provided to accelerate network application development pace by simplifying typical cross-cutting concerns using the out-of-the-box filters such as:
  • LoggingFilter logs all events and requests.
  • ProtocolCodecFilter converts an incoming IoBuffer into message POJO and vice versa.
  • SSLFilter adds SSL - TLS support.
  • and many more!

In this article, we will walk through how to implement an IoFilter for a real world use case. It's easy to implement an IoFilter in general, but you might also need to know specifics of Mina.NET internals. Any related internal properties will be explained here.

Filters already present

We have many filters already written. The following table list all the existing filters, with a short description of their usage.

Filter Type Description
Blacklist BlacklistFilter Blocks connections from blacklisted remote addresses.
Buffered Write BufferedWriteFilter Buffers outgoing requests like the BufferedOutputStream does.
ConnectionThrottle ConnectionThrottleFilter Blocks connections from connecting at a rate faster than the specified interval.
ErrorGenerating ErrorGeneratingFilter
Executor ExecutorFilter Enforce a certain thread model while allowing the events per session to be processed simultaneously.
FileRegionWrite FileRegionWriteFilter
KeepAlive KeepAliveFilter
Logging LoggingFilter Logs event messages, like MessageReceived, MessageSent, SessionOpened, ...
Noop NoopFilter A filter that does nothing. Useful for tests.
Profiler ProfilerTimerFilter Profile event messages, like MessageReceived, MessageSent, SessionOpened, ...
ProtocolCodec ProtocolCodecFilter A filter in charge of encoding and decoding messages.
Reference counting ReferenceCountingFilter Keeps track of the number of usages of this filter.
SessionAttributeInitializing SessionAttributeInitializingFilter
StreamWrite StreamWriteFilter
SSL SslFilter SSL support.

Overriding Events Selectively

You can extend IoFilterAdapter instead of implementing IoFilter directly. Unless overriden, any received events will be forward to the next filter immediately:

class MyFilter : IoFilterAdapter
{
  public override void SessionOpened(INextFilter nextFilter, IoSession session)
  {
    // Some logic here...
    nextFilter.SessionOpened(session);
    // Some other logic here..
  }
}

Transforming a Write Request

If you are going to transform an incoming write request via IoSession.Write(), things can get pretty tricky. For example, let's assume your filter transforms HighLevelMessage to LowLevelMessage when IoSession.Write() is invoked with a HighLevelMessage object. You could insert appropriate transformation code to your filter's FilterWrite() method and think that's all. However, you have to note that you also need to take care of MessageSent event because an IoHandler or any filters next to yours will expect MessageSent() method is called with original write request as a parameter, because it's irrational for the caller to get notified that LowLevelMessage is sent when the caller actually wrote HighLevelMessage. Consequently, you have to implement both FilterWrite() and MessageSent() if your filter performs transformation.

Please also note that you still need to implement similar mechanism even if the types of the input object and the output object are identical because the caller of IoSession.Write() will expect exactly what he wrote in his or her MessageSent() handler method.

Let's assume that you are implementing a filter that transforms a string into a char[]. Your filter's FilterWrite() will look like the following:

public override void FilterWrite(INextFilter nextFilter, IoSession session, IWriteRequest writeRequest)
{
  nextFilter.FilterWrite(session, new MyWrapRequest(writeRequest, ((string)writeRequest.Message).ToCharArray()));
}

class MyWrapRequest : WriteRequestWrapper
{
  private readonly char[] _chars;

  public MyWrapRequest(IWriteRequest writeRequest, char[] chars)
    : base(writeRequest)
  {
    _chars = chars;
  }

  public override Object Message
  {
    get { return _chars; }
  }
}

Now, we need to do the reverse in MessageSent():

public override void MessageSent(INextFilter nextFilter, IoSession session, IWriteRequest writeRequest)
{
  MyWrapRequest wrapRequest = writeRequest as MyWrapRequest;
  if (wrapRequest != null)
    nextFilter.MessageSent(session, wrapRequest.InnerRequest);
  else
    nextFilter.MessageSent(session, writeRequest);
}

Watch out the Empty Buffers!

Mina.NET uses an empty buffer as an internal signal at a couple of cases. Empty buffers sometimes become a problem because it's a cause of various exceptions such as IndexOutOfRangeException. This section explains how to avoid such a unexpected situation.

ProtocolCodecFilter uses an empty buffer (i.e. buf.HasRemaining == false) to mark the end of the message. If your filter is placed before the ProtocolCodecFilter, please make sure your filter forward the empty buffer to the next filter if your filter implementation can throw a unexpected exception if the buffer is empty:

public override void MessageSent(INextFilter nextFilter, IoSession session, IWriteRequest writeRequest)
{
  IoBuffer buf = writeRequest.Message as IoBuffer;
  if (buf != null && !buf.HasRemaining)
  {
    nextFilter.MessageSent(session, writeRequest);
    return;
  }
}

public override void FilterWrite(INextFilter nextFilter, IoSession session, IWriteRequest writeRequest)
{
  IoBuffer buf = writeRequest.Message as IoBuffer;
  if (buf != null && !buf.HasRemaining)
  {
    nextFilter.FilterWrite(session, writeRequest);
    return;
  }
}

Do we always have to insert the if block for every filters? Fortunately, you don't have to. Here's the golden rule of handling empty buffers:
  • If your filter works without any problem even if the buffer is empty, you don't need to add the if blocks at all.
  • If your filter is placed after ProtocolCodecFilter, you don't need to add the if blocks at all.
  • Otherwise, you need the if blocks.

If you need the if blocks, please remember you don't always need to follow the example above. You can check if the buffer is empty wherever you want as long as your filter doesn't throw a unexpected exception.

Last edited Apr 15, 2014 at 6:38 AM by longshine, version 2