Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Netzwerke (https://www.delphipraxis.net/14-netzwerke/)
-   -   MultipartFormData und TRESTRequest (https://www.delphipraxis.net/211666-multipartformdata-und-trestrequest.html)

Rabenrecht 20. Okt 2022 10:45

MultipartFormData und TRESTRequest
 
So, bin nach langer Zeit (3 Jahre+) mal wieder an einer ernsthaften Entwicklung in Delphi dran: eine Schnittstelle (extern), die bislang über Soap angesprochen wurde, hat den Support für Soap nun endgültig aufgekündigt. Ab jetzt ist die Rest-Api zu verwenden.

Es muss also ein Rest-Api-Client her. Soweit so gut.

Die Rest-Api erwartet an einer Stelle ein multipart-formdata body. Die einzelnen Parts können wiederum die verschiedensten Content Types sein. Ein application/json ist immer dabei und dann diverse Dateien, also application/pdf, image/png, text/plain, whatever.


Und genau da stehe ich auf dem Schlauch, wie ich das in Delphi hinbekomme.

Ich nutze die unter REST.Client bereit gestellten Klassen, dh. TRESTClient, TRESTRequest und TRESTResponse.

Hier komme ich für einen Multipart-Body aber nicht wirklich weiter.
Zwar lässt sich mit FRestRequest.AddParameter('myName', 'myValue', TRESTRequestParameterKind.pkREQUESTBODY) ein Multipart-Body erzeugen, der jeweilige content type lässt sich so aber nicht festlegen. Der content type ist aber ziemlich wichtig :P

Nach etwas Recherche bin ich auf System.Net.Mime.TMultipartFormData gestoßen. Die Klasse bietet ziemlich sinnvoll aussehende Methoden. Zb. TMultipartFormData.AddFile, wo ich einfach nur den Datei-Pfad angeben brauche und sich damit vielleicht auch meine nächste Frage, wie ich denn in Delphi aus einer Datei den passenden Octet-Stream erzeuge, erübrigt ;)

Nur sehe ich gar nicht, wie TMultipartFormData mit den REST.Client Klassen zusammenspielt.


Da dachte ich mir, vielleicht haben ja die Delphi-Experten den ein oder anderen Tipp für mich :-D

Derzeit ist Delphi 10.2 im Einsatz.

Rollo62 20. Okt 2022 11:41

AW: MultipartFormData und TRESTRequest
 
Wenig Zeit ... hilft Dir das weiter ?
https://en.delphipraxis.net/topic/74...#comment-62684

Uwe Raabe 20. Okt 2022 11:44

AW: MultipartFormData und TRESTRequest
 
Wenn du mindestens ein File per AddFile zum Request hinzugefügt hast, wird der ContentType automatisch auf ctMULTIPART_FORM_DATA gesetzt.

Um mehrere AddBody-Calls abzusetzen, ohne dabei den vorigen Body zu überschreiben, funktioniert das nur mit diesem Overload:
Delphi-Quellcode:
procedure AddBody(ABodyContent: string; AContentType: TRESTContentType = ctNone); overload;

Rabenrecht 25. Okt 2022 13:41

AW: MultipartFormData und TRESTRequest
 
Danke, das hat mich auf den richtigen Weg gebracht!

Bin noch über diverse weitere Schwierigkeiten gestolpert, aber dafür mache ich vielleicht besser eigene Threads auf :)

Rabenrecht 26. Okt 2022 16:39

AW: MultipartFormData und TRESTRequest
 
