Net-Base Maġazin

27.05.2026

Multipart/Form-Data Upload f'Delphi: flussi robusti, kontroll tal-boundary u debugging mingħajr spekulazzjoni

Multipart/Form-Data uploads jidhru trivjali, iżda f'Delphi jaqgħu malajr meta jiġu trattati streams, ismijiet tal-fajl, Content-Type, Boundary-Handling u timeouts. Din il-biċċa kodiċi tas-sors turi implementazzjoni robusta u faċilment debuggabbli ma' THTTPClient — inkluż il-Content-Length ikkalkulat b'mod korrett.

27.05.2026

Għaliex Multipart f’Delphi spiss jaffaċja problemi biss fil-produzzjoni

Upload Multipart/Form-Data f‘ Delphi huwa faċli biex jinħolqot bi ftit klikks – u mbagħad jisfida fl-integrazzjonijiet reali minħabba d-dettalji: Content-Type mhux korrett għal kull Part, Boundary-string li b’żball jinstab fil-payload, line breaks mhux xierqa, ismijiet ta’ fajl mhux-ASCII jew servers li jirrifjutaw chunked transfer encoding (HTTP mingħajr Content-Length). Fuq dan iżidu problemi prattiċi tipici f’softwer korporattiv personalizzat: fajls kbar (CAD, PDFs, Scans), netwerks instabbli, Reverse-Proxies, API-Gateways stretti u rekwiżiti amministrattivi għall-debugging.

Delphi ġġib miegħu System.Net.HttpClient bħala stack prattiku, iżda l-eżempji tal-“Happy Path” jħallu barra kundizzjonijiet periferali importanti. Il-qiegħdan tal-kodiċi li ġej jaħdem aktar fil-fond: nibnu Multipart bħala Stream deterministiku, nirrevedu Content-Length b’mod korrett, nappoġġjaw RFC-5987 għal ismijiet ta‘ fajl u nipprovdu għażla ta‘ debugging li tagħmel ir-Request riproduċibbli mingħajr ma jkollok tneħħi l-protezzjoni TLS.

Deċiżjoni ta‘ arkitettura: THTTPClient minflok Indy – u meta dan jinbidel

THTTPClient (System.Net) juża backends differenti skond il-pjattaforma (fuq Windows tipikament WinHTTP/WinINet). Dan huwa spiss vantaġġjuż f’ambjenti korporattivi: policy ta‘ Proxy u TLS huma iktar kompatibbli mas-sistema. Indy, min-naħa l-oħra, huwa ħafna aktar trasparenti u aġġustabbli, iżda jeħtieġ il-proprji TLS-Bindings u fil-produzzjoni kultant jiġi “separatament zu pflegen” (verżjonijiet ta‘ OpenSSL, suite tal-cipher).

Dan l-approċċ juża THTTPClient, peress li spiss diġà jintuża f’proċessi ta‘ modernizzazzjoni (REST-Client, OAuth, Downloads). Madankollu, jekk teħtieġ kontroll strett fuq TLS-handshakes, ċertifikati tal-klijent f’format speċjali jew katini ta‘ proxy estremament speċifiċi, Indy (jew stack HTTP dedikat) jista‘ jkun sensibbli. Dan ma jaffettwax ħafna l-bini tal-Multipart – imma jaffettwa l-debugging u l-operazzjoni.

Multipart/Form-Data Upload in Delphi: Stream, xejn maġiku

Il-kunċett ewlieni: Multipart fl-aħħarnett huwa sempliċement stream ta‘ bajts. Jekk nibnuh aħna stess, nistgħu:

  • Nagħżlu Boundary b’mod konsistenti u nittestjawh b’mod stabbli
  • Nissettjaw headers għal kull part b’mod korrett (inkl. Content-Disposition, Content-Type)
  • Nikkalkulaw il-Content-Length b’mod affidabbli (importanti għal servers li ma jappoġġjawx chunked transfer encoding)
  • Nisstremjaw fajls kbar mingħajr ma nżommu kollox fir-RAM

Il-kodiċi: Multipart-Builder b’streaming u ismijiet ta‘ fajl skont RFC-5987

Il-builder hawn taħt joħloq wħud mill-għażliet: jew body kompletament bbażat fuq il-memorja (għal uploads żgħar) jew fajl ta‘ spool fuq id-disk (għal payloads kbar). Dan jidher “oldschool”, iżda fil-produzzjoni huwa estremament prattiku, peress li jevita chunked u jnaqqas il-kumplessità tad-debugging. Spoolen ifisser: tista‘ terġa‘ tuża l-istess request-body anki jekk ikun meħtieġ retry.

