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

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

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

一、前言

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

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

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

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

好了,閑話少敘,我們進(jìn)入正題。

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

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

編碼方式不同

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

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

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

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

name: 張三
age: 18

實(shí)際上,請(qǐng)求體會(huì)被編碼成如下格式:

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

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

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

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

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

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

接下來,我們?cè)敿?xì)說一下其中的細(xì)節(jié)。

  • --AaB03x,boundary,也就是邊界,它就是分割表單不同part的分界線。AaB03x是一個(gè)隨機(jī)字符串,需要保證整個(gè)請(qǐng)求體都是用相同的boundary。
  • name="name"和name="age",這個(gè)很容易理解,就是每個(gè)part的名字,也就是字段名。
  • Content-Type,每個(gè)part的內(nèi)容類型,我們這里因?yàn)閭鞯氖瞧胀ㄎ谋?,所以?nèi)容類型使用text/plain。
  • --boundary表示一個(gè)part的開始,--boundary--表示所有part都結(jié)束了。

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

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

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

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

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

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

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

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

--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)容不同的是,我們?cè)黾右粋€(gè)filename參數(shù),來表示文件名稱;并且把Content-Type設(shè)置成image/jpeg,表明它的格式是圖片。

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

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

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

三、HttpServeltRequest的getParameter

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

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

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

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

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

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

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

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

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

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

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

age=20&gender=male

此時(shí)age就有兩個(gè)值,所以返回的是數(shù)組,只是getParameter方法默認(rèn)只返回?cái)?shù)組的第一個(gè)元素而已。

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)什么細(xì)思極恐的事情。在之前我一直排斥使用getParameterMap方法,原因就是我討厭處理數(shù)組。

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

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

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

比如:

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

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

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

  • x-www-form-urlencoded:所有的參數(shù)都能被獲取
  • multipart/form-data:不能讀取帶有filename標(biāo)記的內(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,才會(huì)放到multipartParameterNames里面
            this.multipartParameterNames.add(part.getName());
         }
      }
      setMultipartFiles(files);
   }
   catch (Throwable ex) {
      handleParseFailure(ex);
   }
}

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

在寫這篇文章時(shí),我本打算介紹一下這個(gè)細(xì)節(jié)。但是后來,理智告訴我放棄傳播這種無用的知識(shí),因?yàn)樗⒉荒苤笇?dǎo)我們寫出更好的代碼。相反可能會(huì)將我們引入錯(cuò)誤的方向,比如下面的代碼:

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

你知道它想表達(dá)什么嗎?

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

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

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

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

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

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

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

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

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

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

這里的思路就是解析querystring。

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

推薦兩個(gè)工具類,第一個(gè)是UriComponentsBuilder:

String queryString = request.getQueryString();

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

這個(gè)類是Spring提供的,對(duì)于SpringBoot用戶來說,開箱即用。

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

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

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

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

五、總結(jié)

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

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

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

相關(guān)文章

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

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

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

    SpringValidation自定義注解及分組校驗(yàn)功能詳解

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

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

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

    SpringBoot中的@EnableAutoConfiguration注解解析

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

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

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

    SpringBoot之spring.factories的使用方式

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

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

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

    java 排序算法之希爾算法

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

    Java的三種代理模式簡(jiǎn)述

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

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

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

最新評(píng)論