Habari STOMP Client libraries release 2023.06

Habarisoft released new versions of its native STOMP client libraries for Delphi / Object Pascal for integration with popular open source message brokers:

This version contains a new decoder for STOMP header lines.

Full release notes can be found at: https://www.habarisoft.com/release_notes.html

Home page and demo applications

Home page: https://www.habarisoft.com/

About Habari STOMP Client libraries

habari_logo_2016

Habari STOMP Client libraries enable Object Pascal applications to take advantage of message broker / message queue technology – which is distributed, loosely coupled, reliable and asynchronous – to build integrated systems, using peer-to-peer and publish-subscribe communication models.

The libraries are available for the open source message brokers ActiveMQ, ActiveMQ Artemis, Eclipse OpenMQ and RabbitMQ. ActiveMQ and OpenMQ are also serving as default messaging subsystems in Enterprise Application Servers such as GlassFish, Payara, TomEE+ or WildFly.

Typical use cases of message brokers include: load balancing (distributed processing), client notification in multi-tiered applications, dynamically configuring and controlling software, alerting and logging, and integration with ISAPI, PHP, .NET, Python or Jakarta EE based web applications

Advertisement

RabbitMQ: Serving Millions of Clients with Native MQTT

In a recent blog article, it was announced that the rewritten MQTT plugin included in the upcoming version 3.12 of the RabbitMQ open source message broker (Copyright © 2007-2021 VMware, Inc.) will reduce MQTT resource usage considerably (source).

“Native MQTT shipping in 3.12 turns RabbitMQ into an MQTT broker. It allows connecting millions of clients to RabbitMQ. Even if you do not plan to connect that many clients, by upgrading your MQTT workload to 3.12, you will substantially save infrastructure costs because memory usage drops by up to 95%.”

https://blog.rabbitmq.com/posts/2023/03/native-mqtt/
Test 1: Memory usage connecting 1 million MQTT clients (Copyright © 2007-2021 VMware, Inc.)

Test uses a 3-node cluster, which requires 108.0 + 100.7 + 92.4 = 301.1 GiB in 3.11 and only 6.1 + 6.3 + 6.3 = 18.7 GiB of memory in 3.12.

Test 2: 100k publishers, 100k subscriberss (Copyright © 2007-2021 VMware, Inc.)

Test 2 uses a 3-node-cluster, which requires 21.6 + 21.5 + 21.7 = 64.8 GiB in 3.11 and only 2.6 + 2.6 + 2.6 = 7.8 GiB of memory in 3.12.

ScroogeXHTML for Object Pascal 8.3 released

ScroogeXHTML for Object Pascal is a library which supports a subset of the Rich Text Format (RTF) standard. It converts RTF to HTML5 and XHTML stand-alone documents, or to fragments which can be embedded in other documents. The library is compatible with Delphi 2009+ and Free Pascal 3.2.0.

The 8.3 release adds basic Linux platform support (Free Pascal only).

Resources

Other platforms

ScroogeXHTML is also available for the Java platform.

scrooge_portrait_logo_2016

Daraja HTTP Framework 2.5 released

dj

The Daraja HTTP Framework is a free open source library for Object Pascal (Free Pascal 3.2.0, Delphi 2009+), based on the stand-alone HTTP server component in Internet Direct (Indy).

This release includes a new example application which demonstrates server-sent events.

More information

– GitHub: https://github.com/michaelJustin/daraja-framework
– API documentation: https://michaeljustin.github.io/daraja-framework/
– Resources: https://www.habarisoft.com/daraja_framework.html
– Wiki: https://github.com/michaelJustin/daraja-framework/wiki

How to: read a snowman ☃ – terminated text from a TCP socket in Delphi and Free Pascal

Full source code now available on GitHub. Covering Internet Direct (Indy), Ararat Synapse, and Synapse SynCrtSock.

This is the second part in a series which explores basic TCP socket use cases and presents them in minimal examples.

For this part, I selected an unusual string terminator byte sequence. The Unicode Character “☃” (U+2603), named Snowman, is not used in any protocol I am aware of. Encoded in UTF-8 it is three bytes long1. Some background: many Internet protocols use carriage return (octet 13) and/or line feed (octet 10) as line terminator. Therefore, some of the TCP socket libraries offer read methods with a configurable line terminator, but limited to these two. However, some (text-oriented) protocols use a different terminator. A notable example is STOMP, which specifies that the content must be terminated with a NULL byte2.

1) In UTF-8 encoding, Snowman is the byte sequence 0xE2 0x98 0x83.

2) See Stomp Protocol Specification Version 1.2.

