Filtering DataSnap Byte Stream

From RAD Studio
Jump to: navigation, search

Go Up to DataSnap Client Application


The communication between a DataSnap client and a DataSnap server can be intercepted by a suite of filters. Each filter can perform transformations over the byte stream such as encryption and/or compression; the byte stream can be intercepted by more than one filter and such the output of one becomes the input of the next filter. The filters are attached to the byte stream at design time (or coded), by setting the Filters property of the DataSnap server transport components such as Datasnap.DSTCPServerTransport.TDSTCPServerTransport.

The filters are available at design time if they are present in a package registered with RAD Studio. The filter needs to be built into a package and the package needs to be installed into Delphi. Server-side design time support enables the filter to show up in the filter list editor. Client-side design-time support enables design-time connection using TSQLConnection.

There is no need to associate filters at the client side, as they are automatically instantiated based on a handshake protocol between client and server. Hence it is important that the client code registers the filters before connecting to a filtered server either by adding the unit name to the uses clause or in an early stage, such as initialization time.

Note: Data Explorer is not able to connect to servers with native code filters because DataExplorer uses a managed client.


Note: You can follow this tutorial by watching the video: Delphi Labs - Episode 3, by Paweł Głowacki.


Defining a filter

Any filter should extend the TTransportFilter class and the implementation needs to provide at minimum an ID that uniquely identifies it and two methods: ProcessInput and ProcessOutput.

  public
    function ProcessInput(const Data: TBytes): TBytes; override;
    function ProcessOutput(const Data: TBytes): TBytes; override;
    function Id: UnicodeString; override;

The processing methods are inverse to one another: the result of one passed as input to the other produces the initial input.

Each connection instantiates a filter, so it is not necessary that the processing functions be thread safe; local variables can be used for instance, but do not assume a state-full state for an instance.

The default no-parameter constructor is used to instantiate a filter instance. Additional parameters may be needed to have the client instances compatible with the server instances. Parameter values can be exchanged between server-side filters and client-side filters. As an example, this may be necessary if a location for the encryption key needs to be passed along if one chooses to implement a symmetrical encryption filter.

All parameters are exposed as (name, string) pairs. Their names can be returned through the GetParameters method and their values can be queried or changed using GetParameterValue and SetParameterValue.

  protected
    function GetParameters: TDBXStringArray; override;
  public  
    function GetParameterValue(const ParamName: UnicodeString): UnicodeString; override;
    function SetParameterValue(const ParamName: UnicodeString; const ParamValue: UnicodeString): Boolean; override;

In some cases, all parameters or a subset of those parameters needs to be changed at design time; their names can be provided by the GetUserParameters method.

  protected
    function GetUserParameters: TDBXStringArray; override;

Note: The parameter names that are not returned by this method are not visible or editable at design time.

Registering a filter

A filter needs to be registered with the TTransportFilterFactory singleton. The recommended way to register a filter is through the unit initialization and finalization sections, but it can be coded through an initialization phase in the user's application.

Below is the code snippet registering the compression filter available out of the box:

initialization
  TTransportFilterFactory.RegisterFilter(TTransportCompressionFilter);

finalization
  TTransportFilterFactory.UnregisterFilter(TTransportCompressionFilter);

The encryption filter

The encryption filter is used to encrypt a DataSnap byte stream. The encryption filter works on the server side as well as on the client side. In the following lines the behavior of the encryption filter is described.

Server side:

  • Both Datasnap.DSTCPServerTransport.TDSTCPServerTransport and Datasnap.DSHTTP.TDSHTTPService components have a Filters property.
  • When selecting the filter property you can add a new filter in the dialog that comes up. In the FilterId property, you can choose PC1 or RSA.
  • In case of using the PC1 encryption filter, the Properties property holds the Key value to use for the encryption. If using the RSA filter, the Properties property holds a list of three properties, UseGlobalKey, KeyLength, and KeyExponent.

Client side:

In this way, when you actually run the client/server, the communication between them will be encrypted.

Additional notes

There are some things to take into account while using the encryption filter with DataSnap client/server applications:

  • The data encryption is not available with thin clients.
  • If the server has an encryption filter but the client does not, then the client will automatically add the filter.
  • If the client has an encryption filter but the server does not, then the client will drop its filter and will not use it.

The compression filter

The compression filter is based on ZLib and provides compression capabilities for DataSnap byte streams. The compression filter works both on the server side and the client side.

The Datasnap.DSTCPServerTransport.TDSTCPServerTransport and Datasnap.DSHTTP.TDSHTTPService components have a Filters property. This is where you add a new filter setting the FilterId property to ZLibCompression. You can also specify properties for the compression filter by setting the Properties property.