Cén fáth a mbíonn Multipart i Delphi go minic ag teip ach amháin i rith an oibriúcháin
Is furasta Multipart/Form-Data Upload i Delphi a chur le chéile go tapa – ach teipeann sé ansin i n-iompar i bhfíor‑chomhtháthuithe mar gheall ar sonraí beaga: Content-Type mícheart in aghaidh an Parta, slabhra Boundary a thagann tríd i ndiaidh an payload gan choinne, briseanna líne míchóirithe, ainmneacha comhaid nach bhfuil ASCII iad nó freastalaithe a dhiúltaíonn chunked transfer encoding (HTTP gan Content-Length). Chomh maith leis sin tagann fadhbanna traidisiúnta i mbogearraí corparáideacha shaincheaptha: comhaid mhóra (CAD, PDFs, scans), líonraí athraitheacha, reverse‑proxies, API‑gateways dian agus riachtanais riarthóra maidir le debugging.
Tuigann Delphi le System.Net.HttpClient stac úsáideach, ach fágann na samplaí „Happy Path“ coinníollacha imeallacha tábhachtacha gan aird. Téann an sliseag cód thíos isteach go daingean: tógfaimid Multipart mar Stream go deterministic, ríomhfaimid Content-Length i gceart, tacaímid le RFC-5987 do ainmneacha comhaid agus soláthraímid rogha Debug a dhéanann an Request inúsáidte go huathoibríoch gan gá duit TLS a bhriseadh.
Cinneadh ailtireachta: THTTPClient in ionad Indy – agus cathain a theipeann sé
THTTPClient (System.Net) úsáideann backends éagsúla ag brath ar an ardán (faoin Windows go minic WinHTTP/WinINet). Bíonn sé sin go minic le sochar i dtimpeallachtaí corparáideacha: tá beartais Proxy agus TLS níos comhoiriúnach leis an gcóras. Tá Indy an‑trédhearcach agus inúsáidte go héasca le haistriúcháin, ach tugann sé a cheangail TLS féin agus uaireanta bíonn sé „separat zu pflegen“ sa turning (leaganacha OpenSSL, cipher suites).
Úsáidtear an cur chuige anseo THTTPClient toisc go mbíonn sé cheana i n‑úsáid i go leor nuashonruithe (REST-Client, OAuth, downloads). Má theastaíonn uait rialú docht ar TLS‑handshakes, teastais chliaint i bhfoirmeacha speisialta nó slabhraí proxy an‑sonracha, d’fhéadfadh Indy (nó stac HTTP tiomnaithe) a bheith oiriúnach. Ní athraíonn sin go mór an tógáil Multipart – ach athraíonn sé an debugging agus an t‑oibriú.
Multipart/Form-Data Upload i Delphi: Stream amháin, ní draíocht ar bith
An smaoineamh lárnach: is sruth baití amháin é Multipart ag an deireadh. Má thógann muid é féin, is féidir linn:
- Boundary a roghnú go cúramach agus a thástáil go seasta
- ceannteidil do gach Part a shocrú i gceart (lena n-áirítear
Content-Disposition,Content-Type) - ríomh
Content-Lengthgo muiníneach (tábhachtach do fhreastalaithe gan tacaíocht do chunked) - comhaid mhóra a shruthlíneáil gan gach rud a choinneáil san RAM
An cód: Multipart‑Builder le sruthlú agus ainmneacha comhaid RFC-5987
Tá an Builder thíos in ann corp bunaithe ar chuimhne a ghiniúint roghnach (do uplaodaí beaga) nó comhad spool ar dhiosca a chruthú (do Payloads móra). Tá sé sin „oldschool“, ach an‑úsáideach sa oibriú, mar seachnaíonn sé chunked agus éascaíonn sé debugging. Ciallaíonn spooláil: is féidir an corp iarratais céanna a athúsáid fiú má tá retry riachtanach.
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
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);
// Tógann an fheidhm an corp iomlán (body) isteach i sruth. Má tá ASpoolToFile folamh,
// úsáidtear TMemoryStream; murach sin, cruthaítear comhad.
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
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
// Ba chóir go mbeadh Boundary sách randamach. Tábhachtach: gan spásanna.
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
// Tá ceanntáscanna multipart ina ASCII. Maidir le luachanna sa choirp (m.sh. UTF-8) socraímid Content-Type do gach páirt.
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“…“ i bhfad níos láidre do ainmneacha comhad neamh-ASCII ná 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(‚Ní féidir FileStream a bheith nil‘);
if (FileStream is TCustomMemoryStream) and (FileStream.Size = 0) then
; // ceadaithe, ach minic earráid: comhad folamh
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
// Tabhair faoi deara: bainfear úsáid as suímh an srutha.
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
// Dhá pharaiméadar ainmchomhaid: filename (do sheanfhreastalaithe) agus 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);
// Tábhachtach: cuir an suíomh ar an tús; murach sin ní sheolfar ach na iarmhair.
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.
Cad a dhéantar go sainráite difriúil sa chód
- Gan „automatisches Multipart“: Fanann smacht ar Header, ar na codálacha agus ar an Boundary i do lámha. Is minic a bhíonn sé seo cinntitheach le haghaidh APIanna REST dian.
- Tacaíocht RFC-5987 über
filename*: Nuair a bhíonn carachtair speisialta (m.sh. Umlauts) in ainmneacha comhaid (m.sh. „Prüfbericht.pdf“), is é sin an fabht idirnascaithe is coitianta. Déanann roinnt freastalaithe neamhaird arfilename*, agus ansin úsáidtearfilenamemar fallback. - Spool-to-File mar ghné oibríochta: Do uaslódálacha móra agus do iarrachtaí athuair tá stream an choirp in-athúsáidte thar a bheith luachmhar.
- Content-Length ist verfügbar, toisc go gcruthófar an Body roimh ré. Seachnaíonn sé seo Chunked-Encoding más rud nach nglacann an córas sprioc leis.
Seoladh Request: Timeouts, Header agus straitéis Retry chiallmhar
Ní réitíonn Multipart féin fadhbanna comhtháthaithe: teastaíonn Timeouts, sórtáil earráidí agus, de rogha, iarrachtaí athuair uait. Tá sé tábhachtach idirdhealú a dhéanamh idir idempotent agus neamh-idempotent: is minic nach mbíonn uaslódálacha idempotent (d’fhéadfadh dúbailtí tarlú). Níor chóir iarrachtaí athuair a dhéanamh ach más rud é go dtugann an freastalaí semantacht idempotent (m.sh. Upload-ID, Header saincheaptha Idempotency-Key) nó má tá dé-dhúbailiú ar thaobh an fhreastalaí agat.
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;
Fadhbanna coitianta sa chleachtas
- Stream-Position: Más rud é nach bhfuil an FileStream ar shuíomh 0, luchtófar ach an chuid eile. Sa Builder cuireadh
Seek(0)i bhfeidhm mar sin. - Chunked vs. Content-Length: Diúltaíonn roinnt geataí (nó sean-stácaí freastalaí) Chunked. Is minic go mbíonn sé seo ina chás legacy i réitigh bogearraí atá gar don phróiseas. Sa chás sin tá Spool-to-File pragmatach.
- CRLF: Tá Multipart ag súil le CRLF (
#13#10), ní le LF amháin. Tá roinnt freastalaithe foighneach, agus níl cuid eile. - Content-Type pro Datei: Má sheolann tú go forleathan
application/octet-stream, is minic go leor sin. Má dhéanann an freastalaí seiceáil (m.sh. PDF), socraigh an Content-Type i gceart. In Delphi is féidir leat léiriú MIME a réiteach trí do thábla féin nó trí fheidhmiúlachtaí an chórais oibriúcháin, ach ná bí ag brath go folamh ar shíneadh comhaid.
Debugging: Wire-Dump inathnuaite gan briseadh TLS
Faoi HTTPS ní fheiceann tú an Body sa phroxy, má tá sé toirmiscthe duit MitM (m.sh. teastas Fiddler) a úsáid. Tá sé seo gnáth i dtimpeallachtaí corparáideacha. Cabhraíonn an Builder toisc go bhfuil tú i seilbh an Body iomlán ar bhonn sruth agus (i gcás comhaid spool) go mbíonn sé ar fáil mar chomhad.
Cur chuige molta:
- Scríobh an Spool-Body chuig comhad sealadach.
- Logáil
Content-Typelena n-áirítear Boundary agusContent-Length. - Cruthaigh, más gá do Support/DevOps, repro
curlroghnach: Ní gá an Body a atghintear 1:1, ach is féidir leat na paraiméadair agus na comhaid a mhacasamhlú.
Tábhachtach: Ná logáil riamh tokennanna táirgthe ná sonraí pearsanta. I go leor comhtháthuithe bogearraí gnó is é sin an chuid atá faoi réir comhlíonta.
Varianten: mehrere Dateien, optionale Felder, Server mit „komischen“ Erwartungen
Mehrere Dateien unter demselben Feldnamen
Go leor APIanna ag súil le files[] nó le roinnt uaireanta an t-ainm céanna. Tacaíonn an Builder leis sin go díreach: Glaoigh AddFile arís agus arís eile le héinne FieldName. An bhfuil tú ag úsáid files, files[] nó attachments is saincheist í den freastalaí amháin.
Server verlangt exakt „application/json“ als zusätzlichem Part
Patrún coitianta: bloc meiteashonraí JSON in éineacht le comhad. Sa chás sin seolann tú an JSON mar Field-Part, ach le Content-Type: application/json; charset=utf-8. Ní „Form Field“ sa chiall UI é seo, ach is féidir é a léiriú go soiléir i Multipart:
MP.AddField('metadata', '{"documentType":"report","source":"delphi"}', 'application/json; charset=utf-8');
Legacy: Server akzeptiert nur filename, nicht filename*
Seo áit a gcabhraíonn fallback trí filename. Má dhéanann an freastalaí dícheadú mícheart ar neamh-ASCII i filename, is minic nach mbíonn bealach níos iontaofa ann seachas ainmneacha comhaid a chur ar neamhaird ar thaobh an fhreastalaí agus réimse breise originalName a chur sa JSON.
Einordnung für Modernisierung und Betrieb
I landscapes fhásaithe Delphi bíonn Multipart go minic ar imeall: comhéadan le DMS, cartlann, córas ticéadóireachta, Portál Custaiméirí nó freastalaí inmheánach REST-freastalaí. Is ansin go díreach a thagann brú ó riachtanais slándála nua (TLS, Gateways, Proxies) agus ó mhéideanna comhaid níos mó.
Tá an cur chuige atá curtha i láthair go háirithe úsáideach má:
- tá ort uaslódálacha a dhífhabhtáil go inchomhsheasmhach (Oibríocht/Riarachán)
- má theastaíonn uait Chunked a sheachaint
- tá ainmneacha comhaid/encodings i ndáiríre ag tarlú sa chleachtas (úmlautanna, spásanna, lúibíní)
- má tá Retry/Idempotency le réiteach go coincheapaí go soiléir
Ní bhíonn sé chomh suntasach má sheolann tú go heisiach comhaid bheaga chuig freastalaí éadroime atá foighneach agus nach dteastaíonn aon trédhearcacht oibríochta uait. Sa chás sin tá réiteach simplí ardleibhéil leordhóthanach – go dtí go dtagann an chéad chomhad „aisteach“ ón rannóg ghnó.
Fazit: Stabiler Multipart-Upload ist ein Streaming- und Betriebsproblem
Is í an cheist faoi uaslódáil Multipart/Form-Data ghlan i Delphi ní chomh mór maidir le “cén chomhpháirt” ach maidir le rialú: Boundary, CRLF, ainm comhaid, Content-Type agus thar aon rud eile sruth Body deterministach. Má thógann tú é sin go soiléir go luath, sábhálann sé am níos déanaí i lúibíní dífhabhtaithe le API-Gateways agus Reverse-Proxies.
Teorainn feidhme an chur chuige: Má tá tú ag uaslódáil comhadanna an-mhóra (go leor GB) gan Spooling agus gan Content-Length, bíonn an t-ábhar sruthú gan réamh-ríomh ábhartha – ansin caithfidh na freastalaithe sprioc agus an bonneagar tacaíocht iontaofa a chur ar fáil do Chunked, agus beidh coincheap dífhabhtaithe difriúil uait. Maidir le go leor ionchuimsithe i réitigh fiontair dhigiteacha is é an Builder atá taispeántaithe anseo an mheán pragmatach idir iontaofacht, inrochtana agus tomhaltas acmhainní inrochtana.
Má tá tú gafa le integraíocht Delphi atá fásaithe, ina theipeann uaslódálacha ó am go ham nó amháin „ar roinnt comhad“, is minic gur léiriú é sin ar na coinníollacha teorann sin. Chun tacaíocht shonrach le haghaidh anailíse, nuachóiriú nó soiléiriú ar oibriú, is féidir teagmháil a dhéanamh linn anseo:
Sna comhthéacsanna teicniúla, imríonn Delphi Thttpclient agus REST uaslódáil comhaid API ról tábhachtach, nuair is gá go n-oibreodh integraíochtaí, sreafaí sonraí agus forbairt bhreise le chéile go glan.