Der Unterschied und die Erweiterung zwischen form-data und x-www-form-urlencoded

I. Einleitung

form-data und x-www-form-urlencoded, ihre vollständigen Darstellungen sind multipart/form-data und application/x-www-form-urlencoded.

Der Einfachheit halber verwenden wir unten form-data und x-www-form-urlencoded.

Der Unterschied zwischen den beiden ist ein Klischee. Wenn man auf Baidu sucht, gibt es viele Informationen. Aber ich möchte es trotzdem in einem Artikel zusammenfassen, hauptsächlich aus zwei Gründen:

  1. Obwohl form-data und x-www-form-urlencoded grundlegend sind, sind sie wichtig. Und ich bin kürzlich bei der Arbeit zufällig auf diese praktische Falle gestoßen. Nach einigen Recherchen habe ich ein neues Verständnis, daher möchte ich es zusammenfassen.
  2. Der Inhalt des Artikels vergleicht nicht nur die Unterschiede zwischen den beiden, sondern erstreckt sich auch auf Probleme wie HttpServletRequest und das nicht wiederholbare Lesen von Streams, um allen dabei zu helfen, verwandtes Wissen zu verbinden.

Okay, kommen wir ohne weitere Umschweife zum Punkt.

2. Der Unterschied zwischen form-data und x-www-form-urlencoded

form-data und x-www-form-urlencoded sind beide Formate für Formularanfragen. Es gibt zwei Hauptunterschiede.

Die Codierung ist anders

Lassen Sie uns zunächst über x-www-form-urlencoded sprechen. Die Codierungsmethode ist im Namen verborgen: urlencoded. Woran haben Sie gedacht, als Sie dieses Schlüsselwort sahen?

Das ist richtig, es ist die Funktion encodeURI in js. Ich glaube, dass jeder mit der Funktion dieser Funktion vertraut ist, daher werden wir nicht auf Details eingehen.

x-www-form-urlencoded kann als durch encodeURI codierter Abfragestring verstanden werden.

Beispielsweise verwenden wir x-www-form-urlencoded, um den folgenden Inhalt einzureichen:

Name: 张三
Alter: 18

Tatsächlich wird der Anforderungstext im folgenden Format codiert:

Name=%E5%BC%A0%E4%B8%89&Alter=18

Hier muss betont werden, dass das Format von urlencoded nicht das JSON-Format ist, sodass Sie es nicht mit der JSON-Bibliothek in JSON konvertieren können.

Lassen Sie uns als Nächstes über Formulardaten sprechen, die vollständig als Multipart/Formulardaten dargestellt werden.

Das Multipart/Formulardatenformat unterteilt den Formularinhalt in mehrere Teile, was Multipart bedeutet.

Wenn Sie beispielsweise denselben Formularinhalt wie oben verwenden und das Multipart-/Formulardatenformat verwenden, sieht der Anforderungstext etwa wie folgt aus:

--AaB03x

Inhaltsdisposition: Formulardaten; name="name"

Inhaltstyp: Text/einfach

Zhang San

--AaB03x

Inhaltsdisposition: Formulardaten; name="Alter"

Inhaltstyp: Text/einfach

18

--AaB03x--

Lassen Sie uns als Nächstes ausführlich über die Details sprechen.

  • --AaB03x, Grenze, die Grenze, ist die Trennlinie, die verschiedene Teile des Formulars trennt. AaB03x ist eine zufällige Zeichenfolge und es muss sichergestellt werden, dass der gesamte Anforderungstext dieselbe Grenze verwendet.
  • name="name" und name="age", das ist leicht zu verstehen, es ist der Name jedes Teils, der den Feldnamen darstellt.
  • Inhaltstyp, der Inhaltstyp jedes Teils. Da wir hier normalen Text übergeben, verwendet der Inhaltstyp text/plain.
  • --boundary gibt den Anfang eines Teils an und --boundary-- gibt das Ende aller Teile an.

Zusätzlich zum oben genannten Anforderungstext müssen unsere Anforderungsheader wie folgt festgelegt werden:

