Horse provides powerful and flexible file handling capabilities, including file uploads, downloads, and serving static content. This guide covers everything from basic file operations to advanced use cases.
THorse.Get('/document', procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc) var LStream: TMemoryStream; begin LStream := TMemoryStream.Create; try // Generate or load content into stream LoadDocumentIntoStream(LStream); Res.SendFile(LStream, 'document.pdf', 'application/pdf'); except LStream.Free; raise; end; // Don't free the stream - Horse manages it end);
When passing a stream to SendFile(), Horse takes ownership of the stream and will free it automatically. Do not manually free the stream after calling SendFile().
Horse handles file uploads through the ContentFields property, which processes both multipart/form-data and application/x-www-form-urlencoded requests.
THorse.Post('/form', procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc) var LValue: string; begin if Req.ContentFields.ContainsKey('username') then begin LValue := Req.ContentFields['username']; Res.Send('Username: ' + LValue); end else Res.Status(THTTPStatus.BadRequest).Send('Username is required'); end);
var LFile: TStream;begin if Req.ContentFields.TryGetValue('avatar', LFile) then begin // LFile is a TStream containing the uploaded file // Process the file... end;end;
unit MyApp.FileValidation;interfaceuses System.Classes, System.SysUtils;type TFileValidator = class public class function ValidateImageFile(AStream: TStream; out AError: string): Boolean; class function ValidateFileSize(AStream: TStream; AMaxSizeBytes: Int64; out AError: string): Boolean; class function ValidateFileExtension(const AFileName: string; const AAllowed: array of string; out AError: string): Boolean; end;implementationclass function TFileValidator.ValidateFileSize(AStream: TStream; AMaxSizeBytes: Int64; out AError: string): Boolean;begin Result := AStream.Size <= AMaxSizeBytes; if not Result then AError := Format('File size exceeds maximum of %d bytes', [AMaxSizeBytes]);end;class function TFileValidator.ValidateFileExtension(const AFileName: string; const AAllowed: array of string; out AError: string): Boolean;var LExt: string; LAllowedExt: string;begin LExt := LowerCase(ExtractFileExt(AFileName)); Result := False; for LAllowedExt in AAllowed do begin if LExt = LowerCase(LAllowedExt) then begin Result := True; Break; end; end; if not Result then AError := 'File extension not allowed';end;class function TFileValidator.ValidateImageFile(AStream: TStream; out AError: string): Boolean;var LBuffer: array[0..3] of Byte; LBytesRead: Integer;begin Result := False; AError := ''; if AStream.Size < 4 then begin AError := 'Invalid file format'; Exit; end; AStream.Position := 0; LBytesRead := AStream.Read(LBuffer, 4); AStream.Position := 0; if LBytesRead = 4 then begin // Check for PNG signature if (LBuffer[0] = $89) and (LBuffer[1] = $50) and (LBuffer[2] = $4E) and (LBuffer[3] = $47) then Result := True // Check for JPEG signature else if (LBuffer[0] = $FF) and (LBuffer[1] = $D8) then Result := True // Check for GIF signature else if (LBuffer[0] = $47) and (LBuffer[1] = $49) and (LBuffer[2] = $46) then Result := True; end; if not Result then AError := 'File is not a valid image format (PNG, JPEG, or GIF)';end;end.
Usage:
THorse.Post('/upload/avatar', procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc) var LFile: TStream; LError: string; begin if not Req.ContentFields.TryGetValue('avatar', LFile) then begin Res.Status(THTTPStatus.BadRequest).Send('No file provided'); Exit; end; // Validate file size (max 5MB) if not TFileValidator.ValidateFileSize(LFile, 5 * 1024 * 1024, LError) then begin Res.Status(THTTPStatus.BadRequest).Send(LError); Exit; end; // Validate image format if not TFileValidator.ValidateImageFile(LFile, LError) then begin Res.Status(THTTPStatus.BadRequest).Send(LError); Exit; end; // File is valid, save it SaveAvatarFile(LFile); Res.Send('Avatar uploaded successfully'); end);
THorse.Get('/files/:filename', procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc) var LFileName: string; begin LFileName := Req.Params['filename']; try if not FileExists(LFileName) then begin Res.Status(THTTPStatus.NotFound).Send('File not found'); Exit; end; Res.SendFile(LFileName); except on E: Exception do Res.Status(THTTPStatus.InternalServerError) .Send('Error loading file: ' + E.Message); end; end);
If automatic MIME type detection doesn’t work for your use case, you can always specify the content type explicitly as the second parameter to SendFile(), Download(), or by using ContentType() before sending.