fetch實(cai)踐(keng)補充篇,文件上傳Content-type multipart/form-data怎麼設置

原諒我是一個標題黨,其實這個坑和fetch關係不大,歸根結底是發送http響應的問題,閒話少說,直接說事。前面本身爲了好玩,在本身的練手項目裏,將vue-resource替換成原生JS的fetch API,使用效果仍是極佳的,徹底沒有其餘原生API那種晦澀感。html

夯實基礎

前面一篇文章fetch已入過門,因此這隻說重點,以前使用vue-resource和fetch時,在Conten-type設置上吃過很多虧,因此本身作了大量功課,重要的事情說三遍,post請求content-type,即數據請求的格式主要設置方式:vue

  • application/x-www-form-urlencoded(大多數請求可用:eg:'name=Denzel&age=18')web

  • multipart/form-data(文件上傳,此次重點說)json

  • application/json(json格式對象,eg:{'name':'Denzel','age':'18'})segmentfault

  • text/xml(如今用的不多了,發送xml格式文件或流,webservice請求用的較多)後端

問題描述

我想經過fetch異步上傳一張圖片到服務器保存,而後返回服務的響應地址(需求很簡單,有沒有)。因而我這樣寫的代碼:服務器

let data =new FormData();
data.append('file',$("#realFile").files[0]);
data.append('name','denzel'),
data.append('flag','test')
const option ={
            method:'post',
            mode:'cors', 
            headers: {
                'Content-Type': 'multipart/form-data'
            },                                                                          
            body:data
};
fetch('http://localhost:8089/Analyse/imgUploadServlet',option)
.then(function(response){
    if(response.ok){
        console.log('suc')
        return response.text();
    }else{
        console.log('網絡錯誤,請稍後再試')
        return ;
    }
}).then(function(data){
    console.log('imgUrl',data);
})

但在服務器上打印的是這樣的錯誤信息(後端幸虧用了try-catch,否則蹦一大堆錯誤,找不死你):網絡

  • 錯誤信息: the request was rejected because no multipart boundary was
    foundapp

很無厘頭有沒有,後端代碼獲取數據前,已經對請求的content-type作了檢查,並且沒有報錯,那說明發送的是文件上傳的請求,沒毛病啊,並且這個上傳文件的後端代碼,之前在jsp頁面中用過啊,沒毛病啊,再在谷歌dev-tools查看一下請求:cors

if (!ServletFileUpload.isMultipartContent(request)) {
        // 若是不是則中止
        PrintWriter writer = response.getWriter();
        writer.println("Error: 表單必須包含 content-type=multipart/form-data");
        writer.flush();
        return;
    }

dev-tools請求信息:

Access-Control-Allow-Methods:POST, GET, OPTIONS, DELETE
Access-Control-Allow-Origin:*
Content-Length:0
Content-Type:text/html;charset=UTF-8
Date:Sun, 16 Jul 2017 01:51:51 GMT
Server:Apache-Coyote/1.1
Request Headers
view source
Accept:*/*
Accept-Encoding:gzip, deflate, br
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6
Connection:keep-alive
Content-Length:68172
content-type:multipart/form-data
Host:localhost:8089
Origin:http://localhost
Referer:http://localhost/
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Request Payload
------WebKitFormBoundaryJ0rfRWvZ56LNpJ1U
Content-Disposition: form-data; name="file"; filename="chn.PNG"
Content-Type: image/png


------WebKitFormBoundaryJ0rfRWvZ56LNpJ1U
Content-Disposition: form-data; name="name"

denzel
------WebKitFormBoundaryJ0rfRWvZ56LNpJ1U
Content-Disposition: form-data; name="flag"

test
------WebKitFormBoundaryJ0rfRWvZ56LNpJ1U--

好像都沒問題,那no multipart boundary究竟是個什麼鬼,只有百度一下

問題癥結

原來不是我一我的遇到這樣的問題,你們都引用的是這樣一句話:

you should never set that header yourself. We set the header properly with the boundary. If you set that header, we won't and your server won't know what boundary to expect (since it is added to the header). Remove your custom Content-Type header and you'll be fine.
翻譯過來就是:

你不該該本身設置請求頭(what ?),咱們會爲請求頭正確設置邊界,但若是你設置了,咱們和你的服務器都無法預知你的邊界是什麼(由於邊界是被自動加到請求頭的),刪除你的自定義Content-Type請求頭設置,問題將會解決(翻譯渣,雖然英語六級飄過,但那已是六年前;了)。

好了,知道問題是啥了,刪除請求頭相關設置(Content-type),再發送,天啦,真的是耶,怎麼會這樣,不是說每一個http請求都應該正確的設置本身的請求數據類型嗎?我之前的書是否是白看了,啊,冷靜,別人說的正確,那再經過dev-tools查看一下請求信息吧。

Accept:*/*
Accept-Encoding:gzip, deflate, br
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6
Connection:keep-alive
Content-Length:88623
content-type:multipart/form-data; boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd
Host:localhost:8089
Origin:http://localhost
Referer:http://localhost/
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Request Payload
------WebKitFormBoundaryAnydWsQ1ajKuGoCd
Content-Disposition: form-data; name="file"; filename="Screenshot_2017-05-23-11-41-22-090_com.wacai365.png"
Content-Type: image/png


------WebKitFormBoundaryAnydWsQ1ajKuGoCd
Content-Disposition: form-data; name="name"

denzel
------WebKitFormBoundaryAnydWsQ1ajKuGoCd
Content-Disposition: form-data; name="flag"

test
------WebKitFormBoundaryAnydWsQ1ajKuGoCd--

與上面失敗的請求一比較,發現content-type後面竟然多跟了boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd這樣一串火星字符,再看發送的數據,數據之間都被請求數據類型的那個boundary字符串分割開,好像,我是有點懂,什麼叫邊界了,就是發http請求規定的數據交換規則.相似於:A發送請求給B,並告訴B,我給你送來了三個快遞(但爲了好搬運,我將它捆成了一個包裹),包裹拆分的規則在快遞單上有說明,因而B就按A說的規則,進行包裹拆分。

問題討論:post請求Content-type到底該不應設置

我得出的結論是,要正確設置。fetch 發送post字符類請求時,

  1. 非文件上傳時,無關你發送的數據格式是application/x-www-form-urlencoded或者application/json格式數據,你不設置請求頭,fetch會給你默認加上一個Content-type = text/xml類型的請求頭,有些第三方JAX能夠本身識別發送的數據,並本身轉換,但feth絕對不會,不行,你能夠試一下;

  2. 文件上傳請求時,由於不知道那個boundary的定義方式,因此就如建議的同樣,咱們不設置Content-type。

若是本文有描述不正確的,歡迎指正,一塊兒討論,畢竟本身知識有限

相關文章
相關標籤/搜索