Inhaltstyp: multipart/form-data; Grenze=AaB03x

Der Schlüsselteil hier ist border=AaB03x. Was ist die Zufallszahl, die die Grenze definiert? Die im Anforderungshauptteil verwendete Zufallszahl muss mit der im Anforderungsheader definierten Zufallszahl übereinstimmen.

Okay, wir haben uns mit den grundlegenden Formaten dieser beiden Codierungsmethoden befasst. Was ist Ihr erster Eindruck? Denken Sie, dass das Format von Multipart/Formulardaten viel komplizierter ist als das Format von x-www-form-urlencoded?

Ja, das ist einer der großen Vorteile von x-www-form-urlencoded – für gewöhnliche Textinhalte benötigt x-www-form-urlencoded weniger Bytes.

Es werden verschiedene Inhaltstypen unterstützt

x-www-form-urlencoded unterstützt nur gewöhnlichen Textinhalt, während jeder Teil von multipart/form-data unterschiedliche Inhaltstypen wie Bilder, Audio, Videos usw. unterstützt.

Fügen wir dem Formular beispielsweise ein Bild hinzu:

--AaB03x

Inhaltsdisposition: Formulardaten; name="name"

Inhaltstyp: Text/einfach

Zhang San

--AaB03x

Inhaltsdisposition: Formulardaten; name="Alter"

Inhaltstyp: Text/einfach

18

--AaB03x

Inhaltsdisposition: Formulardaten; name="Alter"; filename="test.jpg"

Inhaltstyp: Bild/JPEG

xxxxxxxx

--AaB03x--

Anders als bei gewöhnlichem Textinhalt fügen wir einen Dateinamenparameter hinzu, um den Dateinamen darzustellen, und setzen den Inhaltstyp auf image/jpeg, um anzugeben, dass es sich bei dem Format um ein Bild handelt.

Darüber hinaus müssen Sie erkannt haben, dass multipart/form-data nicht im JSON-Format vorliegen und nicht mit der JSON-Bibliothek gelesen werden können.

Als nächstes fassen wir kurz die Unterschiede zwischen den beiden zusammen.

  • x-www-form-urlencoded, ein leichtgewichtiges Formular, das nur normalen Text unterstützt, hat den Vorteil, dass es weniger Bytes belegt.
  • Multipart/form-data unterteilt den Inhalt in mehrere Teile. Jeder Teil unterstützt unterschiedliche Formate. Der Vorteil besteht darin, dass er das Hochladen von Dateien unterstützt, der Nachteil besteht jedoch darin, dass er mehr Bytes beansprucht.

3. getParameter von HttpServeltRequest

Oben haben wir allen die grundlegenden Unterschiede zwischen multipart/form-data und x-www-form-urlencoded erklärt.

Als nächstes erweitern wir einen Inhalt, nämlich den getParameter von HttpServletRequest – eine Methode, mit der wir fast täglich in Kontakt kommen.

Ich dachte immer, dass die getParameter-Methode nur die Parameter der Anforderungsadresse abrufen kann, wie zum Beispiel:

/api/user?name=张三&age=18

Erst vor kurzem habe ich herausgefunden, dass die Dinge alles andere als einfach sind. Aus diesem Grund möchte ich Ihnen diese Inhalte gesondert vorstellen.

Tatsächlich kann getParameter nicht nur die Parameter der Anforderungsadresse abrufen, sondern auch die Parameter im Formular abrufen, einschließlich der Formate x-www-form-urlencoded und form-data.

Ich weiß nicht, waren Sie jemals neugierig, warum getParameterMap das Map<String, String[]>-Format zurückgibt, außer wenn ein Array an die Anforderungsadresse übergeben werden kann, wie zum Beispiel:

/api/user?name=张三&age=18&age=20

Ein weiterer wichtiger Grund ist, dass derselbe Parametername nicht nur in der URL-Adresse, sondern auch im Anfragetext erscheinen kann, wie zum Beispiel in der folgenden Post-Anfrage:

/api/user?name=张三&age=18

Inhaltstyp: application/x-www-form-urlencoded