Ok, bin doch noch nicht durch mit dem Thema :(

Ich bekomme es einfach nicht hin, die nicht-file Teile des Bodies sowohl mit einem von mir gewählten Namen UND dem von mir gewählten Content Type hinzuzufügen.

Und dabei nutze ich schon TRESTRequestParameterList.AddItem, bei dem ich beides tatsächlich angeben kann.

Der angegebene Name wird berücksichtigt, der Content Type aber warum auch immer trotzdem auf text/plain gesetzt (anstatt des angegebenen application/json).

Langsam gehen mir die Ideen aus :(

Uwe Raabe 26. Okt 2022 16:52

AW: MultipartFormData und TRESTRequest
 
In dem Fall solltest du mal ein Beispiel geben, wie der Request genau aussehen soll, was du aktuell raus bekommst und wie du das gemacht hast.

Rabenrecht 27. Okt 2022 07:30

AW: MultipartFormData und TRESTRequest
 
Ok :-)

Soll:
Code:
POST /api/derEndpunkt?QueryParameter=1234 HTTP/1.1
Connection: Keep-Alive
Content-Type: multipart/form-data; boundary=--------102722082331395
Accept: application/json, application/*+json
Accept-Charset: UTF-8, *;q=0.8
Authorization: Basic cm9vdDpvcHRpbWFs
Cookie: JSESSIONID=CACA72D331F84414ADF4E7C8CFFE02C2
User-Agent: Embarcadero RESTClient/1.0
Content-Length: 9121
Host: zielhost

----------102722082331395
Content-Disposition: form-data; name="nameJson"
Content-Type: application/json
Content-Transfer-Encoding: quoted-printable

{"key":"value"}
----------102722082331395
Content-Disposition: form-data; name="nameFile"; filename="Test_Bild.png"
Content-Type: image/png
Content-Transfer-Encoding: binary

[das serialisierte Bild]
----------102722082331395--

Was ich mache:
Delphi-Quellcode:
procedure PostObject;
begin
  FRestRequest.Params.Clear;
  FRestRequest.ClearBody;
  FRestRequest.Response.ResetToDefaults;
  FRestRequest.Method := rmPOST;
  FRestRequest.Resource := '/api/derEndpunkt?QueryParameter=1234';

  //Variante 1
  //FRestRequest.AddParameter('nameJson', '{"key":"value"}', false);

  //Variante 2
  //FRestRequest.AddParameter('nameJson', '{"key":"value"}', TRESTRequestParameterKind.pkREQUESTBODY);
  //FRestRequest.Params.ParameterByName('nameJson').ContentType := TRESTContentType.ctAPPLICATION_JSON;

  //Variante 3
  FRestRequest.Params.AddItem('nameJson', '{"key":"value"}', TRESTRequestParameterKind.pkREQUESTBODY, [], TRESTContentType.ctAPPLICATION_JSON);

  //Variante 4
  //FRestRequest.Body.Add('{"key":"value"}',TRESTContentType.ctAPPLICATION_JSON);

  FRestRequest.AddFile('nameFile', 'PfadZurPng', TRESTContentType.ctIMAGE_PNG);

  FRestRequest.Accept := 'application/json, application/*+json';
  FRestRequest.Execute;
end;
Varianten 1, 2 und 3 führen allen zum gleichen Ergebnis:
Code:
POST /api/derEndpunkt?QueryParameter=1234 HTTP/1.1
Connection: Keep-Alive
Content-Type: multipart/form-data; boundary=--------102722082331395
Accept: application/json, application/*+json
Accept-Charset: UTF-8, *;q=0.8
Authorization: Basic cm9vdDpvcHRpbWFs
Cookie: JSESSIONID=CACA72D331F84414ADF4E7C8CFFE02C2
User-Agent: Embarcadero RESTClient/1.0
Content-Length: 9121
Host: zielhost

----------102722082331395
Content-Disposition: form-data; name="nameJson"
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable

{"key":"value"}
----------102722082331395
Content-Disposition: form-data; name="nameFile"; filename="Test_Bild.png"
Content-Type: image/png
Content-Transfer-Encoding: binary

[das serialisierte Bild]
----------102722082331395--
Der Content-Type des Json Parts des Request ist immer text/plain.

Mit Variante 4 wird der Content Type zwar richtig auf application/json gesetzt, der Name ist aber generiert. Klar, habe ja auch keinen explizit angegeben.

TiGü 27. Okt 2022 09:45

AW: MultipartFormData und TRESTRequest
 
Debug doch mal mit Debug-DCUs aktiviert rein?
Du müsstest bei beiden Varianten 3 und 4 in einer Überladung von TRESTRequestParameterList.AddItem enden.
Hier kannst du doch feststellen, wer oder was dir den ContentType überschreibt.

Wenn es da noch okay ist, sollte TCustomRESTRequest.Execute das nächste Debug-Ziel sein.
Da gibt es im oberen Drittel irgendwann den Aufruf von TCustomRESTRequest.ContentType().
Hier würde ich debuggen.
Ältere Delphi-Versionen haben da vielleicht noch nicht alle Spezialitäten drin bzw. unterstützen so Multipart-Sachen noch nicht so gut.

Uwe Raabe 27. Okt 2022 13:44

AW: MultipartFormData und TRESTRequest
 
Das scheint ein Problem mit Delphi 10.2 zu sein. Der verantwortliche Code liegt in in TCustomRESTRequest.DoPrepareRequestBody Zeile 2742
Delphi-Quellcode:
      if LContentType = TRESTContentType.ctMULTIPART_FORM_DATA then
      begin
        // Multipart
        // For multipart names of body parameters are written - in contrast to WWWForm (see below)
        if (LParam.Kind = TRESTRequestParameterKind.pkFile) then
          LMultipartPeerStream.AddFormFile(LParam.Name, LParam.Value, ContentTypeToString(LParam.ContentType))
        else
          LMultipartPeerStream.AddFormField(LParam.Name, LParam.Value);
      end
In Delphi 11 sieht die Sequenz dann so aus
Delphi-Quellcode:
        if SameText(AContentType, TRESTContentType.ctMULTIPART_FORM_DATA) then
        begin
          // Multipart
          // For multipart names of body parameters are written - in contrast to WWWForm (see below)
          if LParam.Stream <> nil then
            LMultipartFormData.AddStream(LParam.Name, LParam.Stream, LParam.Value,
              ContentTypeToString(LParam.ContentType))
          else if LParam.Kind = TRESTRequestParameterKind.pkFile then
            LMultipartFormData.AddFile(LParam.Name, LParam.Value,
              ContentTypeToString(LParam.ContentType))
          else
            LMultipartFormData.AddField(LParam.Name, LParam.Value,
              ContentTypeToString(LParam.ContentType));
        end
Im Gegensatz zu 10.2 wird hier der ContentType des Parameters mit übergeben.

Rabenrecht 27. Okt 2022 15:10

AW: MultipartFormData und TRESTRequest
 
Ja, habe das jetzt auch so im Code nachvollzogen.

Danke fürs Nachschauen, Uwe!

So, als Workarround hätte ich nun die Option, den Json-Teil des Bodys als Datei zu speichern und dann mit AddFile hinzufügen...

Ich habe geschaut, ob ich das irgendwie austricksen kann, aber sobald LParam.Kind = TRESTRequestParameterKind.pkFile ist, wird der Wert des Parameters als Dateipfad interpretiert.
Mein "Json als Datei abspeichern" Umweg scheint die einzige Möglichkeit zu sein, die mir in Tokyo bleibt :freak:

Mal sehen, ob das klappt.


Alle Zeitangaben in WEZ +1. Es ist jetzt 04:42 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz