program ChunkDownload;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
SysUtils, fphttpapp, httproute, HTTPDefs, Classes, fphttpserver, fpWeb;
type
TFPHTTPConnectionResponseHack = class(TFPHTTPConnectionResponse);
procedure downloadFileWithChunks(aRequest: TRequest; aResponse: TResponse);
const
CHUNK_SIZE = 8192; // 8 KB chunks
var
FilePath: string;
FileStream: TFileStream;
Buffer: array of Byte;
BytesRead: Integer;
CustomFileName: string;
connection: TFPHTTPConnection;
ChunkHeader: string;
begin
if not (aResponse is TFPHTTPConnectionResponse) then
raise Exception.Create('Invalid response type, not TFPHTTPConnectionResponse');
if TFPHTTPConnectionResponseHack(aResponse).Connection = nil then
raise Exception.Create('Connection is nil. Ensure the request is handled via fphttpserver.');
FilePath := 'D:\Project\Lazarus\Applications\Silhouette\SilSetup\windows\x86_64-win64\Output\Silhouette_6_x86_64-win64.exe';
if FilePath = '' then
begin
aResponse.Content := '{"result": "Missing file path"}';
aResponse.Code := 400;
aResponse.ContentLength := Length(aResponse.Content);
aResponse.SetCustomHeader('Access-Control-Allow-Origin', '*');
aResponse.SendContent;
WriteLn('Sent 400 response');
Exit;
end;
if not FileExists(FilePath) then
begin
aResponse.Content := '{"result": "File not found: ' + FilePath + '"}';
aResponse.Code := 404;
aResponse.ContentLength := Length(aResponse.Content);
aResponse.SetCustomHeader('Access-Control-Allow-Origin', '*');
aResponse.SendContent;
WriteLn('Sent 404 response for path: ', FilePath);
Exit;
end;
try
FileStream := TFileStream.Create(FilePath, fmOpenRead or fmShareDenyWrite);
try
connection := TFPHTTPConnectionResponseHack(aResponse).Connection;
CustomFileName := ExtractFileName(FilePath);
// Set headers
aResponse.ContentType := 'application/x-msdownload';
aResponse.ContentLength := FileStream.Size;
aResponse.SetCustomHeader('Content-Disposition', 'attachment; filename="' + CustomFileName + '"');
aResponse.SetCustomHeader('Accept-Ranges', 'bytes');
aResponse.SetCustomHeader('Access-Control-Allow-Origin', '*');
AResponse.SetCustomHeader('Access-Control-Expose-Headers', 'Content-Disposition, Content-Length, Transfer-Encoding');
aResponse.SetCustomHeader('Transfer-Encoding', 'chunked');
TFPHTTPConnectionResponse(aResponse).SendHeaders;
WriteLn('Headers set:');
WriteLn('Content-Type: ', aResponse.ContentType);
WriteLn('Content-Length: ', IntToStr(aResponse.ContentLength));
WriteLn('Content-Disposition: attachment; filename="', CustomFileName, '"');
WriteLn('Access-Control-Allow-Origin: *');
// Allocate buffer
SetLength(Buffer, CHUNK_SIZE);
while FileStream.Position < FileStream.Size do
begin
BytesRead := FileStream.Read(Buffer[0], CHUNK_SIZE);
if BytesRead <= 0 then
break;
// Write chunk size in hexadecimal followed by CRLF
ChunkHeader := IntToHex(BytesRead, 1) + #13#10;
Connection.Socket.WriteBuffer(ChunkHeader[1], Length(ChunkHeader));
// Write the actual data followed by CRLF
Connection.Socket.WriteBuffer(Buffer[0], BytesRead);
Connection.Socket.WriteBuffer(#13#10, 2);
end;
// Send final chunk (0 length) to indicate end of transfer
ChunkHeader := '0'#13#10#13#10;
Connection.Socket.WriteBuffer(ChunkHeader[1], Length(ChunkHeader));
finally
FileStream.Free;
end;
except
on E: Exception do
begin
aResponse.Content := '{"result": "Error: ' + E.ClassName + ' - ' + E.Message + '"}';
aResponse.Code := 500;
aResponse.ContentLength := Length(aResponse.Content);
aResponse.SetCustomHeader('Access-Control-Allow-Origin', '*');
aResponse.SendContent;
WriteLn('Error: ', E.ClassName, ' - ', E.Message);
end;
end;
end;
begin
Application.Port := 9090;
HTTPRouter.RegisterRoute('/file', TRouteMethod.rmGet, @downloadFileWithChunks);
Application.Threaded := true;
Application.Initialize();
WriteLn('Server starter at port: ' + IntToStr(Application.Port));
Application.Run;
end.