Alter=20&Geschlecht=männlich

Zu diesem Zeitpunkt hat das Alter zwei Werte, daher wird ein Array zurückgegeben, die Methode getParameter gibt jedoch standardmäßig nur das erste Element des Arrays zurück.

public String getParameter(String name ) {
    handleQueryParameters();
    ArrayList<String> values = paramHashValues.get(name);
    if (values != null) {
        if(values.size() == 0) {
            return "";
        }
        return values.get(0);
    } else {
        return null;
    }
}

Haben Sie bisher etwas entdeckt, das Sie zum Nachdenken anregt? Ich habe mich bisher immer dagegen gewehrt, die getParameterMap-Methode zu verwenden, weil ich es hasse, mit Arrays umzugehen.

Stattdessen verwende ich lieber getParameter. Aber jetzt scheint es, dass diese Bequemlichkeit zu Fehlern in meinem Code führen kann, da getParameter nicht mit der Übergabe von Parametern an Arrays kompatibel ist.

Dies bedeutet natürlich nicht, dass getParameter nicht verwendet werden kann, Sie müssen sich jedoch vor der Verwendung fragen, ob dieser Parameter nur einen einzelnen Wert übergibt.

Hier ist eine weitere Erfahrung: Verwenden Sie HttpServletRequest nach Möglichkeit nicht zum Empfangen von Parametern. Ein besserer Ansatz besteht darin, die Anforderungsparameter in einer Klasse zu kapseln.

Zum Beispiel:

@Data
public class UserDTO {
    private String name;
    private List<Integer> age;
}

Einerseits kann der Datentyp mehr Informationen übermitteln und uns sagen, dass dieser Parameter ein Array übergeben kann; andererseits kann mir der Datenbinder von Spring dabei helfen, alles zu handhaben.

Obwohl getParameter die Parameter in x-www-form-urlencoded und form-data abrufen kann, weisen sie dennoch einige subtile Unterschiede auf.

  • x-www-form-urlencoded: Alle Parameter können abgerufen werden
  • multipart/form-data: Inhalte, die mit dem Dateinamen getaggt sind, können nicht gelesen werden
private void parseRequest(HttpServletRequest request) {
   try {
      Collection<Part> parts = request.getParts();
      this.multipartParameterNames = new LinkedHashSet<>(parts.size());
      MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
      for (Part part : parts) {
         String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
         ContentDisposition disposition = ContentDisposition.parse(headerValue);
         String filename = disposition.getFilename();
         if (filename != null) {
            if (filename.startsWith("=?") && filename.endsWith("?=")) {
               filename = MimeDelegate.decode(filename);
            }
            files.add(part.getName(), new StandardMultipartFile(part, filename));
         }
         else {
             // 只有filename为空part,才会放到multipartParameterNames里面
            this.multipartParameterNames.add(part.getName());
         }
      }
      setMultipartFiles(files);
   }
   catch (Throwable ex) {
      handleParseFailure(ex);
   }
}

Sind Sie immer noch neugierig, ob das erste Element des von getParameter übernommenen Arrays der Parameter im Anforderungstext oder der Parameter in der URL-Adresse ist?

Als ich diesen Artikel schrieb, hatte ich vor, dieses Detail zu behandeln. Aber später sagte mir die Vernunft, ich solle die Verbreitung dieses nutzlosen Wissens aufgeben, da es uns nicht dazu anleitet, besseren Code zu schreiben. Im Gegenteil, es kann uns in die falsche Richtung führen, wie zum Beispiel der folgende Code:

Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap.containsKey("age") && parameterMap.get("age").length > 1) {
    String age = parameterMap.get("age")[1];
}

Wissen Sie, was es ausdrücken will?

Okay, dieser Abschnitt enthält viele Details. Wenn Sie Schwierigkeiten beim Lesen haben, wird empfohlen, ihn ein paar Mal zu lesen.

4. Streamen Sie nicht wiederholbares Lesen

Ich glaube, Sie müssen auf das Problem des nicht wiederholbaren Lesens von Streams gestoßen sein. Möglicherweise haben Sie jedoch Fragen: Was hat das, worüber wir heute sprechen, mit diesem Thema zu tun?

