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.
MaxIntor 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
0is the default and means “leave the networking library’s own limit in place” — 16 KiB on Indy.- Any value
> 0is applied to the Indy IOHandler right after the socket connects.MaxIntreproduces 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 asClientID; changing it on a started connection raisesEMQException. - 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.
TBTConnectionimplementsIMaxLineLengthby delegating to a field on its base class,TBTAbstractConnection.- When
Startopens the transport, the connection pushes a non‑zero value ontoTransport.CommAdapter.MaxLineLength— a property now on the sharedIStompCommAdapterabstraction — just before it connects. - The Indy adapter’s
AfterSocketConnectedhook reads it back and, if it is positive, assignsTCPClient.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
- Find the switch. It lives in your Delphi
.dproj/ Lazarus.lpiconditional defines — not in the library’s own build. - Add the call. Put
BTCommInterfacesin yourusesclause and set(Connection as IMaxLineLength).MaxLineLengthbeforeStart, choosing a bounded ceiling rather thanMaxIntwhere you can. - 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
| Item | Detail |
|---|---|
MaxLineLength | Integer property on IConnection (via IMaxLineLength) and on IStompCommAdapter. Bytes. Default 0. |
0 | Use the transport’s own limit (Indy: 16,384 bytes). |
> 0 | Applied to the Indy IOHandler on connect. Governs the frame command and header lines. |
| Timing | Set before Connection.Start. Later changes raise EMQException. |
| Transports | Honoured by Indy. Stored but unused by Synapse and mORMot2. |
| Products | Habari STOMP Client for ActiveMQ, Artemis, OpenMQ and RabbitMQ. |
| Toolchains | Delphi 2009 and later; Free Pascal 3.2. |
HABARI_X_MAXLINELENGTH | Deprecated. 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