Table of Contents

Hosting

Sparkle provides a hosting layer that turns a server application into a single console executable that runs, unchanged, in three ways:

  • as an interactive console application, on Windows and Linux: run it from the IDE or a terminal, watch the log lines, press Ctrl+C to stop it;
  • as a Windows service, registered with the Service Control Manager;
  • as a systemd daemon on Linux.

The mode is chosen at runtime by the command line, not at compile time. There are no conditional defines, no VCL form and no TService descendant: the application code does not know, and does not need to know, how it was started. Everything that differs between the three modes - the handshake with the Service Control Manager, the signal handling, where a log line goes, what the process exit code says - is handled by the host.

The entry point is unit Sparkle.Host. The supporting units are Sparkle.​Host.​Contracts (the interfaces and base classes), Sparkle.​Host.​Shutdown (graceful shutdown), Sparkle.Host.Log (logging), Sparkle.Host.Reload (configuration reload) and Sparkle.​Host.​Components (helpers for the design-time components). All of them are part of the Sparkle runtime package.

Getting Started

The quickest way to get a hosted server is the TMS Sparkle Server wizard:

  1. Choose File > New > Other and look for the TMS BIZ category under Delphi Projects.
  2. Double-click TMS Sparkle Server.

The same category offers the TMS XData Server and TMS RemoteDB Server wizards, which create the same kind of project around a TXDataServer or a TRemoteDBServer component.

The wizard creates a console project whose program is one line:

program SparkleProject;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Sparkle.Host,
  ServerModuleUnit in 'ServerModuleUnit.pas' {ServerModule: TDataModule};

begin
  RunHost(TServerModule);
end.

and a data module holding a TSparkleGenericServer component and two dispatcher components, a TSparkleSocket​Dispatcher and a TSparkleHttp​SysDispatcher. The module wires the server to a dispatcher and starts it in its OnCreate event:

procedure TServerModule.DataModuleCreate(Sender: TObject);
begin
  // The socket dispatcher runs the same on Windows and Linux. For high-load
  // Windows production servers, HTTP.sys is recommended: uncomment the five
  // commented lines below (Linux keeps the socket dispatcher). Non-localhost
  // URLs need an HTTP.sys URL reservation - see the TMS HTTP Config Tool.
//  {$IFDEF MSWINDOWS}
//  Server.Dispatcher := HttpSysDispatcher;
//  HttpSysDispatcher.Start;
//  {$ELSE}
  Server.Dispatcher := SocketDispatcher;
  SocketDispatcher.Start;
//  {$ENDIF}

  // "Now listening on: ..." to the console (or the log file, as a service).
  ReportActiveDispatchers(Self);
end;

Press F9. A console window opens and shows the line Now listening on: http://localhost:2001/tms/sparkle. Open that address in a browser to see the response, then press Ctrl+C in the console to stop the server gracefully. The project targets Win32, Win64 and Linux64.

Note

The generated module uses the socket server on both platforms, so development and production behave the same and no administrative setup is needed. See Choosing the dispatcher for when to switch to Http.Sys on Windows.

The Host Program

There are three ways to describe what the process hosts, from the simplest to the most flexible. They are not alternatives: the first two are shortcuts for the third, so a program can move up a step without rewriting what it already has.

Hosting a data module

RunHost with a component class - typically a data module that holds the server and dispatcher components, as generated by the wizard:

begin
  RunHost(TServerModule);
end.

The host creates one instance of the module on start and frees it on stop. Everything the module needs to do happens in its OnCreate (wire the dispatcher, start it) and, if needed, OnDestroy events. Freeing the module destroys the dispatcher component, which stops the underlying server.

Hosting a block

RunHost with an anonymous procedure, for servers built from code. The block has the shape every Delphi console server already has - create, start, wait, free - except that the wait is a call to WaitForShutdown instead of ReadLn:

uses
  Sparkle.Host,
  Sparkle.Host.Shutdown,
  Sparkle.Socket.Server;

begin
  RunHost(
    procedure
    var
      Server: TSocketHttpServer;
    begin
      Server := TSocketHttpServer.Create;
      try
        Server.AddModule(TMyServerModule.Create('http://localhost:8080/myapi'));
        Server.Start;
        WaitForShutdown;
      finally
        Server.Free;
      end;
    end);
end.