Sagen Sie es mir nicht, es passiert wirklich. Dieses Problem beschäftigt mich in letzter Zeit!

Kürzlich entwickle ich bei der Arbeit ein Schnittstellen-Proxy-Tool, das eine gewisse Verarbeitung des Anforderungshauptteils erfordert. Wenn ich das x-www-form-urlencode-Format teste, kann ich den Anforderungstext nicht abrufen. Durch das Debuggen habe ich herausgefunden, dass getParameter das Problem verursacht hat.

Ich glaube, dass Sie durch die vorherige Einführung bereits den Grund kennen: Für die beiden Formate form-data und x-www-form-urlencoded fragt die getParameter-Methode die Parameter aus dem Anforderungshauptteil ab. Da es sich um den Anforderungshauptteil handelt, von Natürlich wird der InputStream gelesen, daher kann beim späteren Lesen des Streams kein Inhalt abgerufen werden.

Ich glaube, Sie wissen bereits, dass die allgemeine Lösung für dieses Problem darin besteht, einen Filter zu schreiben, um die HttpServletRequest einzuschließen, die Daten im InputStream zu lesen und sie in einem Byte[] zwischenzuspeichern. Die Wrapper-Klasse überschreibt die getInputStream-Methode, um einen ByteArrayInputStream zurückzugeben, und löst so das Problem des wiederholten Lesens verschiedener Streams.

Für die obige Lösung kann ich viele Informationen im Internet finden, daher werde ich den Code nicht veröffentlichen.

Was ich Ihnen heute vorstellen möchte, ist eine andere Denkweise, die das durch getParameter oder getParameterMap verursachte Problem des Stream-Lesens nur teilweise lösen kann.

Ein Teil der Lösung besteht darin, dass diese Methode funktioniert, wenn Sie nur die Parameter an der Anforderungsadresse abrufen möchten – der E/A-Stream wird nicht gelesen.

Die Idee hier ist, den Abfragestring zu analysieren.

Die Abfragezeichenfolge kann über die Methode request.getQueryString abgerufen werden. Der nächste Schlüssel ist, wie man bequem damit umgeht.

Es werden zwei Werkzeugklassen empfohlen, die erste ist UriComponentsBuilder:

String queryString = request.getQueryString();

MultiValueMap<String, String> queryParams = UriComponentsBuilder.fromUriString("?" + queryString)
        .build()
        .getQueryParams();

Diese Klasse wird von Spring bereitgestellt und ist für SpringBoot-Benutzer sofort verfügbar.

Das zweite ist URLEncodedUtils:

String queryString = request.getQueryString();

List<NameValuePair> nameValuePairs = URLEncodedUtils.parse(queryString, StandardCharsets.UTF_8);

Map<String, List<String>> queryParams = nameValuePairs.stream()
        .collect(Collectors.groupingBy(NameValuePair::getName))
        .entrySet().stream()
        .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
        .map(NameValuePair::getValue).collect(Collectors.toList())));

Diese Klasse ist eine von httpclient bereitgestellte Toolklasse und muss das Maven-Paket einführen:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>< /span> </dependency>
    <version>${version}</version>

Diese beiden Toolklassen können uns sehr gut beim Umgang mit Abfragezeichenfolgen helfen. Verwenden Sie die Split-Funktion nicht, um sie dumm aufzuteilen.

5. Zusammenfassung

„Man kann Lehrer werden, indem man die Vergangenheit Revue passieren lässt und Neues lernt.“

Dieser Artikel beginnt mit dem Unterschied zwischen form-data und x-www-form-urlencoded und führt Schritt für Schritt die getParameter-Methode von HttpServletRequest und das dadurch verursachte Problem des nicht wiederholbaren Lesens des Streams ein.

Ich glaube, wenn Sie erneut auf ähnliche Probleme stoßen, sollten Sie problemlos damit umgehen können.

おすすめ

転載: blog.csdn.net/qq_23365135/article/details/130256954