This is the sign you've been looking for neon signage

Die Krux der REST-Calls: FormData & Boundary

Eine Boundary ist eine Zeichenfolge, die verwendet wird, um die einzelnen Teile des Inhalts eines HTTP-Requests oder -Responses zu trennen, wenn der Inhalt als multipart/form-data codiert ist.

Der Boundary wird als Trennzeichen zwischen den einzelnen Teilen des Inhalts verwendet und besteht aus einer Zeichenfolge, die von zwei hyphen (-) Symbolen umgeben ist. Jeder Teil des Inhalts wird von zwei Boundary-Zeichenfolgen umgeben, die als Prefix und Suffix für den Teil dienen.

Die Boundary-Zeichenfolge darf nicht im Inhalt der Teile vorkommen, da sie als Trennzeichen zwischen den einzelnen Teilen des Inhalts verwendet wird. Wenn die Boundary-Zeichenfolge im Inhalt der Teile vorkäme, wäre es für den Server schwierig zu bestimmen, wo ein Teil beginnt und endet. Die Boundary-Zeichenfolge wird verwendet, um die Teile voneinander zu trennen und um anzugeben, wo ein Teil beginnt und endet.

Wenn die Boundary-Zeichenfolge im Inhalt der Teile vorkäme, würde der Server möglicherweise denken, dass das Auftauchen der Boundary-Zeichenfolge das Ende eines Teils markiert, wodurch der Inhalt des Teils unvollständig wäre. Deshalb ist es wichtig, dass die Boundary-Zeichenfolge nicht im Inhalt der Teile vorkommt.

Die Boundary-Zeichenfolge wird im „Content-Type“-Header des HTTP-Requests oder -Responses angegeben. Wenn wir uns jetzt ein REST-Call in der Konsole des Browsers näher anschauen, sehen wir im Header – Anfrageheader folgendes:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7Hp2HdBDdrmOBiyf

Die Nutzlast zeigt uns folgendes an:

------WebKitFormBoundary7Hp2HdBDdrmOBiyf
Content-Disposition: form-data; 
name="profilePicture"; 
filename="pexels-photo-823301.jpeg"
Content-Type: image/jpeg
------WebKitFormBoundary7Hp2HdBDdrmOBiyf
Content-Disposition: form-data; 
name="username"
DenHerrRing
------WebKitFormBoundary7Hp2HdBDdrmOBiyf
Content-Disposition: form-data; 
name="data"
{Hier stehen Daten als JSON String}
------WebKitFormBoundary7Hp2HdBDdrmOBiyf--

Wie man gut sehen kann, trennt der Boundary ------WebKitFormBoundary7Hp2HdBDdrmOBiyf alle Felder voneinander und am Ende der Nutzlast sind nochmal zwei „-“ – Zeichen, um zu signalisieren, dass hier das Ende der Form ist.

Soweit, so gut.

Wie aber wird ein Boundary erzeugt, bzw. generiert?

In der Regel wird die Boundary-Zeichenfolge vom Browser generiert, der das Formular absendet. Der Browser wählt eine zufällige Zeichenfolge aus, die als Boundary-Zeichenfolge verwendet wird, und fügt sie dem „Content-Type“-Header des ausgehenden HTTP-Requests hinzu. Der „Content-Type“-Header des ausgehenden HTTP-Requests sieht dann ungefähr wie folgt aus:

"Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"

Die Zeichenfolge ----WebKitFormBoundary7MA4YWxkTrZu0gW ist die Boundary-Zeichenfolge.

Es ist auch möglich, dass der Server die Boundary-Zeichenfolge festlegt. In diesem Fall würde der Server die Boundary-Zeichenfolge im „Content-Type“-Header des HTTP-Responses angeben. Der Client, der das Formular absendet, muss dann die Boundary-Zeichenfolge im „Content-Type“-Header des ausgehenden HTTP-Requests verwenden, um sicherzustellen, dass der Inhalt des Formulars korrekt codiert wird.

Wie man schnell merkt, ist die Boundary Zeichenfolge nichts für uns, da bereits zwei Parteien für die Erstellung zuständig sind. Somit haben wir als Benutzer / Programmierer keinen Einfluss dartauf, wie die Boundary-Zeichenfolge generiert wird.

