欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

form-data與x-www-form-urlencoded的區(qū)別以及知識延伸

 更新時間:2023年11月30日 10:08:54   作者:代碼君.  
這篇文章主要給大家介紹了關(guān)于form-data與x-www-form-urlencoded的區(qū)別以及知識延伸,form-data和x-www-form-urlencoded都是HTTP請求中用于傳輸表單數(shù)據(jù)的編碼格式,需要的朋友可以參考下

一、前言

form-data和x-www-form-urlencoded,它們完整的表示是multipart/form-data和application/x-www-form-urlencoded。

為了方便,我們下面就用form-data和x-www-form-urlencoded表示。

兩者的區(qū)別,可謂是老生常談,隨便百度一下,也是有大堆資料。可是我還想用一篇文章來總結(jié)一下,主要有兩點原因:

  • form-data和x-www-form-urlencoded雖然是基礎(chǔ),但卻很重要。而且最近在工作中,恰好遇到了這方便的坑。經(jīng)過一番研究,有了新的體悟,所以想要總結(jié)一下。
  • 文章內(nèi)容不只是比較兩個的區(qū)別,還會引申到HttpServletRequest,以及流不可重復(fù)讀等問題,幫助大家把相關(guān)的知識串起來。

好了,閑話少敘,我們進入正題。

二、form-data和x-www-form-urlencoded區(qū)別

form-data 和 x-www-form-urlencoded 都是表單請求的一種格式,主要區(qū)別有兩點。

編碼方式不同

我們先說x-www-form-urlencoded,它的編碼方式就隱藏在名字里:urlencoded??吹竭@個關(guān)鍵詞,你想到了什么?

沒錯,就是js中的encodeURI函數(shù),這個函數(shù)的功能,我相信大家并不陌生,我們就不在贅述了。

x-www-form-urlencoded就可以理解為被encodeURI編碼過的querystring。

比如,我們用x-www-form-urlencoded提交如下內(nèi)容:

name: 張三
age: 18

實際上,請求體會被編碼成如下格式:

name=%E5%BC%A0%E4%B8%89&age=18

這里需要強調(diào)一下,urlencoded這種格式并不是json格式,因此不能使用json庫將其轉(zhuǎn)成json。

接下里,我們來講form-data,完整的表示為multipart/form-data。

multipart/form-data這種格式,會把表單內(nèi)容分成多個部分,這也就是multipart的含義。

比如,同樣是上面的表單內(nèi)容,使用multipart/form-data格式,請求體將會類似下面這樣:

--AaB03x
Content-Disposition: form-data; name="name"
Content-Type: text/plain
張三
--AaB03x
Content-Disposition: form-data; name="age"
Content-Type: text/plain
18
--AaB03x--

接下來,我們詳細說一下其中的細節(jié)。

  • --AaB03x,boundary,也就是邊界,它就是分割表單不同part的分界線。AaB03x是一個隨機字符串,需要保證整個請求體都是用相同的boundary。
  • name="name"和name="age",這個很容易理解,就是每個part的名字,也就是字段名。
  • Content-Type,每個part的內(nèi)容類型,我們這里因為傳的是普通文本,所以內(nèi)容類型使用text/plain。
  • --boundary表示一個part的開始,--boundary--表示所有part都結(jié)束了。

除了上面的請求體,我們的請求頭需要向下面這樣設(shè)置:

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

這里關(guān)鍵的部分就是boundary=AaB03x,定義分界線的隨機數(shù)是什么,請求體中使用的隨機數(shù),需要和請求頭中定義的隨機數(shù)一致。

好了,這兩種編碼方式的基本格式,我們已經(jīng)講完了,你的第一感覺是什么,是不是覺得multipart/form-data的格式,要比x-www-form-urlencoded復(fù)雜的多。

沒錯,這正x-www-form-urlencoded的一大優(yōu)點——對于普通的文本內(nèi)容,x-www-form-urlencoded占用的字節(jié)更少。

支持的內(nèi)容類型不同

x-www-form-urlencoded只支持普通的文本內(nèi)容,而multipart/form-data的每一個part部分,都支持不同的Content-Type,比如圖片、音頻、視頻等。

比如,我們向表單里面添加一個圖片:

--AaB03x
Content-Disposition: form-data; name="name"
Content-Type: text/plain
張三
--AaB03x
Content-Disposition: form-data; name="age"
Content-Type: text/plain
18
--AaB03x
Content-Disposition: form-data; name="age"; filename="test.jpg"
Content-Type: image/jpeg
xxxxxxxx
--AaB03x--