unit Net-Base.Multipart;

interface

uses
System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient,
System.NetEncoding, System.Hash;

type
TMultipartFormData = class
private
FBoundary: string;
FParts: TObjectList<TObject>;
function CRLF: TBytes;
function BytesOfAscii(const S: string): TBytes;
function Quote(const S: string): string;
function Rfc5987FileNameStar(const FileName: string): string;
function NewBoundary: string;
public
constructor Create(const ABoundary: string = “);
destructor Destroy; override;

function ContentType: string;
function Boundary: string;

procedure AddField(const Name, Value: string; const ContentType: string = ‚text/plain; charset=utf-8‘);
procedure AddFile(const FieldName, FileName, ContentType: string; const FileStream: TStream);

// Jibni l-body kollu f’Stream. Jekk ASpoolToFile huwa vojt,
// jintuża TMemoryStream; inkella jinħolqot fajl.
function BuildBodyStream(out AContentLength: Int64; const ASpoolToFile: string = “): TStream;
end;

implementation

uses
System.Generics.Collections;

type
TPartKind = (pkField, pkFile);

TPart = class
public
Kind: TPartKind;
Name: string;
ContentType: string;
// Field
Value: string;
// File
FileName: string;
FileStream: TStream;
constructor Create;
end;

constructor TPart.Create;
begin
inherited Create;
end;

constructor TMultipartFormData.Create(const ABoundary: string);
begin
inherited Create;
FParts := TObjectList<TObject>.Create(True);
if ABoundary <> “ then
FBoundary := ABoundary
else
FBoundary := NewBoundary;
end;

destructor TMultipartFormData.Destroy;
begin
FParts.Free;
inherited;
end;

function TMultipartFormData.NewBoundary: string;
var
R: TBytes;
begin
// Il-boundary għandha tkun biżżejjedament każwali. Importanti: ebda spazji.
SetLength(R, 16);
TNetEncoding.Base64.Decode(TNetEncoding.Base64.EncodeBytesToBytes(THashSHA2.GetHashBytes(GuidToString(TGuid.NewGuid))), R);
Result := ‚—-DelphiBoundary‘ + THashSHA2.GetHashString(GuidToString(TGuid.NewGuid));
Result := Result.Replace(‚{‚,“).Replace(‚}‘,“).Replace(‚-‚,“);
end;

function TMultipartFormData.Boundary: string;
begin
Result := FBoundary;
end;

function TMultipartFormData.ContentType: string;
begin
Result := ‚multipart/form-data; boundary=‘ + FBoundary;
end;

function TMultipartFormData.CRLF: TBytes;
begin
Result := TBytes.Create($0D, $0A);
end;

function TMultipartFormData.BytesOfAscii(const S: string): TBytes;
begin
// L-headers tal-multipart huma ASCII. Għal valuri fil-body (per eż., UTF-8) nissettjaw Content-Type għal kull part.
Result := TEncoding.ASCII.GetBytes(S);
end;

function TMultipartFormData.Quote(const S: string): string;
begin
Result := ‚“‚ + S.Replace(‚“‚, ‚“‚) + ‚“‚;
end;

function TMultipartFormData.Rfc5987FileNameStar(const FileName: string): string;
var
Utf8: TBytes;
Enc: string;
begin
// filename*=“UTF-8“…“ huwa ħafna aktar robust għal isem fajl mhux-ASCII milli biss filename=“…“.
Utf8 := TEncoding.UTF8.GetBytes(FileName);
Enc := TNetEncoding.URL.EncodeBytesToString(Utf8);
Result := ‚filename*=‘ + ‚UTF-8““’+ Enc;
end;

procedure TMultipartFormData.AddField(const Name, Value: string; const ContentType: string);
var
P: TPart;
begin
P := TPart.Create;
P.Kind := pkField;
P.Name := Name;
P.Value := Value;
P.ContentType := ContentType;
FParts.Add(P);
end;

procedure TMultipartFormData.AddFile(const FieldName, FileName, ContentType: string; const FileStream: TStream);
var
P: TPart;
begin
if FileStream = nil then
raise EArgumentNilException.Create(‚FileStream ma jistax ikun nil‘);

if (FileStream is TCustomMemoryStream) and (FileStream.Size = 0) then
; // permess, iżda spiss żball: fajl vojt