Note: terminators with three or more bytes are supported by the provided code, but very unusual: every additional byte will slow down the request processing considerably.

The clients are tested with a small console application (see below).

program FixedDelimiterClient;

uses
  ClientIndySockets10,
  //ClientSynapse266,
  //ClientSynopseCrtSock,
  SysUtils;

const
  FIXED_DELIMITER = '' + #$2603; // Snowman
  // ☃
  SERVER_HOST = '127.0.0.1';
  SERVER_PORT = 30000;

  procedure Test(ADelimiter: string);
  var
    Response: string;
  begin
    WriteLn(Format('try to read from %s:%d delimited with %s',
      [SERVER_HOST, SERVER_PORT, ADelimiter]));
    Response := ReadDelimited(SERVER_HOST, SERVER_PORT, Utf8Encode(ADelimiter));
    WriteLn(Format('received response "%s" - %d bytes',
      [Response, Length(Response)]));
  end;

begin
  try
    Test(FIXED_DELIMITER);
    Test(FIXED_DELIMITER);
    Test(FIXED_DELIMITER);
  except
    on E: Exception do
    begin
      WriteLn(E.Message);
    end;
  end;
  ReadLn;
end. 

The Internet Direct (Indy) client source code:

function ReadDelimited(AHost: string; APort: Integer; ATerminator: RawByteString): string;
var
   Client: TIdTCPClient;
begin
   Client := TIdTCPClient.Create;
   try
     Client.Host := AHost;
     Client.Port := APort;
     Client.Connect;
     Result := Client.IOHandler.ReadLn(ATerminator, IndyTextEncoding_UTF8);
   finally
     Client.Free;
   end;
end;

Notes

How to: read a fixed number of bytes from a TCP socket in Delphi and Free Pascal

Full source code now available on GitHub. Covering Internet Direct (Indy), Ararat Synapse, and Synapse SynCrtSock.

This is the first part in a series which explores basic TCP socket use cases and presents them in minimal examples, with useful comments regarding not-so-obvious requirements and pitfalls (a.k.a surprising results).

The clients are tested with a small console application (see below).

program FixedLengthClient;

uses
  ClientMainIndy10,
  SysUtils;

const
  CONTENT_LENGTH = 8192;
  SERVER_HOST = '127.0.0.1';
  SERVER_PORT = 30000;

  procedure Test(AExpectedLength: Integer);
  var
    Response: TBytes;
  begin
    WriteLn(Format('try to read %d bytes from %s:%d',
      [AExpectedLength, SERVER_HOST, SERVER_PORT]));
    Response := Read(SERVER_HOST, SERVER_PORT, AExpectedLength);
    WriteLn(Format('received %d bytes', [Length(Response)]));
  end;

begin
  try
    Test(CONTENT_LENGTH);
    Test(CONTENT_LENGTH - 1);
    Test(CONTENT_LENGTH + 1); // (surprise me)
  except
    on E: Exception do
    begin
      WriteLn(E.Message);
    end;
  end;
  ReadLn;
end. 

The first client uses Internet Direct (Indy). Here is the source code:

(*
  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at http://mozilla.org/MPL/2.0/.
*)

unit ClientMainIndy10;

interface

uses
  SysUtils;

function Read(AHost: string; APort: Integer; ALength: Integer): TBytes;

implementation

uses
  IdTcpClient, IdGlobal, Classes;

function Read(AHost: string; APort: Integer; ALength: Integer): TBytes;
var
   Client: TIdTCPClient;
begin
   SetLength(Result, ALength);
   Client := TIdTCPClient.Create;
   try
     Client.Host := AHost;
     Client.Port := APort;
     Client.Connect;
     Client.IOHandler.ReadBytes(Result, Length(Result), False);
   finally
     Client.Free;
   end;
end;

end.

Notes

How to: Server-Sent Events with Indy HTTP Server (part 3)

In this part, the server application uses the Indy HTTP server uses SSE to continuously send events to the JavaScript EventSource.


Part 3: the demo application, now streaming

Ingredient #1: the HTML page with JavaScript

The script has not changed, it reads two data items from the ping event:

  • a time stamp in ISO 8601 format
  • the peer data, which is the IP address and port number
<!DOCTYPE html>
<html>
	<head>
		<title>SSE example</title>
	</head>
	<body>
		<script>
		const evtSource = new EventSource("sse");
		
		evtSource.addEventListener("ping", (event) => {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");
  const time = JSON.parse(event.data).time;
  const peer = JSON.parse(event.data).peer;
  newElement.textContent = `ping at ${time} from ${peer}`;
  eventList.appendChild(newElement);
});

		</script>
		<ul id="list">

		</ul>
	</body>