和普通的文本內(nèi)容不同的是,我們增加一個filename參數(shù),來表示文件名稱;并且把Content-Type設(shè)置成image/jpeg,表明它的格式是圖片。

而且,你肯定也意識到了,multipart/form-data也不是json格式,也不能用json庫讀取它。

接下來,我們簡單總結(jié)一下兩者區(qū)別。

  • x-www-form-urlencoded,一種輕型表單,只支持普通文本,優(yōu)點是占用字節(jié)少。
  • multipart/form-data,會把內(nèi)容分成多個部分,每個部分都支持不同的格式,優(yōu)點是支持文件上傳,缺點是占用字節(jié)多。

三、HttpServeltRequest的getParameter

上面,我們已經(jīng)把multipart/form-data和x-www-form-urlencoded的基本區(qū)別,給大家講明白了。

接下來,我們來延伸一個內(nèi)容,就是HttpServletRequest的getParameter——這個我們幾乎每天都會接觸到的方法。

我之前一直以為,getParameter方法只能獲取請求地址上的參數(shù),比如:

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

直到最近,我才發(fā)現(xiàn)事情遠沒有這么簡單。這也是我為什么要單獨給大家引申這塊內(nèi)容的原因。

實際上,getParameter除了能獲取請求地址上的參數(shù),還能獲取表單中的參數(shù),包括x-www-form-urlencoded和form-data這兩種格式。

不知道,你有沒有好奇過,為什么getParameterMap返回的是Map<String, String[]>格式,除了請求地址上可能傳數(shù)組的情況,比如:

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

另外一個重要的原因,就是同一個參數(shù)名,除了可以出現(xiàn)在url地址上,還可以出現(xiàn)在請求體中,比如下面的post請求:

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

Content-Type: application/x-www-form-urlencoded

age=20&gender=male

此時age就有兩個值,所以返回的是數(shù)組,只是getParameter方法默認只返回數(shù)組的第一個元素而已。

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;
    }
}

至此,你有沒有發(fā)現(xiàn)什么細思極恐的事情。在之前我一直排斥使用getParameterMap方法,原因就是我討厭處理數(shù)組。

相反,我更情愿使用getParameter。但現(xiàn)在看來,這種便利可能給我的代碼帶來bug——因為getParameter并不兼容參數(shù)傳數(shù)組的情況。

當(dāng)然,并不是說getParameter不能用,而是在用之前需要問下自己,這個參數(shù)是不是只會傳單個值。

這里還能總結(jié)一個經(jīng)驗:如果可以,不要用HttpServletRequest接收參數(shù),更好的做法是將請求參數(shù)封裝成一個類。

比如:

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

一方面,數(shù)據(jù)類型能夠傳達更多的信息,告訴我們這個參數(shù)可能傳遞一個數(shù)組;另一方便,Spring的data-binder能幫我處理好一切。

雖然,getParameter可以獲取x-www-form-urlencoded和form-data中參數(shù),但是它們還是有一些細微差別。

  • x-www-form-urlencoded:所有的參數(shù)都能被獲取
  • multipart/form-data:不能讀取帶有filename標記的內(nèi)容
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);
   }
}

你是不是還好奇,getParameter取數(shù)組的第一個元素,到底是請求體中的參數(shù),還是url地址上的參數(shù)呢。

在寫這篇文章時,我本打算介紹一下這個細節(jié)。但是后來,理智告訴我放棄傳播這種無用的知識,因為它并不能指導(dǎo)我們寫出更好的代碼。相反可能會將我們引入錯誤的方向,比如下面的代碼:

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

你知道它想表達什么嗎?

好了,這一節(jié)的內(nèi)容細節(jié)有點多,如果你讀起來有點吃力,建議多讀幾遍。

四、流不可重復(fù)讀

流不可重復(fù)讀的問題,相信你一定遇到過。不過,你可能會疑問:我們今天講的內(nèi)容,跟這個問題有什么關(guān)系呢?

你別說,還真有,我最近就被這個問題坑了!

最近,我在工作中要開發(fā)一個接口代理的工具,需要對請求體進行一些處理。當(dāng)我測試x-www-form-urlencode格式時,死活獲取不到請求體。通過debug才發(fā)現(xiàn),原來是getParameter搞的鬼。

相信通過前面的介紹,你已經(jīng)知道原因:對于form-data和x-www-form-urlencoded這兩種格式,getParameter方法會從請求體中查詢參數(shù),既然是請求體,當(dāng)然會讀取InputStream,從而導(dǎo)致后續(xù)再讀取流的時候獲取不到任何內(nèi)容。