P := TPart.Create;
P.Kind := pkFile;
P.Name := FieldName;
P.FileName := FileName;
P.ContentType := ContentType;
P.FileStream := FileStream; // Owner bleibt beim Aufrufer
FParts.Add(P);
end;

function TMultipartFormData.BuildBodyStream(out AContentLength: Int64; const ASpoolToFile: string): TStream;
var
OutStream: TStream;
WriterUtf8: TBytes;
PartObj: TObject;
P: TPart;
Header: string;
Sep, EndSep: string;
B: TBytes;

procedure WriteAscii(const S: string);
begin
B := BytesOfAscii(S);
OutStream.WriteBuffer(B, Length(B));
end;

procedure WriteBytes(const Bytes: TBytes);
begin
if Length(Bytes) > 0 then
OutStream.WriteBuffer(Bytes, Length(Bytes));
end;

procedure CopyStreamFully(Src: TStream);
var
Buf: array[0..64*1024-1] of Byte;
ReadN: Integer;
begin
// Attenzjoni: il-pożizzjoni tal-stream tiġi kkunsmata.
while True do
begin
ReadN := Src.Read(Buf, SizeOf(Buf));
if ReadN <= 0 then
Break;
OutStream.WriteBuffer(Buf, ReadN);
end;
end;

begin
if ASpoolToFile <> “ then
OutStream := TFileStream.Create(ASpoolToFile, fmCreate or fmShareDenyWrite)
else
OutStream := TMemoryStream.Create;

try
Sep := ‚–‚ + FBoundary + #13#10;
EndSep := ‚–‚ + FBoundary + ‚–‚ + #13#10;

for PartObj in FParts do
begin
P := TPart(PartObj);

WriteAscii(Sep);

if P.Kind = pkField then
begin
Header := ‚Content-Disposition: form-data; name=‘ + Quote(P.Name) + #13#10 +
‚Content-Type: ‚ + P.ContentType + #13#10 +
#13#10;
WriteAscii(Header);
// Field-Body in UTF-8, sofern charset=utf-8 gesetzt ist.
WriterUtf8 := TEncoding.UTF8.GetBytes(P.Value);
WriteBytes(WriterUtf8);
WriteBytes(CRLF);
end
else
begin
// Zwei Dateiname-Parameter: filename (für alte Server) und filename* (RFC 5987)
Header := ‚Content-Disposition: form-data; name=‘ + Quote(P.Name) + ‚; ‚ +
‚filename=‘ + Quote(ExtractFileName(P.FileName)) + ‚; ‚ +
Rfc5987FileNameStar(ExtractFileName(P.FileName)) + #13#10 +
‚Content-Type: ‚ + P.ContentType + #13#10 +
‚Content-Transfer-Encoding: binary‘ + #13#10 +
#13#10;
WriteAscii(Header);

// Importanti: issettja l-pożizzjoni fil-bidu, inkella jittella‘ biss dak li fadal.
if P.FileStream.Seek(0, soBeginning) <> 0 then
;
CopyStreamFully(P.FileStream);
WriteBytes(CRLF);
end;
end;

WriteAscii(EndSep);

AContentLength := OutStream.Size;
OutStream.Position := 0;
Result := OutStream;
except
OutStream.Free;
raise;
end;
end;

end.

X’jagħmel il-kodiċi b’mod intenzjonat differenti

  • L-ebda „Multipart awtomatiku“: Il-kontroll fuq l-header, l-encodings u l-boundary jibqa‘ f’wiċċek. Dan spiss huwa deċiżiv fl-APIs stretti REST.
  • Appoġġ RFC-5987 permezz ta‘ filename*: Meta l-ismijiet tal-fajl fihom umlauts (pereżempju „Prüfbericht.pdf“), dan hu l-iktar bug ta‘ interoperabilità komuni. Xi server jignoraw filename*, u f’dak il-każ filename jintuża bħala fallback.
  • Spool-to-File bħala karatteristika operattiva: Għal uploads kbar u ritentivi, stream tal-body riutilizzabbli huwa ta‘ valur estremament għoli.
  • Content-Length huwa disponibbli, għax il-body jinħolqot minn qabel. Dan jipprevjeni Chunked-Encoding jekk is-sistema tal-mira ma taċċettahx.

Ibgħat request: Timeouts, Header u strateġija ta‘ ritentivi sensata