The call to WaitForShutdown is the boundary between starting and running: everything before it is startup, and reaching it means the application is up. That is the instant at which a Windows service reports "running" to the Service Control Manager, and returning from it is when the service reports "stop pending". Because the host observes that boundary, none of the service protocol leaks into your code.

When a single block is hosted it runs on the main thread, so breakpoints and stack traces look exactly as they would in a plain console program.

Hosting several things

CreateHost returns an IHostBuilder to which any number of modules, blocks and workers are added, in a fluent chain ending with IHostBuilder.Run:

begin
  CreateHost
    .ServiceOptions('MyApi', 'My API Server', 'REST API for the sales system')
    .Add(TApiServerModule)
    .Add(TReportsServerModule)
    .Add(TNightlyJobsWorker)
    .Run;
end.

Items are started in the order they were added and stopped in reverse order. If one of them fails to start, the ones already started are stopped, the error is logged and the process exits with code 1 - so a daemon that could not listen says so to systemd, to the Service Control Manager and to whoever automates the deployment, instead of looking like a normal shutdown.

IHostBuilder.​Service​Options sets the identity used when the process is installed as a Windows service or printed as a systemd unit. It is optional: when omitted, the executable base name is used as the service name.

Workers

Not everything in a server process is an HTTP server. A job runner, a queue poller or a scheduler has a loop of its own, and the host can run it next to the servers, in the same process, with the same lifetime. Derive from THostedWorker and override its Execute method:

uses
  Sparkle.Host.Contracts,
  Sparkle.Host.Log;

type
  TNightlyJobsWorker = class(THostedWorker)
  protected
    procedure Execute(const AStop: IStopToken); override;
  end;

procedure TNightlyJobsWorker.Execute(const AStop: IStopToken);
begin
  repeat
    RunPendingJobs(AStop);
  until AStop.WaitFor(60000);
end;

The host gives the worker a thread on start and signals the IStopToken on stop, then waits for Execute to return. IStopToken is just an alias for ICancellationToken. Waiting on the token with ICancellation​Token.​WaitFor instead of sleeping is what makes shutdown immediate: a stop request releases the wait at once, wherever the loop happens to be. Pass the token down into long units of work so they can check IStopToken.​IsStop​Requested and give up in the middle instead of holding up the shutdown.

An exception escaping Execute is logged and brings the whole host down with exit code 1, rather than leaving a process that is up and doing nothing.

A worker can also be hosted alone, with RunHost(TNightlyJobsWorker). See Background Jobs for periodic jobs and for more about writing workers.

Custom hosted services

Anything that implements IHostedService - a Start that returns once the thing is up and a Stop that brings it down - can be added to the builder. Raising from Start aborts the startup as described above.

Running as a Console Application

Started with no command-line switch, the executable runs as a console application on both Windows and Linux. Log lines go to standard output, errors to standard error. Ctrl+C, Ctrl+Break and closing the console window (on Windows) or SIGINT/SIGTERM (on Linux) request a graceful shutdown: the hosted items are stopped in reverse order, requests in progress are allowed to finish, and the process exits.

This is the development experience: press F9 in the IDE and you get a console with live output, on either platform.

Running as a Windows Service

The same executable registers itself with the Service Control Manager. From an elevated command prompt:

MyServer.exe --install

This creates a service set to start automatically, named after the executable (or after IHostBuilder.​Service​Options when given), whose registered command line is the executable plus --service. Start and stop it like any other service:

sc start MyServer
sc stop MyServer

or from the Services console. To remove the registration:

MyServer.exe --uninstall

A service has no console, so log lines are written to a file: the path given by --log=<path>, or a .log file beside the executable when the switch is absent. Pass --log= together with --install and the path is carried into the registered command line:

MyServer.exe --install --log=C:\Logs\MyServer.log

The --service switch itself is not meant to be typed: it tells the host to connect to the Service Control Manager rather than to the console, and --install puts it in the registered command line. If it is given from a terminal, the host logs a warning and continues in console mode.

Note

A Windows service runs under a service account (Local System by default) with C:\Windows\System32 as its working directory. Use absolute paths for anything the server reads or writes, and remember that an Http.Sys base URL on a host other than localhost needs a URL reservation for that account. The socket dispatcher needs no reservation.

Running as a systemd Daemon

