You are not logged in.
Pages: 1
Both findings confirmed and fixed in e005b09e - test-first: the new suite
cases reproduced exactly your matrix (400 on async, no status at all on
sync, and the 0 vs -1 event length) before the fix.
- A chunked body over MaximumAllowedContentLength now gets a clean 413 on
both families before the connection closes. As a side effect this also
fixes a pre-existing quirk where the async server reported its 1GB
in-memory overflow as 400.
- OnBodyDownload now receives aContentLength = -1 for chunked bodies on
both families, and the doc says so.
The ~12s deferred cleanup you measured is the async connection GC, as
documented. Thanks for the Int64/4.5GB datapoint - good to have that
confirmed on real hardware rather than just by arithmetic.
@zen010101 could you please run a second test?
Great report, thanks.
Your ENOSPC observation is implemented in fcd6afed: when writing to the
spool stream fails, both server families now answer 507 Insufficient
Storage before closing, instead of the raw reset (new
HTTP_INSUFFICIENTSTORAGE constant + StatusCodeToText entry). One caveat:
on THttpServer the 507 is deterministic (the body is fully read before
the failing write), on THttpAsyncServer it is best effort - the failure
happens mid-transfer, so if the client is still blasting the body, the
reset may still win the race. A full lingering-close would be the next
step if that turns out to matter in practice - your curl test would tell.
Covered in the regression tests with a stream that fails after 64KB.
The 7-22s cleanup delay after a hard disconnect is the async connection
instance GC - expected, and now documented in the event comment.
* Done in 92470c90 https://github.com/landrix/mORMot2/comm … 54d9abce28 :
both hooks now live in virtual sub-methods
(THttpAsyncServerConnection.DoBodyDownload and
THttpServerSocket.DoBodyDownload), and the managed locals are out of the
OnRead / GetRequest hot paths.
* Regarding THttpSocket.Destroy calling Http.ProcessDone: I checked every
place that sets rfContentStreamNeedFree.
In the THttpSocket hierarchy, the flag is only set for streams created by
the server:
* ContentFromFile responses
* the progressive static-file path
* the new OnBodyDownload spool
The first two are released through ProcessDone in the
THttpServer.Process finally block. The body-download spool is released in
DownloadBody.
THttpClientSocket never assigns Http.ContentStream; client downloads use
the GetBody(DestStream) parameter, and ownership stays with the caller.
So the new call should be a no-op everywhere except for the case it is meant
to cover: a connection being closed after the event in GetRequest, but
before deferred body retrieval by the thread pool or THttpServerResp.
I would still appreciate a second check in case I missed an ownership path.
* On stream lifetime, nginx as a reverse proxy spools request bodies to a
temporary file under client_body_temp_path. proxy_request_buffering is
enabled by default.
client_body_in_file_only has two relevant modes:
* clean: nginx deletes the file when the request is complete
* on: nginx leaves the file in place, and the application becomes
responsible for it
In the latter case, the application receives the path through
$request_body_file.
The current branch effectively implements clean while exposing the file
name through InContent, similar to $request_body_file.
For customizable lifetime handling, I prefer the
TFileStreamEventuallyDelete approach because it would remove the need for
ContentInputName entirely.
TFileStreamEventuallyDelete = class(TFileStreamEx)
It would delete the file on destruction unless DeleteFileOnDestroy is set
to false.
The event would return this stream, or any other TStream as it does today.
The server would free it through the existing rfContentStreamNeedFree paths,
while the deletion policy would stay with the stream class.
The default behavior would match nginx's clean mode. A handler that wants
to keep the file could set DeleteFileOnDestroy to false or rename the file
first, which would correspond to nginx's on mode.
It would also be useful to let the handler reuse the already-open stream
instead of reopening the file by name. Once the body is complete, the
server could rewind the stream and expose it on the request, for example
through a new property on THttpServerRequestAbstract:
InContentStream: TStream
The connection would remain responsible for freeing it after the request.
InContent could still contain the file name with the STATICFILE marker,
serving as the $request_body_file equivalent.
This would also remove the current free-before-Prepare sequence in
DoRequest, because the input stream would move from Http.ContentStream to
the request instance before response processing begins.
If this design looks right, I will implement it this way. You can choose the
class name and location; mormot.core.os, next to TFileStreamEx, seems like the
natural place.
The server side is ready for testing:
https://github.com/landrix/mORMot2/tree … put-stream
I added a new THttpServerGeneric.OnBodyDownload event for both THttpServer and THttpAsyncServer. The event returns a TStream, for example a TFileStreamEx pointing to a temporary file, and the request body is written there without keeping it in memory.
The request then reaches the callbacks with InContentType = STATICFILE_CONTENT_TYPE and InContent containing the file name, similar to how file-based responses are handled. The temporary file is deleted after the request unless the handler renames or moves it.
Compressed request bodies currently return 415 with Accept-Encoding: identity. Chunked bodies are limited by MaximumAllowedContentLength, since their final size is not known in advance. The previous 1 GB in-memory limit does not apply to streamed bodies.
While testing this, I found two existing bugs in the asynchronous request parser and fixed them in the same branch:
* THttpRequestContext.ProcessRead did not consume the actual chunk data from the input buffer. As a result, chunked uploads to THttpAsyncServer could stall or be rejected.
* DoProcessParseLine could read one byte beyond the buffer when a network packet ended exactly on the #13 of a CRLF sequence.
There is also a new TNetworkProtocols.BodyDownload test with 98 assertions. It covers an 8 MB spooled upload, chunked uploads through a raw socket, multipart decoding directly from the spooled file using THttpMultiPartDecoder, rejected compressed bodies, cleanup after aborted uploads, and both server implementations.
The tests are green with FPC on aarch64-linux and Delphi Win32/Win64.
@zen010101: this is the branch to try on your machines. Disk-full situations, slow connections and interrupted uploads are the cases I would especially like to see tested.
I will open the PR after getting some real-world feedback here.
Thanks for the quick review and merge.
I can take the server-side implementation next, based on the approach outlined earlier in this thread:
* Add a `THttpServerGeneric` callback that is invoked after the request headers have been parsed, but before the body is read. The callback would return the destination `TStream` for the request body; returning `nil` would preserve the existing in-memory behavior. Most of the required plumbing is already in place, since `THttpRequestContext.ProcessRead` writes to `ContentStream` when one is assigned.
* Use a spooled temporary file as the default stream implementation. This should also work safely with `THttpAsyncServer` and its relatively small worker pool. `TPipeStream` could remain available for blocking server implementations.
* Expose the uploaded body to the handler using `InContentType = STATICFILE_CONTENT_TYPE` and `InContent` set to the temporary file path, mirroring the existing response-side mechanism.
* Wrap this at the REST layer through a `TRestUriContext.InStream` property.
* Extend SOA method handling so that an interface method with a single `TStream` parameter can consume the request body directly.
* In streaming mode, reject compressed request bodies with HTTP `415 Unsupported Media Type` and return `Accept-Encoding: identity`.
* Feed the resulting stream into `THttpMultiPartDecoder`, allowing multipart/form-data uploads of arbitrary size with constant memory usage. This should also address issue #292.
Does this match the architecture you had in mind?
I would follow the same workflow as before: agree on the public interface here first, then implement it on a branch together with the relevant tests.
The same license as mormot2 is the best solution.
@flydev could you please add a license at https://github.com/flydev-fr/mormot.ai/ … mot-ai-sdk?
Thanks @flydev for posting this; I’ll consolidate everything, and then we’ll see what the next steps are.
Done as suggested: I added a new TTestCoreBase.MultiPartDecoder method and moved the multipart tests there from MimeTypes. MimeTypes now only covers MIME recognition, while the MIM test data array is shared by both.
The existing encoder round-trips are now also decoded incrementally by THttpMultiPartDecoder. This includes Rfc2388NestedFiles=true, where a second decoder reads the nested multipart/mixed directly from the outer Current.Content stream.
I also added decoder-specific tests for a 300 KB section containing many CRLF--boundary near-matches, several work-buffer sizes, a source returning only 1–7 bytes per Read(), delimiters split at every buffer position, token and quoted-pair parameters, padding, preamble and epilogue, the header-line limit, truncated input raising EHttpMultiPart, and edge cases for MultiPartFormDataBoundary().
1,310 assertions, green with FPC aarch64-linux and Delphi Win32/Win64.
the whole TTestCoreBase suite still passes on both.
One issue I ran into: Current.Content.Size reports the number of bytes read so far because the section length is not known in advance. Therefore Delphi's CopyFrom(src, 0) silently copies nothing. The documentation recommends StreamCopyUntilEnd() or a normal Read() loop instead, as already done in mormot.crypt.core and mormot.core.zip.
Two questions before I open the PR:
* Current is a record property, so each access copies its four RawUtf8 fields. The documentation recommends caching Current.Content in a local variable. Would a pointer-returning getter, similar to PHttpMultiPartStreamSection, be preferable?
* MultiPartFormDataBoundary() currently sits in mormot.net.client next to the decoder. Would it fit better in mormot.core.buffers next to the other MultiPartFormData* functions, so it could also be reused by MultiPartFormDataDecode?
Quick update: the first `THttpMultiPartDecoder` version is working and passes the round-trip tests.
Before opening the PR: where should the decoder tests go?
Extend the existing multipart tests in test.core.base.pas or add them to test.net.proto.pas, next to the implementation?
Thanks for the detailed analysis.
A few thoughts:
Dedicated event: If it receives the parsed headers and Content-Length and returns the destination TStream (or nil for the current in-memory behavior), the application can choose between a temporary TFileStreamEx and a TPipeStream per request. The main DoS consideration could then be documented: pipes should be fine with THttpServer, where each connection has its own thread, or with non-blocking/Pending-style consumption; a temporary file would be the safer default for THttpAsyncServer.
Using STATICFILE_CONTENT_TYPE for the input side makes sense and mirrors the existing output logic. Exposing it through TRestUriContext.InStream, together with detecting a single TStream parameter at the SOA level, should also avoid exposing two competing content types to users.
415 with "Accept-Encoding: identity" for compressed input sounds good. Explicit gzip support through TSynZipDecompressor could always be added later if there is actual demand.
The server-side foundation still depends on the event and stream design, but THttpMultiPartDecoder seems fairly self-contained. I could prepare a PR for an incremental boundary scanner operating on any TStream, processing one part at a time, roughly like Go's mime/multipart.Reader. This would let handlers consume a spooled or piped request body without loading the whole multipart payload into memory.
I would include round-trip tests against THttpMultiPartStream, covering preamble and epilogue handling, CRLF edge cases, nested content-type parameters, and boundaries split across read buffers.
Would you prefer it in mormot.net.http, or in mormot.core.buffers next to MultiPartFormDataDecode?
For the server part, I can then adapt to whichever event/stream design you choose.
Hi ab,
I'd like to pick up GitHub issue #292 and ask whether a PR in this direction would be welcome before I start coding.
The goal is to support server-side uploads larger than 2 GB without buffering the complete request body in RAM.
As far as I can see:
THttpRequestContext.ProcessRead already supports writing the request body to ContentStream, for both Content-Length and chunked transfer encoding.
However, neither THttpServer nor THttpAsyncServer assigns ContentStream for incoming requests. Request bodies therefore end up in the Content buffer and are limited by MaxHttpInMemSize.
MultiPartFormDataDecode also expects the complete multipart body in memory.
We currently work around this with application-level chunking: create an upload, send multiple smaller PUT requests, then finalize it. This works well, but it is a custom protocol and cannot be used by normal multipart clients such as HTML forms, curl -F, or third-party applications.
Because THttpAsyncServer calls the request handler only after the body has been received, a Go-style pull parser cannot be used directly. My suggestion would be to split the work into two steps.
1. Allow incoming bodies to be written to a stream
Add a hook or option to THttpServerSocketGeneric that assigns Ctxt.ContentStream after the headers have been parsed and before the body is read.
Possible variants:
An event similar to OnBeforeBody, where the application may assign a stream such as TFileStreamEx, together with rfContentStreamNeedFree.
Automatic spooling based on a configurable memory threshold and temporary directory.
I would personally prefer the event first, since it is small and leaves storage policy to the application. A threshold-based default could be added later.
Since both server implementations use ProcessRead, the same mechanism should work for THttpServer and THttpAsyncServer. The request handler would receive either Content or ContentStream, depending on how the body was stored.
2. Add a streaming multipart reader
As a follow-up, a TMultiPartReader could parse multipart data incrementally from any TStream. The handler could then process one part at a time and copy file parts directly to their final destination.
How should this interact with Content-Encoding, since decompression currently operates on the in-memory buffer?
Is freeing ContentStream through ProcessDone sufficient for aborted connections?
Would you prefer an application-provided stream, automatic threshold-based spooling, or both?
Would a PR starting with step 1 be welcome?
@flydev Have you been able to publish the code on GitHub yet? I’d share mine with you after that.
OK, then maybe we should try to merge that somehow after your vacation ![]()
By the way, I also developed a sqlite-vec extension using lembed, available at https://github.com/landrix/sqlite-vec-for-Delphi. It builds upon two extensions.
I'm exclusively using FreePascal now—no more Delphi. It's just because of the repo names.
If it’s okay with flydev, I’d like to propose some code that builds upon his—specifically, a `mormot.ai` namespace. I’m also willing to maintain it. I’ve implemented the current MCP specification, and right now, I’m working on LLM clients and agents.
Flydev's mormot-os is definitely required. The TLimitedStreamWriter you added today is already being put to use as well.
Sven
Hi flydev,
thanks a lot — I didn't know about mormot2-extensions, and your
mormot-mcp-server is genuinely the closest thing to "MCP done the mORMot way"
I've seen. I went through it in detail;
- It reads like a mORMot core unit, not a Delphi port: IInvokable tools,
RTTI-based schema generation, TDocVariant envelopes, the tri-license header
and * section style.
- The transport layer is excellent — stdio, HTTP, SSE and a real Streamable
HTTP with token-by-token streaming (incl. the Claude CLI demo). That directly
answers the streaming question from my post.
- The TSynTestCase coverage for the transports (origin validation, sessions,
batching, notifications, bad-JSON, 405 paths) is exactly the rigor I'd want.
Where my use case still diverges — and why I think there's room for both:
1. Spec revision. Our agents target the current MCP revision
(2025-11-25); your server is on 2024-11-05 + Streamable HTTP 2025-03-26.
For us the newer revision isn't optional.
2. Auth is our product core. We're a SaaS backend with JWT, HttpOnly refresh
cookies, __Host-csrf double-submit, a role/permission model and owner-capped
scopes. MCP tool execution has to run *inside* that context on our existing
mORMot host (TUriRouter + guards), not as a separate server. Your
TMcpAuthContext is a nice foundation, but the wiring is fairly specific to us.
So I'll keep building our mormot.ai.* clean-room layer (current spec + auth bound
to our context), but I'd love to align the Streamable HTTP transport with your
approach rather than reinvent it.
Which loops back to my question for Arnaud: with community MCP servers now existing,
is there appetite to converge any of this under an official namespace, or do you
prefer it stays as companion packages? Happy either way.
Thanks again for pointing me to it.
Best,
Sven
Hi Arnaud, hi all,
We run mORMot 2 in production (FPC on Linux, native, no Indy/Wine) as the backend
of a SaaS for the trades. We now need an **MCP server** so our backend can expose
tools/resources to AI agents (e.g. Claude Desktop and our own agent), and ideally
LLM client drivers later on.
Before writing much code I'd like to check interest and get your guidance, because
I think this belongs **in the mORMot world**, not as yet another standalone lib.
## The gap
As far as I can see, mORMot has no AI/LLM/MCP module today. Meanwhile mORMot
already ships everything such a module needs and would do it better than the
Delphi-only options out there (which lean on Indy and `System.JSON`/`System.Rtti`):
- `mormot.net.server` / `mormot.net.async` — HTTP server for the *Streamable HTTP*
MCP transport
- `mormot.net.ws.*` — WebSockets for streaming
- `mormot.core.json` / `mormot.core.variants` — JSON-RPC 2.0 envelopes with
`TDocVariant`
- `mormot.core.rtti` — generating JSON-Schema for tool input from typed records
- cross-compiler (FPC + Delphi), cross-platform, no extra dependencies
## What I'm proposing
A small, **clean-room** extension under a new `mormot.ai.*` namespace, built
against the official **MCP specification (revision 2025-11-25)** and JSON-RPC 2.0
— not derived from any third-party MCP code. Initial, transport-neutral layout:
```
mormot.ai.mcp.types JSON-RPC/MCP types + envelope build/parse
mormot.ai.mcp.server engine: tool/resource registry + JSON-RPC dispatch
(initialize, tools/list, tools/call) — JSON in/out
mormot.ai.mcp.transport.stdio stdin/stdout (local subprocess transport)
mormot.ai.mcp.transport.http Streamable HTTP on mormot.net.server
```
The engine takes JSON in and returns JSON out, so it is fully unit-testable
without a socket; transports are thin adapters. Tools implement a small
`IMcpTool` interface (`GetName`/`GetDescription`/`GetInputSchema`/`Execute`).
I already have the first unit (`mormot.ai.mcp.types`) and FPCUnit tests for the
JSON-RPC envelopes; the rest is staged behind it.
## Questions
1. **Interest & home** — would you welcome such an extension under the
`mormot.ai.*` namespace (eventual upstream into mORMot), or would you prefer
it lives as an external companion package? Either is fine for us; I'd just
like to build it the way that has the best chance of being useful to others.
2. **Namespace & conventions** — if upstream is on the table, is `mormot.ai.*`
the naming you'd want (e.g. `mormot.ai.mcp.server`)? Any coding-style or CLA
requirements I should follow from the start?
3. **Transport** — for Streamable HTTP, what's your recommended building block:
plain `THttpServer`/`THttpAsyncServer` with manual chunked writes, or is there
a pattern you'd point me at for long-lived streaming responses + an optional
WebSocket upgrade?
4. **JSON-RPC API shape** — for a framework-grade API, would you prefer the
envelopes/dispatch built on `TDocVariant` (what I have now) or on typed records
with `mormot.core.rtti` serialization? Happy to follow your taste here.
I'm willing to maintain this and contribute it back. Thanks for mORMot — it has
been a joy to build a native FPC backend on it.
Best regards,
Sven
Well, I just can't call the function sqlite3.db_config(sql.DB, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,1);
under Delphi Win64. There is an access violation directly in the sqlite library.
Both in the static version and in the DLL. I can't provide a more detailed analysis because I can't debug sqlite.
It works with Delphi Win32. But the extension is only Win64.
(I have tested the extension with DiSQLite3, there they works)
Please define "code crash".
---------------------------
GExperts Debugger Exception Notification
---------------------------
Project Project1.exe raised exception class EAccessViolation with message 'Access violation at address 0000000000D1EE5F. Read of address FFFFFFFFFFFFFFFF'.
---------------------------
[&Filter ...] [Ignore &All this Session] [&Break] [Additional &Info] [&Continue]
---------------------------
ThreadId=24592
ProcessId=1
ThreadName="Main"
ExceptionMessage="Access violation at address 0000000000D1EE5F. Read of address FFFFFFFFFFFFFFFF"
ExceptionName="EAccessViolation"
ExceptionDisplayName="$C0000005"
ExceptionAddress=00D1EE5F
FileName=<not available>
LineNumber=<not available>
---------------------------
i have downloaded
https://github.com/asg017/sqlite-vec/re … _64.tar.gz
created a win64 vcl application
and
uses
...
,mormot.core.os,mormot.db.sql,mormot.db.raw.sqlite3,mormot.core.unicode
,mormot.core.base,mormot.db.raw.sqlite3.static,mormot.db.sql.sqlite3
,mormot.db.core,mormot.core.datetime
procedure TForm1.FormCreate(Sender: TObject);
var
sql : TSQLDatabase;
lLoad : Integer;
lMsg : PUtf8Char;
begin
sql := TSQLDatabase.Create(ExtractFilePath(Application.ExeName)+'sample.sql','');
try
sqlite3.db_config(sql.DB, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1);
lLoad:= sql.SQLite3Library.load_extension(sql.DB, PUtf8Char(ExtractFilePath(Application.ExeName)+'vec0.dll'), 0, lMsg);
if not (lLoad= SQLITE_OK) then
raise Exception.Create('Error Message');
sql.Free;
except
on E:Exception do
begin
end;
end;
end;code crash at sqlite3.db_config(....
Somehow none of it works. Or it doesn't work with my extension. Does anyone know more about this? I want to download the following extension.
https://github.com/asg017/sqlite-vec/re … .2-alpha.6
Can anyone help?
Any help is welcome to follow PDF/3-A support.
What help is needed? What is still to be done?
with wxsqlite it works
but i need a DB Explorer
Thanks
i need encryption in Delphi Win64, so i need the external DLL.
For the 64bit variant I have to take the SQLite DLL from https://github.com/utelle/wxsqlite3/releases, right?
Is there a database viewer that supports this encryption?
Is it included in main repository?
thank you @ab, for your explanations. I would have to check that again. But I think we exceed the 16 MB limit also not. And the files are rarely changed.
What are the difficults to support GridFS? Aside from that, who programmed it.
Any news on this project? Win64 Support,...Does it make sense to update to the latest version of SpiderMonkey?
Thanks
according to my first post,
This code should work, but it does not.
Can someone take a look on this, please.
thanks
var
sql : TSQLDatabase;
blob : TSQLBlobStream;
memory : TMemoryStream;
begin
sql := TSQLDatabase.Create('d:\temp\test.db');
try
sql.Execute('CREATE TABLE IF NOT EXISTS "files" ("UID" INTEGER PRIMARY KEY AUTOINCREMENT, "Filename" TEXT NOT NULL, "File" BLOB);');
memory := TMemoryStream.Create;
memory.LoadFromFile('d:\temp\v\DOCUMENT_2013-20280_2013_05_14_11_14_31.XML');
memory.Position := 0;
sql.Execute('INSERT INTO files (Filename) VALUES ("'+StringToUTF8('DOCUMENT_2013-20280_2013_05_14_11_14_31.XML')+'")');
blob := sql.Blob('main','files','File',0,true); <------------ cannot open value of type null extended_errcode=1
blob.CopyFrom(memory,memory.Size);
memory.Free;
blob.Free;
finally
sql.Destroy;
end;Your path is not correct.
Try 'd:\temp\test.db' instead of 'test.db'.
dosn't work.
https://www.sqlite.org/c3ref/blob_open.html say the dbname is called 'main', but it also does not work
But why do you use sql.Blob() ?
Use a regular SELECT statement with ColumnBlob() on the TSQLDatabase.
It would be much faster and safer.
sorry, i can't find some example or documentation to read / write binary blobs
Can't obtain Blob direct from SQLite
Which DBName is correct?
var
sql : TSQLDatabase;
blob : TSQLBlobStream;
memory : TMemoryStream;
begin
sql := TSQLDatabase.Create('d:\temp\test.db');
try
sql.Execute('CREATE TABLE IF NOT EXISTS "files" ("UID" INTEGER PRIMARY KEY AUTOINCREMENT, "Filename" TEXT NOT NULL, "File" BLOB);');
memory := TMemoryStream.Create;
memory.LoadFromFile('d:\temp\v\DOCUMENT_2013-20280_2013_05_14_11_14_31.XML');
memory.Position := 0;
sql.Execute('INSERT INTO files (Filename) VALUES ("'+UTF8Encode('DOCUMENT_2013-20280_2013_05_14_11_14_31.XML')+'")');
blob := sql.Blob('test.db','files','File',0,true); <------------ no such table test.db.files extended_errcode=1
blob.CopyFrom(memory,memory.Size);
memory.Free;
blob.Free;
finally
sql.Destroy;
end;
+1 for GridFS please
Any contribution is welcome!
:-)
i do my best.
I have to look also for PDF/A-3 support.
Could it be difficult to integrate the full XMP support?
Pages: 1