Il-multipart waħdu ma jsolvix il-problemi ta‘ integrazjoni: għandek bżonn timeouts, klassifikazzjoni ta‘ żball u b’mod fakultattiv ritentivi. Importanti hija d-distinzjoni bejn idempotent u mhux idempotent: uploads spiss mhumiex idempotenti (jistgħu jseħħu duplikati). Ritentivi għandhom isiru biss jekk is-server jipprovdi semantika idempotenti (pereżempju Upload-ID, header dedikat Idempotency-Key) jew jekk għandek dedupplikazzjoni fuq in-naħa tas-server.

Delphi
uses
  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient,
  Net-Base.Multipart;

function PostMultipart(const Url: string; const Token: string; const MP: TMultipartFormData;
  const SpoolFile: string = ''): IHTTPResponse;
var
  Client: THTTPClient;
  Body: TStream;
  ContentLen: Int64;
  Req: IHTTPRequest;
begin
  Client := THTTPClient.Create;
  try
    // Timeouts: je nach Datei und Leitung realistisch setzen.
    Client.ConnectionTimeout := 15000; // ms
    Client.ResponseTimeout := 600000;  // ms (10 min)

    Body := MP.BuildBodyStream(ContentLen, SpoolFile);
    try
      Req := Client.GetRequest('POST', Url);
      Req.SourceStream := Body;

      Req.AddHeader('Content-Type', MP.ContentType);
      // Manche Server oder Proxies erwarten Content-Length zwingend.
      Req.AddHeader('Content-Length', ContentLen.ToString);

      if Token <> '' then
        Req.AddHeader('Authorization', 'Bearer ' + Token);

      // Optional: wenn der Server sauber JSON liefert, kann Accept helfen.
      Req.AddHeader('Accept', 'application/json');

      Result := Client.Execute(Req, nil);
    finally
      Body.Free;
    end;
  finally
    Client.Free;
  end;
end;