On Linux, the executable prints a ready-to-use unit file for itself:

./myserver --systemd-unit | sudo tee /etc/systemd/system/myserver.service

Only the unit file goes to standard output, so the pipe above produces a valid unit; the usage hints are printed to standard error. The unit looks like this:

[Unit]
Description=My API Server
After=network.target

[Service]
Type=exec
ExecStart=/opt/myserver/myserver
WorkingDirectory=/opt/myserver
Restart=always
User=myserver
Group=myserver

[Install]
WantedBy=multi-user.target

The unit runs the server as a dedicated user named after the service. Create that user, then enable and start the service:

sudo useradd -r myserver
sudo systemctl daemon-reload
sudo systemctl enable --now myserver

Under systemd standard output and standard error are the journal, so the log lines are read with journalctl -u myserver -f. systemctl stop sends SIGTERM, which the host turns into the same graceful shutdown as Ctrl+C.

The switch also works on Windows, for generating the unit on the machine that builds the binary: the ExecStart and WorkingDirectory paths are then placeholders under /opt, to be edited to the deployed location.

Note

Binding to ports below 1024 (like 80 and 443) requires privileges. Either grant the binary the required capability (sudo setcap cap_net_bind_service=+ep /opt/myserver/myserver), listen on a high port behind a reverse proxy, or run the service as root.

Command-Line Switches

Run the executable with --help to see the switches it accepts. Each switch is also accepted as -name and /name.

Switch Description
(none) Runs as a console application until Ctrl+C.
--help Prints the list of switches.
--install Windows only. Registers the executable as a service set to start automatically. Needs an elevated prompt.
--uninstall Windows only. Removes the service registration. A running service is marked for deletion and disappears once it stops.
--log=<path> Windows only. Where log lines go when running as a service. Default: a .log file beside the executable. Carried into the registered command line when given with --install.
--systemd-unit Prints a systemd unit file for this executable to standard output.
--service Windows only. Connects to the Service Control Manager instead of the console. Put in the command line by --install.

The maintenance switches (--help, --install, --uninstall, --systemd-unit) run and exit before anything is hosted: --install does not start the server it is installing.

Logging

A service has no standard output: under the Service Control Manager the standard handles are invalid and WriteLn raises I/O error 105. An application that is going to be hosted therefore cannot write with WriteLn, however harmless it looks while it is still being run from a terminal. Use Log and LogError from unit Sparkle.Host.Log instead:

uses
  Sparkle.Host.Log;

Log('Cache warmed up: ' + IntToStr(Count) + ' entries.');
LogError('Could not reach the payment gateway: ' + E.Message);

Each line is prefixed with a timestamp and a level:

2026-09-10 14:02:31 [info] Now listening on: http://localhost:2001/tms/sparkle

Where the line goes depends on how the process is running:

  • Console: Log writes to standard output, LogError to standard error, so a script that redirects 2> can tell failures from information.
  • Windows service: both are appended to the file returned by LogFileName, UTF-8 encoded.
  • systemd: standard output and error are the journal, so the console behavior is the right one.

This logging is deliberately minimal and self-contained. It is not a replacement for TMS Logging or any other logging framework you already use for your application: those keep working as before, from any mode.

Choosing the Dispatcher

The host never touches the dispatcher components: which dispatcher a server uses, and when it starts, is decided by your code - typically in the data module's OnCreate, as in the code generated by the wizard. Both dispatcher components are on the module, so switching is a matter of which lines are active:

On Linux, the socket dispatcher requires Delphi 10.4 Sydney or newer; on Windows it is available on every Delphi version supported by Sparkle (see the note in the Socket Server chapter).

Reporting What Is Listening

ReportActive​Dispatchers (unit Sparkle.​Host.​Components) logs one Now listening on: <url> line for each server component connected to an active dispatcher component owned by the given component, or a no active dispatchers line when there is none - which is how a forgotten Start shows up instead of staying silent. Call it as the last line of the module's OnCreate, after the dispatcher has been started. ActiveDispatcherUrls returns the same URLs as an array, for code that wants to do something else with them.

Reloading Configuration

Both operating systems have a long-standing way of asking a service to reload its configuration without restarting: SERVICE_CONTROL_PARAMCHANGE on Windows and SIGHUP on Linux. OnReload (unit Sparkle.Host.Reload) registers a block that runs when either arrives; the application never learns which one it was:

