A new MaxLineLength property on IConnection replaces the HABARI_X_MAXLINELENGTH compile‑time switch — so one application can raise the limit for one connection without rebuilding the library.

Applies to The Indy transport. Synapse and mORMot2 accept the value and ignore it.

New API IMaxLineLength in BTCommInterfaces, implemented by every IConnection.

Supersedes HABARI_X_MAXLINELENGTH — still honoured, now with a deprecation warning.

The problem

Indy caps how long a single protocol line may be

STOMP is a line‑oriented protocol. Habari’s Indy transport reads a frame’s command and every header with IOHandler.ReadLn, one line at a time, up to the terminating line feed.

Indy’s TIdIOHandler guards those reads with MaxLineLength, which defaults to 16,384 bytes. When a single header line is longer than that, the read is abandoned and Indy raises:

EIdReadLnMaxLineLengthExceeded: Maximum line length exceeded.

16 KiB of headers is generous for everyday messaging, but some payloads blow past it: a signed token in a custom header, a long selector expression, a base64 blob carried as a header value, or a broker that echoes large routing metadata back on the frame. The frame body is not the issue here — bodies delimited by the null byte are already read without a line‑length cap. It is the header block that trips the limit.

The old way

A compile‑time switch, all or nothing

The original escape hatch was a conditional define. Adding HABARI_X_MAXLINELENGTH to a project’s compiler options made the Indy adapter set the IOHandler’s MaxLineLength to MaxInt straight after connecting — lifting the limit entirely.

project options → conditional defines

HABARI_X_MAXLINELENGTH

It worked, but it carried the awkwardness of every build‑flag feature:

  • Whole‑program, not per‑connection. Every connection in the process lost the guard, including ones talking to brokers you don’t control.
  • Only one value. MaxInt or the default — no room for a deliberate, bounded ceiling.
  • A rebuild to change your mind, and the switch had to be set in each IDE project and in the Ant build separately.
  • Invisible at the call site. Nothing in the connecting code hinted that the limit had moved.

The new way

One property, set before the connection opens

Every IConnection now also implements IMaxLineLength, a small capability interface declared in BTCommInterfaces:

type
  IMaxLineLength = interface(IInterface)
    // Maximum line length in bytes. 0 (default) uses the
    // networking library's own limit; MaxInt lifts it entirely.
    function GetMaxLineLength: Integer;
    procedure SetMaxLineLength(const Value: Integer);
    property MaxLineLength: Integer
      read GetMaxLineLength write SetMaxLineLength;
  end;

Query it from the connection with an as cast and set the value before Start:

Unit1.pas

uses
  BTConnectionFactory, BTMQInterfaces,
  BTCommInterfaces,          // <-- add this unit
  BTCommAdapterIndy;

var
  Factory: IConnectionFactory;
  Connection: IConnection;
begin
  Factory := TBTConnectionFactory.Create;
  Connection := Factory.CreateConnection;

    // raise the header line limit for this connection only
    (Connection as IMaxLineLength).MaxLineLength := 1024 * 1024;  // 1 MiB

  Connection.Start;
  // ... create sessions, producers, consumers as usual
end;

Semantics

  • 0 is the default and means “leave the networking library’s own limit in place” — 16 KiB on Indy.
  • Any value > 0 is applied to the Indy IOHandler right after the socket connects. MaxInt reproduces the old define’s behaviour; a bounded value such as 1–4 MiB keeps a sane ceiling against a runaway peer.
  • Set it before Connection.Start. The setter calls the same guard as ClientID; changing it on a started connection raises EMQException.
  • Indy only. The Synapse and mORMot2 adapters store the value but do not act on it, exactly as they treat ConnectTimeOut.

Before

Define HABARI_X_MAXLINELENGTH, rebuild the library, and every connection in the process runs unguarded at MaxInt.

After

Set MaxLineLength on the one connection that needs it, to the exact ceiling you want, in the code that opens it.

How it works

From the connection down to the socket

The value rides the same path the library already uses for ConnectTimeOut. Nothing new touches a networking‑library type outside the adapter layer.

  1. TBTConnection implements IMaxLineLength by delegating to a field on its base class, TBTAbstractConnection.
  2. When Start opens the transport, the connection pushes a non‑zero value onto Transport.CommAdapter.MaxLineLength — a property now on the shared IStompCommAdapter abstraction — just before it connects.
  3. The Indy adapter’s AfterSocketConnected hook reads it back and, if it is positive, assigns TCPClient.IOHandler.MaxLineLength.

Because the assignment happens in AfterSocketConnected — before the client sends CONNECT and reads the server’s CONNECTED reply — the raised limit is already in force for the very first frame the library parses.

Architecture

Adding the property to IStompCommAdapter keeps the rule the library is built on: no unit outside a BTCommAdapterBase* adapter makes a networking‑library call. The other adapters inherit a harmless no‑op implementation from the shared base class.

Migrating

Moving off the define

  1. Find the switch. It lives in your Delphi .dproj / Lazarus .lpi conditional defines — not in the library’s own build.
  2. Add the call. Put BTCommInterfaces in your uses clause and set (Connection as IMaxLineLength).MaxLineLength before Start, choosing a bounded ceiling rather than MaxInt where you can.
  3. Remove the define from every project and build script that carried it.

There is no flag day. If you leave HABARI_X_MAXLINELENGTH defined, it still seeds the default to MaxInt as before — the compiler will just remind you:

[dcc32 Warning] HABARI_X_MAXLINELENGTH is deprecated: set the
MaxLineLength property via IConnection (IMaxLineLength) instead

Because MaxLineLength defaults to 0 and only does anything when positive, code that never sets it — and never defined the symbol — behaves exactly as it does today.

Reference

At a glance

ItemDetail
MaxLineLengthInteger property on IConnection (via IMaxLineLength) and on IStompCommAdapter. Bytes. Default 0.
0Use the transport’s own limit (Indy: 16,384 bytes).
> 0Applied to the Indy IOHandler on connect. Governs the frame command and header lines.
TimingSet before Connection.Start. Later changes raise EMQException.
TransportsHonoured by Indy. Stored but unused by Synapse and mORMot2.
ProductsHabari STOMP Client for ActiveMQ, Artemis, OpenMQ and RabbitMQ.
ToolchainsDelphi 2009 and later; Free Pascal 3.2.
HABARI_X_MAXLINELENGTHDeprecated. Still seeds the default to MaxInt; emits a compiler warning.

Sizing tip

Lifting the cap to MaxInt means a malformed or hostile stream with no line feed can grow the read buffer until memory runs out. Prefer the smallest ceiling that comfortably fits your largest legitimate header block.

Habari STOMP Client

Feature note for the MaxLineLength connection property. The API lives in BTCommInterfaces; the Indy behaviour in BTCommAdapterBaseIndy.

© Michael Justin. STOMP Client Libraries for Delphi and Free Pascal