| name | horse-files-streams |
| description | Guide to handling file uploads (multipart), downloads, and stream lifetime management in the Horse framework. |
Horse Files & Streams
1. File Uploads (Multipart FormData)
When clients upload files via multipart form data, you can retrieve the file as a stream or save it directly to the disk using the THorseCoreParamField helper from Req.ContentFields:
procedure UploadHandler(Req: THorseRequest; Res: THorseResponse; Next: TProc);
var
LFileField: THorseCoreParamField;
LStream: TStream;
begin
// 1. Get the file field
LFileField := Req.ContentFields.Field('file');
if not Assigned(LFileField) then
raise EHorseException.New
.Status(THTTPStatus.BadRequest)
.Error('No file was uploaded');
// 2. Save it directly to disk
LFileField.SaveToFile('C:\uploads\' + LFileField.FieldName);
// 3. Or access the raw TStream
LStream := LFileField.AsStream;
// Note: DO NOT free LStream if it is owned by the request lifecycle.
Res.Status(THTTPStatus.OK).Send('File uploaded successfully');
end;
2. File Downloads & Streaming
To send files or custom data streams to the client, use Res.SendFile or Res.Download.
SendFile: Sends the file stream to the client (displays inline in the browser if supported).
Download: Forces the browser to download the file with the specified filename.
procedure DownloadHandler(Req: THorseRequest; Res: THorseResponse; Next: TProc);
var
LStream: TStream;
begin
LStream := TFileStream.Create('C:\data\report.pdf', fmOpenRead or fmShareDenyWrite);
// Sends the stream and instructs the client to download as "report_2026.pdf"
Res.Status(THTTPStatus.OK).Download(LStream, 'report_2026.pdf');
// CRITICAL MEMORY RULE: DO NOT FREE LStream here.
end;
3. Critical Memory Rule (Stream Ownership)
The THorseResponse object takes ownership of any stream passed into SendFile, Download, or Render.
- Rule: NEVER call
.Free or FreeAndNil on a stream after passing it to Res.SendFile, Res.Download, or Res.Render.
- Reason: The framework will automatically destroy the stream object once the HTTP response is written to the socket. Freeing it manually in your handler will cause a Double-Free memory corruption (Access Violation) when the response lifecycle terminates.