Problemi tipċi fil-prattika

  • Pożizzjoni tas-stream: Jekk il-FileStream mhux fuq il-pożizzjoni 0, inti t-uploadja biss ir-restu. Għalhekk fil-Builder Seek(0) jiġi mforzat.
  • Chunked vs. Content-Length: Xi gateways (jew stacks ta‘ server aktar antiki) jirrifjutaw Chunked. Dan huwa każ komuni ta‘ legacy f’soluzzjonijiet tas-softwer li jkunu qrib il-proċess. Spool-to-File mbagħad huwa soluzzjoni pragmatika.
  • CRLF: Multipart jistenna CRLF (#13#10), mhux biss LF. Xi server huma tolleranti, oħrajn le.
  • Content-Type għal kull fajl: Jekk tibgħat b’mod ġenerali application/octet-stream, dan spiss ikun aċċettabbli. Jekk is-server jiċċekkja (eż. PDF), issettjaw il-Content-Type b’mod korrett. F’Delphi tista‘ ssolvi l-mapping tal-MIME permezz ta‘ tabella proprja jew funzjonijiet tal-OS, imma m’għandekx tiddependi b’mod blind fuq l-extensions tal-fajl.

Debugging: wire-dump riproduċibbli mingħajr interċettazzjoni TLS

Bi HTTPS ma tarax il-body fil-proxy jekk ma tistax tuża MitM (eż. ċertifikat Fiddler). Dan huwa normali f’ambjenti korporattivi. Il-Builder jgħin għax għandek il-body kollu b’mod streamjat u (fil-każ ta‘ Spool-Datei) għandekh bħala fajl.

Prattika stabbilita:

  1. Ikteb il-Spool-Body f’fajl temporanju.
  2. Iġġenera log ta‘ Content-Type inkluż il-Boundary u l-Content-Length.
  3. Oħloq għal Support/DevOps fakultattiv riproduzzjoni curl: hawn m’għandekx bżonn tirreplika l-body 1:1, imma tista‘ tirrappreżenta l-parametri u l-fajl(i).

Importanti: qatt ma tagħmel log ta‘ tokens produttivi jew ta‘ kontenut personali. F’ħafna integrazzjonijiet ta‘ software korporattiv dan hu eżatt il-parti rilevanti għall-compliance.

Varjanti: diversi fajls, kampi fakultattivi, server b’aspettattivi „strambi”

Diversi fajls taħt l-istess isem tal-kamp

Ħafna APIs jistennew files[] jew diversi drabi l-istess isem. Il-Builder jappoġġja dan direttament: sejjaħ AddFile diversi drabi bl-istess FieldName. Li tuża files, files[] jew attachments hija pura konvenzjoni tas-server.

Server jitlob eżatt „application/json” bħala parti addizzjonali

Mudell komuni: blokk ta‘ metadati JSON flimkien ma‘ fajl. Imbagħad tibgħat il-JSON bħala Field-Part, iżda b‘ Content-Type: application/json; charset=utf-8. Dan mhux „Form Field” fis-sens tal-UI, imma jista‘ jiġi mappjat b’mod nadif f’Multipart:

Delphi
MP.AddField('metadata', '{"documentType":"report","source":"delphi"}', 'application/json; charset=utf-8');

Legacy: Server akkwetta biss filename, mhux filename*

Monnaq il-fallback permezz ta‘ filename jista‘ jkun ta‘ għajnuna. Jekk is-server madankollu jiddekodifika ħażin karattri mhux-ASCII f‘filename, spiss l-approċċ aktar robust hu li tinjorah l-isem tal-fajl fuq is-server u tibgħat kamp addizzjonali originalName fil-JSON.

Kuntest għall-modernizzazzjoni u l-operazzjoni

F’landscapes mogħtija ta‘ Delphi Multipart spiss jinsab fil-fruntiera: interface ma‘ DMS, Archiv, Ticketing, portal tal-klijent jew server intern REST-Server. Hawnhekk joħroġ pressjoni minħabba rekwiżiti ġodda tas-sigurtà (TLS, Gateways, Proxies) u minħabba daqsijiet ta‘ fajls akbar.

Il-approċċ propost jibbenefika partikolarment jekk:

  • Tgħandek bżonn tiddebbuga uploads b’mod riproduċibbli (operazzjoni/amministrazzjoni)
  • Trid/għandek bżonn tevita Chunked
  • Is-sitwazzjoni prattika tinkludi verament ismijiet ta‘ fajl/encodings (karattri diakritiċi, spazji, parentesi)
  • Retry/Idempotency għandhom jiġu solvuti b’mod konċettwalment nadif

Huwa b’mod minimu ta‘ valur jekk tibgħat biss fajls żgħar għal server tolleranti u ma tridx trasparenza operattiva. F’dak il-każ soluzzjoni high-level sempliċi hi suffiċjenti — sakemm ma jasalx l-ewwel fajl „stramb” mill-unità tan-negozju.

Konklużjoni: Multipart-Upload stabbli huwa problema ta‘ streaming u ta‘ operazzjoni

Upload Multipart/Form-Data nadif f’Delphi mhux tant mistoqsija ta‘ «liema komponenta» iżda ta‘ kontroll: Boundary, CRLF, isem tal-fajl, Content-Type u fuq kollox stream tal-body deterministiku. Min jibni dan sew fil-bidu jiffranka ħin aktar tard f’ċikli ta‘ debugging ma‘ API-Gateways u Reverse-Proxies.

Limitu tal-approċċ: Jekk għandek bżonn ittella‘ fajls estremament kbar (diversi GB) mingħajr spooling u mingħajr Content-Length, is-suġġett ta‘ Streaming mingħajr kalkolu preventiv isir rilevanti – f’dak il-każ is-server tad-destinazzjoni u l-infrastruttura jridu jappoġġjaw Chunked b’mod affidabbli, u jkollok bżonn kunċett differenti ta‘ debugging. Għal ħafna integrazjonijiet f’soluzzjonijiet korporattivi diġitali, madankollu, il-Builder li hawnhekk muri huwa eżatt il-bilanċ pragmatiku bejn robustezza, traċċabbiltà u konsum ta‘ riżorsi li jista‘ jiġi kkontrollat.

Jekk int marbut ma‘ integrazzjoni żviluppata Delphi fejn l-ittellijiet jonqsu sporadikament jew biss „fuq ċerti fajls“, dan ġeneralment jindika dawn il-kundizzjonijiet limiti. Għal appoġġ mmirat fl-analiżi, modernizzazzjoni jew ċarezza tal-operat tista‘ tikkuntattjana hawn:

Fil-qasam tekniku jilgħabu rwol importanti wkoll Delphi Thttpclient u REST API upload tal-fajls, meta l-integrazzjonijiet, il-flussi tad-data u l-iżvilupp kontinwu jeħtieġu jaħdmu b’mod nadif flimkien.

Iddiskuti proġett jew inizjattiva ta‘ modernizzazzjoni ma‘ Net-Base.

Aqsam il-post

Aqsam dan il-post direttament

LinkedIn, X, XING, Facebook, WhatsApp u E-Mail huma immedjatament disponibbli. Għal Instagram nippreparaw il-link u t-test qasir direttament.

Imejl

Instagram jiftaħ f'tab ġdid. Il-link u t-test qasir jiġu kkopjati qabel fil-clipboard.