uses
  Sparkle.Host.Reload;

OnReload(
  procedure
  begin
    Settings.LoadFromFile(SettingsFile);
    SocketDispatcher.Server.ReloadCertificates;
  end);

Trigger it with sc control MyServer paramchange on Windows or systemctl kill -s HUP myserver on Linux. The block runs on the thread that received the request and must return quickly; an exception escaping it is logged and swallowed, since a failed reload is no reason to bring the service down. Registering nothing costs nothing: a service with no reload handler does not advertise the capability, and no signal handler is installed.

Stopping from Code

RequestShutdown (unit Sparkle.​Host.​Shutdown) requests the same graceful shutdown that Ctrl+C or a stop control would, from inside the process - for example when the application decides an error is unrecoverable. It is safe to call from any thread, and more than once.

Using the Shutdown Support Alone

The graceful-shutdown mechanics can be used without the rest of the host, in an existing console program that only wants to stop cleanly on Ctrl+C and SIGTERM. Call InstallShutdown​Handlers before starting the server and WaitForShutdown instead of ReadLn:

uses
  Sparkle.Host.Shutdown;

begin
  InstallShutdownHandlers;
  Server := TSocketHttpServer.Create;
  try
    Server.AddModule(TMyServerModule.Create('http://localhost:8080/myapi'));
    Server.Start;
    WaitForShutdown;
  finally
    Server.Free;
  end;
end.

Such a program runs as a console application and as a systemd daemon. Only the Windows service lifetime needs the full host.

Migrating Existing Servers

From the deprecated server wizards

The previous TMS Sparkle Server, TMS XData Server and TMS RemoteDB Server wizards - now listed under TMS BIZ (Deprecated) as "... Server Application (Deprecated)" - generated one project per target (VCL, FMX, Windows service, console) around a shared Server unit exposing StartServer and StopServer procedures. To move such a solution to the host:

  1. Create a new console project (or convert the existing console one) with a program body that hosts a block calling those procedures:

       uses
         Sparkle.Host,
         Sparkle.Host.Shutdown,
         Server in 'Server.pas';
    
       begin
         RunHost(
           procedure
           begin
             StartServer;
             try
               WaitForShutdown;
             finally
               StopServer;
             end;
           end);
       end.
    
  2. Delete the VCL, FMX and service projects. The one console executable now covers development, Windows service and systemd deployment.

  3. Replace /install and /uninstall (the TService switches) with --install and --uninstall in your deployment scripts.

  4. Optionally, replace the THttpSysServer in the Server unit with a TSocketHttpServer to run the same binary on Linux.

From Sparkle.App

The Sparkle.App units (introduced in version 3.18 and now deprecated) hosted a data module with server components, picked a dispatcher automatically (Http.Sys on Windows, Indy elsewhere) and ran as a VCL form in Debug builds or as a Windows service in Release builds. To migrate:

  1. Keep the data module. Drop a dispatcher component on it (TSparkleSocket​Dispatcher or TSparkleHttp​SysDispatcher), connect the server components to it through their Dispatcher property, and start it in OnCreate followed by ReportActive​Dispatchers. The host does not pick a dispatcher for you.
  2. Replace the program body with RunHost(TServerModule) and remove Sparkle.App and Vcl.Forms from the uses clause.
  3. Move the SparkleAppConfig.WinService name, display name and description to IHostBuilder.​Service​Options.
  4. The run mode is no longer tied to the build configuration or to the -standalone switch: a build runs as a console application unless started by the Service Control Manager with --service. Install with --install instead of /install.
  5. Server modules are no longer loaded from packages in the sparkle_modules folder. Add their units to the project (or add several data modules to the host builder) instead.
  6. Output that went to the VCL memo or the TMS Logging console handler goes to Log, or to whatever logging framework the application uses.

From a TService project

Move the bodies of ServiceStart and ServiceStop into a hosted block (or into a data module's OnCreate and OnDestroy), replace the TService descendant and Vcl.SvcMgr with RunHost, and use --install instead of /install. The code that runs the server is unchanged; what disappears is the service plumbing, and what is gained is the console and systemd modes.

Demo

A complete example - an HTTP server data module and a background worker in one process, with service options set - is available in the TMS Sparkle distribution at demos/host.