Search Results for

    Show / Hide Table of Contents

    Middleware System

    TMS Sparkle provides classes that allow you to create middleware interfaces and add them to the request processing pipeline. In other words, you can add custom functionality that pre-process the request before it's effectively processed by the main server module, and post-process the response provided by it.

    Using middleware interfaces allows you to easily extend existing server modules without changing them. It also makes the request processing more modular and reusable. For example, the compression middleware is a generic one that compress the server response if the client accepts it. It can be added to any server module (XData, RemoteDB, Echo, or any other module you might create) to add such functionality.

    A middleware is represented by the interface IHttpServerMiddleware, declared in unit Sparkle.HttpServer.Module. To add a middleware interface to a server module, just use the AddMiddleware function (in this example, we're using a TMS XData module, but can be any Sparkle server module):

    var
      MyMiddleware: IHttpServerMiddleware;
      Module: TXDataServerModule;
    {...}
      // After retrieving the MyMiddleware interface, add it to the dispatcher
      Module.AddMiddleware(MyMiddleware);
      Dispatcher.AddModule(Module);
    

    The following topics explain in deeper details how to use middlewares with TMS Sparkle.

    Compress Middleware

    Use the Compress middleware to allow the server to return the response body in compressed format (gzip or deflate). The compression will happen only if the client sends the request with the header "accept-encoding" including either gzip or deflate encoding. If it does, the server will provide the response body compressed with the requested encoding. If both are accepted, gzip is preferred over deflate.

    To use the middleware, just create an instance of TCompressMiddleware (declared in Sparkle.Middleware.Compress unit) and add it to the server module:

    uses {...}, Sparkle.Middleware.Compress;
    {...}
      Module.AddMiddleware(TCompressMiddleware.Create);
    

    Setting a threshold

    TCompressMiddleware provides the Threshold property which allows you to define the minimum size for the response body to be compressed. If it's smaller than that size, no compression will happen, regardless of the 'accept-encoding' request header value. The default value is 1024, but you can change it:

    var
      Compress: ICompressMiddleware;
    begin
      Compress := TCompressMiddleware.Create;
      Compress.Threshold := 2048; // set the threshold as 2k
      Module.AddMiddleware(Compress);
    

    CORS Middleware

    Use the CORS middleware to add CORS support to the Sparkle module. That will allow web browsers to access your server even if it is in a different domain than the web page.

    To use the middleware, just create an instance of TCorsMiddleware (declared in Sparkle.Middleware.Cors unit) and add it to the server module:

    uses {...}, Sparkle.Middleware.Cors;
    {...}
      Module.AddMiddleware(TCorsMiddleware.Create);
    

    Basically the above code will add the Access-Control-Allow-Origin header to the server response allowing any origin:

    Access-Control-Allow-Origin: *
    

    Additional settings

    You can use overloaded Create constructors with additional parameters. First, you can set a different origin for the Access-Control-Allow-Origin:

    Module.AddMiddleware(TCorsMiddeware.Create('somedomain.com'));
    

    You can also configure the HTTP methods allowed by the server, which will be replied in the Access-Control-Allow-Methods header:

    Module.AddMiddleware(TCorsMiddeware.Create('somedomain.com', 'GET,POST,DELETE,PUT'));
    

    And finally the third parameter will specify the value of Access-Control-Max-Age header:

    Module.AddMiddleware(TCorsMiddeware.Create('somedomain.com', 'GET,POST,DELETE,PUT', 1728000));
    

    To illustrate, the above line will add the following headers in the response:

    Access-Control-Allow-Origin: somedomain.com
    Access-Control-Allow-Methods: GET,POST,DELETE,PUT
    Access-Control-Max-Age: 1728000
    

    Forward Middleware

    Use the Forward middleware to improve behavior of Sparkle server when it's located behind reverse/edge proxies, like Nginx, Traefik, Haproxy, among others.

    When using such proxies, several information of the server request object, like the requested URL, or the IP of the client, are provided as if the proxy is the client - which it is, actually. Thus the "IP of the client" will always be the IP of the proxy server, not of the original client. The same way, the requested URL is the one requested by the proxy itself, which might be different from the original URL requested by the client. For example, the client might have requested the URL https://yourserver.com, but the proxy will then request your internal server IP like http://192.168.0.105.

    When you add the Forward middleware, Sparkle will process the x-forward-* headers sent by the proxy server, which holds information about the original client. Sparkle will then modify the request based on that information, so the request will contain the original requested URL, remote IP. This way your module (like XData) or any further middleware in the process chain will see the request as if it was sent directly by the client.

    To use the middleware, just create an instance of TForwardMiddleware (declared in Sparkle.Middleware.Forward unit) and add it to the server module.

    uses {...}, Sparkle.Middleware.Forward;
    {...}
      Module.AddMiddleware(TForwardMiddleware.Create);
    

    The above code will process forward headers coming from any client. It's also recommended, for security reasons, that the server only process headers sent by the known proxy. To do that, just create the Forward middleware passing a callback that will only accept processing if the proxy name (or IP) matches the one you know. It's up to you, of course, to know the name of your proxy:

    uses {...}, Sparkle.Middleware.Forward;
    {...}
      Module.AddMiddleware(TForwardMiddleware.Create(
        procedure(const Proxy: string; var Accept: Boolean)
        begin
          Accept := Proxy = '192.168.0.132';
        end;
      ));
    

    JWT Authentication Middleware

    Add the JWT Authentication Middleware to implement authentication using JSON Web Token. This middleware will process the authorization header, check if there is a JSON Web Token in it, and if it is, create the user identity and claims based on the content of JWT.

    The middleware class is TJwtMiddleware, declared in unit Sparkle.Middleware.Jwt. The design-time class is TSparkleJwtMiddleware. It's only available for Delphi XE6 and up.

    To use the middleware, create it passing the secret to sign the JWT, and then add it to the TXDataServerModule object:

    uses {...}, Sparkle.Middleware.Jwt;
    
      Module.AddMiddleware(TJwtMiddleware.Create('my jwt secret'));
    

    By default, the middleware rejects expired tokens and allows anonymous access. You can change this behavior by using the Create constructor as explained below in the Methods table.

    Properties

    Name Description
    Secret: TBytes The secret used to verify the JWT signature. Usually you won't use this property as the secret is passed in the Create constructor.

    Methods

    Name Description
    constructor Create(const ASecret: string; AForbidAnonymousAccess: Boolean = False; AAllowExpiredToken: Boolean = False); Creates the middleware using the secret specified in the ASecret parameter, as string. The string will be converted to bytes using UTF-8 encoding.
    The second boolean parameter is AForbidAnonymousAccess, which is False by default. That means the middleware will allow requests without a token. Pass True to the second parameter to not allow anonymous access.
    The third boolean parameter is AAllowExpiredToken which is False by default. That means expired tokens will be rejected by the server. Pass True to the third parameter to allow rejected tokens.
    constructor Create(const ASecret: TBytes; AForbidAnonymousAccess: Boolean = False; AAllowExpiredToken: Boolean = False); Same as above, but creates the middleware using the secret specified in raw bytes instead of string.

    Basic Authentication Middleware

    Add the Basic Authentication middleware to implement authentication using Basic authentication. This middleware will check the authorization header of the request for user name and password provided using Basic authentication.

    The user name and password will then be passed to a callback method that you need to implement to return an user identity and its claims. Different from the JWT Authentication Middleware which automatically creates the identity based on JWT content, in the basic authentication it's up to you to do that, based on user credentials.

    If your module implementation returns a status code 401, the middleware will automatically add a www-authenticate header to the response informing the client that the request needs to be authenticated, using the specified realm.

    Example:

    uses {...}, Sparkle.Middleware.BasicAuth, Sparkle.Security;
    
      Module.AddMiddleware(TBasicAuthMiddleware.Create(
        procedure(const UserName, Password: string; var User: IUserIdentity)
        begin
          // Implement custom logic to authenticate user based on UserName and Password
          // For example, go to the database, check credentials and then create an user
          // identity with all permissions (claims) for this user
          User := TUserIdentity.Create;
          User.Claims.AddOrSet('roles').AsString := SomeUserPermission;
          User.Claims.AddOrSet('sub').AsString := UserName;
        end,
        'My Server Realm'
      ));
    

    Related types

    TAuthenticateBasicProc = reference to procedure(const UserName, Password: string; var User: IUserIdentity);
    

    The procedure that will be called for authentication. UserName and Password are the user credentials sent by the client, and you must then create and return the IUserIdentity interface in the User parameter.

    Properties

    Name Description
    OnAuthenticate: TAuthenticateBasicProc The authentication callback that will be called when the middleware retrieves the user credentials.
    Realm: string The realm that will be informed in the www-authenticate response header. Default realm is "TMS Sparkle Server".

    Methods

    Name Description
    constructor Create(AAuthenticateProc: TAuthenticateBasicProc; const ARealm: string) Creates the middleware using the specified callback and realm value.
    constructor Create(AAuthenticateProc: TAuthenticateBasicProc) Creates the middleware using the specified callback procedure.

    Logging Middleware

    Add the Logging Middleware to implement log information about requests and responses processed by the Sparkle server. This middleware uses the built-in Logging mechanism to log information about the requests and responses. You must then properly configure the output handlers to specify where data should be logged to.

    The middleware class is TLoggingMiddleware, declared in unit Sparkle.Middleware.Logging. The design-time class is TSparkleLogging​Middleware.

    To use the middleware, create it, set the desired properties if needed, then add it to the TXDataServerModule object:

    uses {...}, Sparkle.Middleware.Logging;
    
    var
      LoggingMiddleware: TLoggingMiddleware;
    
    begin
      LoggingMiddleware := TLoggingMiddleware.Create;
      // Set LoggingMiddleware properties.
      Module.AddMiddleware(LoggingMiddleware);
    end;
    

    You don't need to set any property for the middleware to work. It will output information using the default format.

    Properties

    You can refer to TSparkleLogging​Middleware for a full list of properties.

    Format string options

    The format string is a string that represents a single log line and utilize a token syntax. Tokens are references by :token-name. If tokens accept arguments, they can be passed using [], for example: :token-name[param] would pass the string 'param' as an argument to the token token-name.

    Each token in the format string will be replaced by the actual content. As an example, the default format string for the logging middleware is

    :method :url :statuscode - :responsetime ms
    

    which means it will output the HTTP method of the request, the request URL, the status code, an hifen, then the response time followed by the letters "ms". This is an example of such output:

    GET /request/path 200 - 4.12 ms
    

    Here is the list of available tokens:

    :method
    The HTTP method of the request, e.g. "GET" or "POST".

    :protocol The HTTP protocol of the request, e.g. "HTTP1/1".

    :req[header]
    The given header of the request. If the header is not present, the value will be displayed as an empty string in the log. Example: :req[content-type] might output "text/plain".

    :reqheaders
    All the request headers in raw format.

    :remoteaddr
    The remote address of the request.

    :res[header]
    The given header of the response. If the header is not present, the value will be displayed as an empty string in the log. Example: :res[content-type] might output "text/plain".

    :resheaders
    All the response headers in raw format.

    :responsetime[digits]
    The time between the request coming into the logging middleware and when the response headers are written, in milliseconds. The digits argument is a number that specifies the number of digits to include on the number, defaulting to 2.

    :statuscode
    The status code of the response.

    :url
    The URL of the request.

    Encryption Middleware

    Add the Encryption Middleware to allow for custom encryption of request and response body message. With this middleware you can add custom functions that process the body content, receiving the input bytes and returning processed output bytes.

    The middleware class is TEncryptionMiddleware, declared in unit Sparkle.Encryption.Logging.

    To use the middleware, create it, set the desired properties if needed, then add it to the TXDataServerModule object:

    uses {...}, Sparkle.Middleware.Encryption;
    
    var
      EncryptionMiddleware: TEncryptionMiddleware;
    
    begin
      EncryptionMiddleware := TEncryptionMiddleware.Create;
      // Set EncryptionMiddleware properties.
      Module.AddMiddleware(EncryptionMiddleware);
    end;
    

    These are the properties you can set. You need to set at the very minimum either EncryptBytes and DecryptBytes properties.

    Properties

    Name Description
    CustomHeader: string If different from empty string, then the request and response will not be performed if the request from the client includes HTTP header with the same name as CustomHeader property and same value as CustomHeaderValue property.
    CustomHeaderValue: string See CustomHeader property for more information.
    EncryptBytes: TEncryptDecryptBytesFunc Set EncryptBytes function to define the encryption processing of the message body. This function is called for every message body that needs to be encrypted. The bytes to be encrypted are passed in the ASource parameter, and the processed bytes should be provided in the ADest parameter.
    If the function returns True, then the encrypted message (content of ADest) will be used. If the function returns False, then the content of ASource will be used (thus the original message will be used).
    DecryptBytes: TEncryptDecryptBytesFunc Set the DecryptBytes function to define the decryption processing of the message body. See property EncryptBytes for more details.

    TEncryptDecryptBytesFunc

    TEncryptDecryptBytesFunc = reference to function(const ASource: TBytes; out ADest: TBytes): Boolean;
    

    Creating Custom Middleware

    To create a new middleware, create a new class descending from THttpServerMiddleware (declared in Sparkle.HttpServer.Module) and override the method ProcessRequest:

    uses {...}, Sparkle.HttpServer.Module;  
    
    type
      TMyMiddleware = class(THttpServerMiddleware, IHttpServerMiddleware)
      protected
        procedure ProcessRequest(Context: THttpServerContext; Next: THttpServerProc); override;
      end;
    

    In the ProcessRequest method you do any processing of the request you want, and then you should call the Next function to pass the request to the next process requestor in the pipeline (until it reaches the main server module processor):

    procedure TMyMiddleware.ProcessRequest(Context: THttpServerContext; Next: THttpServerProc);
    begin
      if Context.Request.Headers.Exists('custom-header') then
        Next(Context)
      else
        Context.Response.StatusCode := 500;
    end;
    

    In the example above, the middleware checks if the header "custom-header" is present in the request. If it does, then it calls the next processor which will continue to process the request normally. If it does not, a 500 status code is returned and processing is done. You can of course modify the request object before forwarding it to the next processor. Then you can use this middleware just by adding it to any server module:

    Module.AddMiddleware(TMyMiddleware.Create);
    

    Alternatively, you can use the TAnonymousMiddleware (unit Sparkle.HttpServer.Module) to quickly create a simple middleware without needing to create a new class. The following example does the same as above:

    Module.AddMiddleware(TAnonymousMiddleware.Create(
    procedure(Context: THttpServerContext; Next: THttpServerProc)
    begin
      if Context.Request.Headers.Exists('custom-header') then
        Next(Context)
      else
        Context.Response.StatusCode := 500;
    end
    ));
    

    Processing the response

    Processing the response requires a different approach because the request must reach the final processor until the response can be post-processed by the middleware. To do that, you need to use the OnHeaders method of the response object. This method is called right after the response headers are build by the final processor, but right before it's sent to the client. So the middleware has the opportunity to get the final response but still modify it before it's sent:

    procedure TMyMiddleware2.ProcessRequest(C: THttpServerContext; Next: THttpServerProc);
    begin
      C.Response.OnHeaders(
        procedure(Resp: THttpServerResponse)
        begin
          if Resp.Headers.Exists('some-header') then
            Resp.StatusCode := 500;
        end
      );
      Next(C);
    end;
    

    The above middleware code means: when the response is about to be sent to the client, check if the response has the header "some-header". If it does, then return with status code of 500. Otherwise, continue normally.

    Generic Middleware

    Generic middleware provides you an opportunity to add code that does any custom processing for the middleware that is not covered by the existing pre-made middleware classes.

    The middleware class is TSparkleGenericMiddleware, declared in unit Sparkle.Comp.GenericMiddleware. It's actually just a wrapper around the techniques described to create a custom middleware, but available at design-time.

    This middleware publishes two events:

    Events

    Name Description
    OnRequest: TMiddlewareRequestEvent This event is fired to execute the custom code for the middleware. Please refer to Creating Custom Middleware to know how to implement such code. If you don't call at least Next(Context) in the code, your server will not work as the request chain will not be forwarded to your module. Below is an example.
    OnMiddlewareCreate: TMiddlewareCreateEvent Fired when the IHttpServerMiddleware interface is created. You can use this event to actually create your own IHttpServerMiddleware interface and pass it in the Middleware parameter to be used by the Sparkle module.

    TMiddlewareRequestEvent

    TMiddlewareRequestEvent = procedure(Sender: TObject; Context: THttpServerContext; Next: THttpServerProc) of object;
    
    procedure TForm3.XDataServer1GenericRequest(Sender: TObject;
      Context: THttpServerContext; Next: THttpServerProc);
    begin
      if Context.Request.Headers.Exists('custom-header') then
        Next(Context)
      else
        Context.Response.StatusCode := 500;
    end;
    

    TMiddlewareCreateEvent

    TMiddlewareCreateEvent = procedure(Sender: TObject; var Middleware: IHttpServerMiddleware) of object;
    

    Passing data through context

    In many situations you would want do add additional meta informatino to the request context, to be used by further middleware and module.

    For example, you might create a database connection and let it be used by other middleware, or you want to set a specific id, etc. You can do that by using Data and Item properties of the context:

    procedure TForm3.XDataServerFirstMiddlewareRequest(Sender: TObject; Context: THttpServerContext; Next: THttpServerProc);
    var
      Conn: IDBConnection;
      Obj: TMyObject;
    begin
      // Set an ID, object and specific interface to the context
      Obj := TMyObject.Create;
      try
        Conn := GetSomeConnection;
    
        Context.Data['TenantId'] := 5;
        Context.SetItem(Obj);
        Context.SetItem(Conn);
        Next(Context);
      finally
        Obj.Free;
      end;
    end;
    

    Then you can use it in further middleware and/or module this way:

    procedure TForm3.XDataServerSecondMiddlewareRequest(Sender: TObject; Context: THttpServerContext; Next: THttpServerProc);
    var
      Conn: IDBConnection;
      Obj: TMyObject;
      TenantId: Integer;
    begin
      Conn := Context.Item<IDBConnection>;
      if Conn <> nil then ; // do something with connection
    
      Obj := Context.Item<TMyObject>;
      if Obj <> nil then ; // do something with Obj
    
      if not Context.Data['TenantId'].IsEmpty then
        // use TenantId
        TenantId := Context.Data['TenantId'].AsInteger;
    
      Next(Context);
    end;
    

    Finally, you can use such context data from anywhere in your application, using the global reference to the request. The global reference uses a thread-var, so you must access it from the same thread which is processing the request:

    procedure TMyForm.SomeMethod;
    var
      Context: THttpServerContext;
      Conn: IDBConnection;
      Obj: TMyObject;
      TenantId: Integer;
    begin
      Context := THttpServerContext.Current;
    
      // now get custom data from context
      Conn := Context.Item<IDBConnection>;
      if Conn <> nil then ; // do something with connection
    
      Obj := Context.Item<TMyObject>;
      if Obj <> nil then ; // do something with Obj
    
      if not Context.Data['TenantId'].IsEmpty then
        // use TenantId
        TenantId := Context.Data['TenantId'].AsInteger;
    end;
    
    In This Article
    • Compress Middleware
      • Setting a threshold
    • CORS Middleware
      • Additional settings
    • Forward Middleware
    • JWT Authentication Middleware
      • Properties
      • Methods
    • Basic Authentication Middleware
      • Related types
      • Properties
      • Methods
    • Logging Middleware
      • Properties
      • Format string options
    • Encryption Middleware
      • Properties
        • TEncryptDecryptBytesFunc
    • Creating Custom Middleware
      • Processing the response
    • Generic Middleware
      • Events
        • TMiddlewareRequestEvent
        • TMiddlewareCreateEvent
    • Passing data through context
    Back to top TMS Sparkle v3.31
    © 2002 - 2025 tmssoftware.com