在使用 fetch / axios 時經常會涉及到文件上傳,以及其餘請求,其中包括一些 content-type ,被這些不一樣類型到 content-type 搞得頭大,到底何時該用怎麼樣的類型呢,本文將會梳理這些問題。javascript
表單<form>用來收集用戶提交的數據,發送到服務器。下面代碼中包含:文件選擇框(獲取本地文件),提交按鈕(提交表單控件)。html
<form action="http://localhost:8899/react/aa" method="post" enctype="multipart/form-data">
<input type="file" name="image" multiple="multiple" />
<input type="submit" value="提交"/>
</form>
複製代碼
用戶點擊「提交」按鈕,每個控件都會生成一個鍵值對,鍵名是控件的name屬性,鍵值是控件的value屬性。咱們採用node做爲服務端,使用 koa-body 解析 post 方式傳遞的文件前端
// 服務端獲取請求中的文件
router.post('/aa', async ctx => {
console.log(ctx.request.files);
})
複製代碼
表單數據以鍵值對的形式向服務器發送,這個過程是瀏覽器自動完成的。可是有時候,咱們但願經過腳本完成過程,構造和編輯表單鍵值對。瀏覽器原生提供了 FormData 對象
來完成這項工做。java
let formdata = new FormData(form);
複製代碼
FormData()構造函數的參數是一個表單元素,這個參數是可選的。若是省略參數,就表示一個空的表單
,不然就會處理表單元素裏面的鍵值對。通常咱們使用的方法就是構建一個空的表單對象,FormData 提供不少實例方法,咱們能夠經過 append 方法來添加表單中的鍵值對。node
formdata.append(key1,value1)
formdata.append(key2,value2)
複製代碼
下面的代碼經過 axios 提交 formdata 表單數據來實現文件上傳react
<input type="file" @change="onChange">
methods:{
onChange(event){
const params = new FormData()
params.append('file',event.target.files[0])
axios.post('http://localhost:8899/react/aa',params,{
headers:{
'content-type':'multipart/form-data'
}
})
}
}
複製代碼
查看了 axios 源碼
發現其實上傳文件不須要設置 content-type 源碼 lib/adapters/xhr.js
文件中定義了瀏覽器使用 XHR :ios
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
// config 是傳入的配置對象 如: {url,method,data,headers}
// 獲取傳入的參數和請求頭
var requestData = config.data;
var requestHeaders = config.headers;
// 判斷是否爲 formData 實例,若是是刪除 請求頭中的 content-type
if (utils.isFormData(requestData)) {
delete requestHeaders['Content-Type']; // Let the browser set it
}
})
}
複製代碼
isFormData 實現: FormData 就是表單對象的構造函數, 使用 instanceof 來檢測構造函數的 prototype 屬性是否出如今實例對象的原型鏈上。git
/**
* Determine if a value is a FormData
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an FormData, otherwise false
*/
function isFormData(val) {
return (typeof FormData !== 'undefined') && (val instanceof FormData);
}
複製代碼
lib/defaults.js
文件github
defaults.headers = {
common: {
'Accept': 'application/json, text/plain, */*'
}
};
utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
defaults.headers[method] = {};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
複製代碼
當使用 axios 發起請求時,會經過默認設置的 transformRequest 在發送給服務器前改變請求的數據,'PUT', 'POST', 'PATCH' and 'DELETE'
只對這幾種請求方式有效。chrome
// lib/defaults.js 文件
transformRequest: [function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
return data;
}],
複製代碼
因此通常狀況下使用 axios 請求不須要設置 content-type 若是有些特殊狀況須要處理的能夠放在 transformRequest:[function(data,headers){return data}] 中作處理
爲了展現清楚,直接簡化fetch請求的封裝,具體能夠查看下面的 codepen
示例,這裏主要是爲了展現使用 fetch 上傳文件時,同時設置了 headers 請求時會出現什麼問題,將代碼設置以下:
fetch(api,{
url,
headers:{
'content-type':'multipart/form-data'
},
mode:'no-cors'
})
複製代碼
500 (Internal Server Error)
是請求api服務端報的錯,
Unexpected end of input
是瀏覽器運行 response.json()報的錯。
爲何瀏覽器會報 Unexpected end of input 後面我會單獨講,接下來咱們看下服務端的報錯緣由。
在上傳文件時
,不須要設置 headers 字段,瀏覽器會自動生成完整的 content-type(包含 boundary)。
移除 headers 字段後,fetch api能夠正常的上傳文件了!!
其實這就是 js 語法錯誤,下面代碼中使用 fetch api 獲取資源後返回一個響應的 response 對象,若是咱們指定 content-type 爲 application/json
那麼服務端會返回給咱們一個 json 格式的字符串,因此當咱們調用 resopnse.json() 時候能夠解析出正確當對象。
fetch(api).then(response=>response.json()).then(res=>res)
複製代碼
你能夠把 resopnse.json() 理解爲 JSON.parse(),因此能夠經過 JSON.parse() 來模擬前面的 Unexpected end of input
報錯瀏覽器在讀取咱們的代碼時,碰到了不可預知的錯誤,致使瀏覽器 無語進行下面的讀取
以下面的代碼都會輸出這個錯誤。
JSON.parse("{")
JSON.parse('[{"test": 4}')
複製代碼
常見都還有 Unexpected token < in JSON at position 0
繼續模擬下該錯誤發生的場景,前端繼續使用 response.json
去解析服務端返回的數據。而在服務端不按照 content-type 預約的值傳回,就會獲得這個報錯。
// 服務端
router.post('/aa', async ctx => {
ctx.body = '<div>內容</div>'
})
複製代碼
若是想簡單的模擬直接使用 JSON.parse("<h1>1</h1>")
就會獲得一樣的結果。下面的代碼都是一個道理。
JSON.parse("{sd}")
// Unexpected token s in JSON at position 1
JSON.parse("d}")
// Unexpected token d in JSON at position 0
複製代碼
JSON.parse()支持的類型以下:
JSON.parse('{}'); // {}
JSON.parse('true'); // true
JSON.parse('"foo"'); // "foo"
JSON.parse('[1, 5, "false"]'); // [1, 5, "false"]
JSON.parse('null'); // null
JSON.parse('1'); // 1
複製代碼
因此想要正確的解析服務端的返回值,先後端要統一設定好 content-type 對應傳輸的數據類型,響應對象response也支持其餘多個方法
請求方式爲 get 只會使用 application/x-www-form-urlencoded 編碼方式
axios.get('/user',{
params:{
id:1,
name:'dd',
person:'張三'
}
})
複製代碼
理論上會請求 http://localhost:8080/user?id=1&name=dd&person=張三
可是對於特殊字符會進行 Url編碼。
Url編碼一般也被稱爲百分號編碼(Url Encoding,also known as percent-encoding),是由於它的編碼方式很是簡單,使用%百分號加上兩位的字符——0123456789ABCDEF——表明一個字節的 十六進制形式。Url編碼默認使用的字符集是US-ASCII。例如a在US-ASCII碼中對應的字節是0x61,那麼Url編碼以後獲得的就 是%61
因此通過編碼之後實際請求路徑變成 http://localhost:8080/user?id=1&name=dd&person=%E5%BC%A0%E4%B8%89
name=hehe&age=10
格式,引入若是你傳入對是一個對象,在 axios 默認配置中會將 content-type 設置爲 application/json;charset=utf-8axios.post('http://localhost:8899/react/aa','我就是內容',{ headers:{ 'content-type':'text/plain' } })
axios.post('http://localhost:8899/react/aa',{name:'dd',age:18})
get 傳參數的方式須要添加到路徑上,因此 Url編碼的工做須要咱們手動實現
// 在URL中寫上傳遞的參數
fetch('http://localhost:8080?a=1&b=2', {
method: 'GET'
})
// 處理傳入的 params 參數
for(let key in params){
param += `${key}=${encodeURIComponent(params[key])}&`
}
複製代碼
fetch('http://localhost:8080',{
method:'POST',
headers:{
'content-type':'application/x-www-form-urlencoded'
},
mode:'no-cors',
body:'name=dd&age=12'
})
複製代碼
須要使用 JSON.stringify() 將對象轉換成 JSON 字符串,body做爲接受數據的字段
fetch(url, {
method: 'POST', // or 'PUT'
body: JSON.stringify(data),
headers: new Headers({
'Content-Type': 'application/json'
})
})
複製代碼
axios
FormData 對象的使用
Error when POST file multipart/form-data
Chrome: Uncaught SyntaxError: Unexpected end of input
Web開發須知URL編碼與解碼 請求頭截圖