Es gibt jedoch einen Fall, in dem Wir die Boundary-Zeichenfolge manuell festlegen können. Wenn das Formular über JavaScript abgesendet wird, können Wir die Boundary-Zeichenfolge manuell festlegen, indem Wir sie als Option in die „send“ Methode übergeben. Hier ist ein Beispiel:

var boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://example.com/upload", true);
xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
xhr.send(formData);

In diesem Beispiel wird die Boundary-Zeichenfolge manuell auf ----WebKitFormBoundary7MA4YWxkTrZu0gW festgelegt.

Du fragst dich jetzt bestimmt, wieso ich diesen Beitrag verfasst habe?! In den meisten Fällen ist der Content-Type = application/json oder Boundary werden vom Server / Client definiert. Was ist aber, wenn dem nicht so ist? Ich hatte erst vor kurzem einen Server, der strikt Boundary haben wollte, Sie selber aber nicht definiert hat. Zudem kam hinzu, dass Angular zum Beispiel diese auch nicht erstellt hat. Dies hatte zur folge, dass mein Call so aussaht:

------WebKitFormBoundaryyO8s8ywQ5L8PGweW
Content-Disposition: form-data; 
name="profilePicture"; 
filename="pexels-photo-823301.jpeg"
Content-Type: image/jpeg
------WebKitFormBoundaryyO8s8ywQ5L8PGweW
Content-Disposition: form-data; 
name="username"
DenHerrRing
------WebKitFormBoundaryyO8s8ywQ5L8PGweW
Content-Disposition: form-data; 
name="data"
{Hier stehen Daten als JSON String}
------WebKitFormBoundaryyO8s8ywQ5L8PGweW--

Dieser hat wie gewünscht Boundary Zeichenfolgen, welche von meinem Browser erstellt wurden. Allerdings konnte der Server diese nicht richtig auflösen, da der Header wie folgt aussah:

content-type: multipart/form-data

Somit hat der HttpClient von Angular keine Boundary-Property mit geschickt, und der Server konnte dies nicht verarbeiten:

[PATCH] Error 400 Bad Request: Failed to load the submitted data due to invalid formatting.

Mein Observable sah wie folgt aus:

uploadFile(colectionId: string, data: FormData): Observable<any> {
        let headers = new HttpHeaders();
        headers = headers.append('content-type', 'multipart/form-data');
        return this.httpClient.patch<UserResponse>(`${this.dbUrl}/${colectionId}`, data, {headers: headers})
    }

Der HttpClient hat der Response den Header content-type: multipart/form-data gegeben, allerdings ohne zu wissen, welche Boundary-Zeichenkette der Browser erstellt hat.

Dies lässt sich schnell fixen. Anstatt content-type übergeben wir den Header enctype und unser Code sieht dann wie folgt aus:

uploadFile(colectionId: string, data: FormData): Observable<any> {
        let headers = new HttpHeaders();
        headers = headers.append('enctype', 'multipart/form-data');
        return this.httpClient.patch<UserResponse>(`${this.dbUrl}/${colectionId}`, data, {headers: headers})
    }

Enctype bewirkt, dass der Header content-type um die Boundary-Zeichenkette, welche der Browser erstellt, erweitert wird und der Server unseren Request dann richtig auflösen kann.

Genauer gesagt, wird content-type dazu verwendet, um den MIME-Typ des Inhalts zu kennzeichnen, während enctype das Formularkodierungsformat definiert, das verwendet wird, wenn das Formular an den Server gesendet wird. Wenn Du also multipart/form-data als Wert für enctype verwendest, wird der Browser dafür sorgen, dass der Inhalt des Formulars entsprechend kodiert wird und einen Boundary-Parameter im Content-Type-Header des ausgehenden HTTP-Requests hinzufügt.

Ich hoffe, ich konnte dir den Unterschied zwischen den Headern content-type und enctype gut erklären und dir dabei helfen, nicht in die selbe Krux zu fallen, wie ich es bin.

Solltest du Fragen oder Anregungen haben, melde dich einfach bei mir!