</html>

The code is based on the article Using server-sent events on MDN: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events

Ingredient #2: server side code

The TIdHTTPServer subclass contains a method to provide client-specific data in the /sse resource. When invoked, it will return a single ping event with a JSON payload, containing the peer IP and port number, and a timestamp:

  function TMySSEServer.BuildContentText(
    AContext: TIdContext): string;
  begin
    Result := 'event: ping' + #13
      + Format('data: {"time": "%s", "peer": "%s:%d"}',
      [DateToISO8601(Now, False),
      AContext.Binding.PeerIP,
      AContext.Binding.PeerPort]) + #13#13;
  end; 

The DoCommandGet method uses the BuildContentText function to provide the event data, and simulates work by sleeping for a random time interval.

Note

  • The data stream is running in an endless loop (repeat ... until false).
  • Because the method never terminates, the method calls AResponseInfo.WriteHeader to send the HTTP headers to the client (line 13).
  • Neither ContentText nor ContentStream can be used to send data to the client. Instead, the event data must be sent by using the Write.. methods of the connection’s IOHandler (line 16).
 procedure TMySSEServer.DoCommandGet(AContext: TIdContext;
    ARequestInfo: TIdHTTPRequestInfo;
    AResponseInfo: TIdHTTPResponseInfo);
  var
    Data: string;
  begin
    AResponseInfo.CharSet := 'UTF-8';
    if ARequestInfo.Document = '/sse' then
    begin
      AResponseInfo.ContentType := 'text/event-stream';
      AResponseInfo.CacheControl := 'no-store';
      AResponseInfo.ContentLength := -2;
      AResponseInfo.WriteHeader;
      repeat
        Data := BuildContentText(AContext);
        AContext.Connection.IOHandler.Write(Data);
        Sleep(Random(1000));
      until False;
    end
    else
    begin
      AResponseInfo.ContentType := 'text/html';
      AResponseInfo.ContentStream := TFileStream.Create('index.html', fmOpenRead);
    end;
  end;

Output

When the browser navigates to http://localhost, the server will provide the HTML and the embedded JavaScript will start reading event data from http://localhost/sse:

Notable difference from the previous version:

  • The server sends a continuous stream of events as response to the HTTP GET request to the /sse resource.
  • The length of the response is unknown (it is virtually unlimited), therefore the HTTP response must not contain a content-length header.
  • The connection will not be closed after sending one or more events.
  • The client will only retry (reconnect and send a new request), if the the server disconnects its end of the connection, or no data is received and the connection times out.

Diagnostics

To see the full response of the server to the GET request, you may use

curl -v -N localhost/sse

Example:

Updates

Example project source code is now on GitHub

The complete code for all three projects is now available on GitHub at https://github.com/michaelJustin/indy-snippets

Example project for Daraja HTTP Framework

There is a new example project in the develop branch of Daraja. URL: https://github.com/michaelJustin/daraja-framework/tree/develop/demo/server-sent%20events

How to: Server-Sent Events with Indy HTTP Server (part 2)

In this second part, a server application uses the Indy HTTP server to provide a HTML page which uses SSE to update its content with data sent from the server.

Part 2: the basic demo application, some client data added

Ingredient #1: the HTML page with JavaScript

The script now reads two data items from the ping event:

  • the time stamp, now sent from the server in proper ISO 8601 format
  • the peer data, which is the remote ip address and port number
<!DOCTYPE html>
<html>
	<head>
		<title>SSE example</title>
	</head>
	<body>
		<script>
		const evtSource = new EventSource("sse");
		
		evtSource.addEventListener("ping", (event) => {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");
  const time = JSON.parse(event.data).time;
  const peer = JSON.parse(event.data).peer;
  newElement.textContent = `ping at ${time} from ${peer}`;
  eventList.appendChild(newElement);
});

		</script>
		<ul id="list">

		</ul>
	</body>
</html>

The code is based on the article Using server-sent events on MDN

https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events

Ingredient #2: server side code

The TIdHTTPServer subclass now contains a private method to provide client-specific data in the /sse resource.

  function TMySSEServer.BuildContentText(AContext: TIdContext): string;
  begin
    Result := '';
    repeat
      Result := Result + 'event: ping' + #13 +
        Format('data: {"time": "%s", "peer": "%s:%d"}',
        [DateToISO8601(Now, False), AContext.Binding.PeerIP,
        AContext.Binding.PeerPort]) + #13#13;
      Sleep(100);
    until Random < 0.8;
  end; 