這個問題的一般解法,相信你已經(jīng)知道了,就是寫一個Filter對HttpServletRequest包裝,讀取InputStream中的數(shù)據(jù),并緩存到一個byte[]中。包裝類重寫getInputStream方法返回一個ByteArrayInputStream,從而解決流不同重復(fù)讀的問題。

上面的解法,網(wǎng)上的資料一搜一大堆,我就不貼代碼了。

我今天要給你介紹的是另外一種思路,這種思路只能部分解決由于getParameter或getParameterMap導(dǎo)致的流讀取的問題。

這里的部分解決,指的是如果你只是希望獲取請求地址上的參數(shù),那么這個方法能夠奏效——它不會讀取IO流。

這里的思路就是解析querystring。

可以通過request.getQueryString方法獲取查詢字符串,接下來的關(guān)鍵,就是如何方便的處理它。

推薦兩個工具類,第一個是UriComponentsBuilder:

String queryString = request.getQueryString();

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

這個類是Spring提供的,對于SpringBoot用戶來說,開箱即用。

第二個是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())));

這個類是httpclient提供的工具類,需要引入maven包:

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

這兩個工具類,能很好的幫助我們處理querystring,別再傻傻的用split函數(shù)分割了。

五、總結(jié)

“溫故而知新,可以為師矣”。

這篇文章從form-data和x-www-form-urlencoded的區(qū)別為起點,一步步展開,介紹了HttpServletRequest的getParameter方法,及其導(dǎo)致的流不可重復(fù)讀的問題。

到此這篇關(guān)于form-data與x-www-form-urlencoded的區(qū)別以及知識延伸的文章就介紹到這了,更多相關(guān)form-data與x-www-form-urlencoded區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 關(guān)于Elasticsearch封裝公共索引增刪改查

    關(guān)于Elasticsearch封裝公共索引增刪改查

    索引是Elasticsearch中存儲數(shù)據(jù)的邏輯單元,類似于關(guān)系數(shù)據(jù)庫中的表,它包含多個文檔,每個文檔都是一個結(jié)構(gòu)化的JSON數(shù)據(jù)格式,在實際應(yīng)用中,索引的使用與配置可以依據(jù)不同的方案進行,例如在Spring Boot項目中,可以選擇自動配置或者手動編寫配置類
    2024-10-10
  • SpringValidation自定義注解及分組校驗功能詳解

    SpringValidation自定義注解及分組校驗功能詳解

    這篇文章主要介紹了SpringValidation自定義注解及分組校驗功能,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-01-01
  • 基于jvm-sandbox的imock開發(fā)指南詳解

    基于jvm-sandbox的imock開發(fā)指南詳解

    這篇文章主要為大家介紹了基于jvm-sandbox的imock開發(fā)指南詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • SpringBoot中的@EnableAutoConfiguration注解解析

    SpringBoot中的@EnableAutoConfiguration注解解析

    這篇文章主要介紹了SpringBoot中的@EnableAutoConfiguration注解解析,@EnableAutoConfiguration也是借助@Import的幫助,將所有符合自動配置條件的bean定義注冊到IoC容器,需要的朋友可以參考下
    2023-09-09
  • Java8的stream().map()用法詳解

    Java8的stream().map()用法詳解

    這篇文章主要介紹了Java8的stream().map()用法詳解,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • SpringBoot之spring.factories的使用方式

    SpringBoot之spring.factories的使用方式

    這篇文章主要介紹了SpringBoot之spring.factories的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • 一文搞懂Java常見的三種代理模式(靜態(tài)代理、動態(tài)代理和cglib代理)

    一文搞懂Java常見的三種代理模式(靜態(tài)代理、動態(tài)代理和cglib代理)

    Java中常見的三種代理模式是靜態(tài)代理模式、動態(tài)代理模式和CGLIB代理模式,本文就來給大家詳細的講解一下這三種代理模式,感興趣的小伙伴跟著小編一起來看看吧
    2023-08-08
  • java 排序算法之希爾算法

    java 排序算法之希爾算法

    這篇文章主要介紹了java 排序算法之希爾排序,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-08-08
  • Java的三種代理模式簡述

    Java的三種代理模式簡述

    這篇文章主要簡述Java的三種代理模式,java的代理模式主要包括靜態(tài)代理、動態(tài)代理、Cglib代理,感興趣的小伙伴可以參考下面文章的具體內(nèi)容
    2021-09-09
  • mybatis-plus?插入修改配置默認值的實現(xiàn)方式

    mybatis-plus?插入修改配置默認值的實現(xiàn)方式

    這篇文章主要介紹了mybatis-plus?插入修改配置默認值的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07

最新評論