The DoCommandGet method uses the BuildContentText function to provide the event data:

  procedure TMySSEServer.DoCommandGet(AContext: TIdContext;
    ARequestInfo: TIdHTTPRequestInfo;
    AResponseInfo: TIdHTTPResponseInfo);
  begin
    if ARequestInfo.Document = '/sse' then
    begin
      AResponseInfo.ContentType := 'text/event-stream';
      AResponseInfo.CacheControl := 'no-store';
      AResponseInfo.ContentText := BuildContentText(AContext);
    end
    else
    begin
      AResponseInfo.ContentType := 'text/html';
      AResponseInfo.ContentStream :=
        TFileStream.Create('index.html', fmOpenRead);
    end;
    AResponseInfo.CharSet := 'UTF-8';
  end;

Output

When the browser navigates to http://localhost, the server will provide the HTML and the embedded JavaScript will start reading data from the address http://localhost/sse and receive one or more events.

The ping event, which the server sends to the browser, now includes the server time in ISO 8601 format and the peer IP address and port.

Next part

In the next part, the data stream will be sent continuously.

How to: Server-Sent Events with Indy HTTP Server (part 1)

In this article, a server application uses the Indy HTTP server to provide a HTML page which uses SSE to update its content with data sent from the server.

Server-Sent Events (SSE) is a server push technology enabling a client to receive automatic updates from a server via an HTTP connection, and describes how servers can initiate data transmission towards clients once an initial client connection has been established.

Server-sent events, https://en.wikipedia.org/w/index.php?title=Server-sent_events&oldid=1093881969 (last visited Sept. 17, 2022).

Part 1: a very basic demo application

Ingredient #1: a HTML page with Javascript

<!DOCTYPE html>
<html>
	<head>
	<title>SSE example</title>
	</head>
	<body>
	<script>
	const evtSource = new EventSource("sse");
		
	evtSource.addEventListener("ping", (event) => {
	  const newElement = document.createElement("li");
	  const eventList = document.getElementById("list");
	  const time = JSON.parse(event.data).time;
	  newElement.textContent = `ping at ${time}`;
	  eventList.appendChild(newElement);
	});
	</script>
	<ul id="list">
	</ul>
	</body>
</html>

The code is based on the article Using server-sent events on MDN

https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events

Ingredient #2: server side code

In the example application, a TIdHTTPServer subclass is used to deliver the HTML document when a browser accesses it.

Note that the server has to keep connections alive, so the Server property KeepAlive must be set to true:

  procedure Test;
  begin
    Server := TMySSEServer.Create;
    try
      Server.KeepAlive := True;
      Server.Startup;
      ReadLn;
    finally
      Server.Free;
    end;
  end;

The DoCommandGet method is overriden and looks like this:

  procedure TMySSEServer.DoCommandGet(AContext: TIdContext;
    ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  var
    S: TStream;
    C: string;
  begin
    if ARequestInfo.Document = '/sse' then
    begin
       AResponseInfo.ContentType := 'text/event-stream';
       AResponseInfo.CacheControl := 'no-store';
       AResponseInfo.CharSet := 'UTF-8';
      
         C := 'event: ping' + #13
           + Format('data: {"time": "%d"}', [GetTickCount]) 
           + #13#13;

       AResponseInfo.ContentText := C;
       AResponseInfo.ResponseNo := 200;
    end
    else
    begin
      S := TFileStream.Create('index.html', fmOpenRead);
      AResponseInfo.ContentType := 'text/html';
      AResponseInfo.ContentStream := S;
      AResponseInfo.ResponseNo := 200;
    end;
  end;

Output

When the browser navigates to http://localhost, the server will provide the HTML and the embedded JavaScript will start reading data from the address http://localhost/sse (and receive only one event). As specified in the HTML spec for EventSource, the client will retry after some seconds:

Each EventSource object has the following associated with it:
– A reconnection time, in milliseconds. This must initially be an implementation-defined value, probably in the region of a few seconds.

HTML – 9.2 Server-sent events (https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events)

Note: this is just the first step. In the next part, we will implement a continuos data stream.

ScroogeXHTML for Object Pascal 8.2 released

ScroogeXHTML for Object Pascal is a library which supports a subset of the Rich Text Format (RTF) standard. It converts RTF to HTML5 and XHTML stand-alone documents, or to fragments which can be embedded in other documents. The library is compatible with Delphi 2009+ and Free Pascal 3.2.0. The 8.2 release includes small improvements in the core library and documentation about the new post process API. The installer is now digitally signed.

Resources

Other platforms

ScroogeXHTML is also available for the Java platform.

scrooge_portrait_logo_2016