文件上傳二三事

引子

其實很早就開始醞釀這一篇了,無奈老是發現有缺漏的地方,遂努力惡補前端+後端+底層相關知識。今天終於能夠發表了。 html

--跟生孩子同樣啊。前端

選擇文件

談到文件上傳,不得不提 form,中文名叫表單。它能夠包含一個用來選擇文件的東東,叫作 file。jquery

<form name="uploadForm" action="/upload" method="post" enctype="multipart/form-data">
file:<input type="file" name="anyname"/>
</form>

action 表示表單的數據發送的目標地址,method 表示發送表單所使用的 http 方法(get / post),enctype表示數據的編碼方式,對於文件上傳,必須爲 multipart/form-dataajax

具體的定義參見 formchrome

下面是對應的頁面,能夠看到,有一個提示選擇文件的按鈕json

file-upload

點擊按鈕,就能夠選擇文件啦。後端

choose-file

  • 小貼士:文件選擇好以後,能夠經過 FileReader 進行預覽,或者簡單的編輯。api

如何上傳

簡單的上傳,只須要提交對應的 form 就能夠了。是否是很簡單,O(∩_∩)O哈哈哈~。瀏覽器

加強實現

上面介紹的都太簡單粗暴膚淺了,實際項目中老闆,客戶100%會投反對票。由於實在是太簡陋了。服務器

美化選擇按鈕

瀏覽器提供的原生控件實在是醜的不忍心看,能夠本身畫一個好看的按鈕。

.chooseFile{
    min-width: 30px;
    min-height: 15px;
    width: 106px;
    height: 29px;
    background-color: #B6E2C9;
    color: black;
    font-family: monospace;
    font-weight: 400;
    border-color: white;
    border-radius: 17px;
    padding: 5px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
}

記得把原來的form隱藏掉。

接下來你須要作的是給這個按鈕綁定 click listener ,當它被點擊時,觸發 form 中的 file 的 click 事件。

custom-choose

不想刷新頁面

有些時候,但願上傳時不刷新當前頁面。可是使用 form 是避免不了頁面刷新的。怎麼辦?

第一個想出這個辦法的確定是個頭腦靈活的傢伙--使用隱藏的 iframe 上傳。

原理是,在當前頁面(父頁面)中添加 iframe,iframe 的頁面(子頁面)中包含 form 和相關的函數(驗證,預處理等等)。當用戶在父頁面點擊選擇文件的按鈕時,去觸發子頁面中 file 控件的 click 事件。

當用戶提交時,提交子頁面中的 form。這時,子頁面跳轉,而父頁面沒有刷新。

這個方案有個缺點,就是須要先後端協同工做。

當須要使用回調函數來處理上傳完成後後端返回的數據時,須要和後端預先達成約定,如,回調函數名,參數列表,等等。這對先後端徹底分離的開發場景(好比,你只是開發前端UI)是一個挑戰。(出現全棧工程師的緣由,是否是就是由於前端工程師想把這些依賴可是卻又沒法徹底控制的工做給過來?)

好比,父頁面須定義回調函數

function uploadSuccess (result){
...
}

後端須對action(上面form中定義的/upload)返回html,html包含對回調函數的調用,以及制定參數。

<html>
...
<script>
window.uploadSuccess('xxxxxx');
</sript>
...
</html>

固然,若是你是全棧工程師,這都不算事。本身一我的搞,還約定個啥。

FormData,ajax文件上傳

你說文件上傳這麼常見的功能,咋就不用直接用 js 搞定呢? 非要牽扯什麼 form,什麼 iframe,煩?

客官,看來你須要的是 FormData

FormData 容許經過 js 構造 form ,而後經過 ajax 方式上傳。爲了方便,這裏使用 jquery 的 ajax。

var data = new FormData();
data.append('file', fileObj);

$.ajax({
    url: '/upload',
    type: 'POST',
    data: data,
    cache: false,
    dataType: 'json',
    processData: false, // Don't process the files
    contentType: false, // Set content type to false as jQuery will tell the server its a query string request
    success: function(data, textStatus, jqXHR) {
        console.log(JSON.stringify(data, null, 4));
    },
    error: function(jqXHR, textStatus, errorThrown) {
          //jqXHR may have no responseJSON in old jquery
        console.log(JSON.stringify(jqXHR.responseJSON, null, 4));
    }
});

須要注意的是,processData 必須指定爲false,不然,jquery 會嘗試格式化formData,這會引發一些錯誤。

一些低版本的瀏覽器可能對 FormData 沒有提供支持,因此實際項目中要謹慎使用哦。

文件驗證

有時候,咱們須要對文件進行譬如大小,類型(經過擴展名),名稱的驗證,只有符合預期的才容許上傳。

前端

前端獲取這三個屬性很是簡單。

var file = uploadForm.anyname.files[0];
console.log(file.name);
console.log(file.size);
console.log(file.type);

更詳細的介紹 file api

後端

相對前端來講,因爲涉及到 http 報文的細節,因此稍微複雜一點(意思就是說,我講的頗有多是片面的,錯誤的)。

http 報文,也就是你從瀏覽器的 network 調試窗口看到的 request 信息,它主要包括 header 和 body 兩部分。header 中包含 content-length,也就是發送數據的長度,通常能夠依次做爲對文件大小的判斷。若是後端檢測到它大於預設的最大限制,則返回錯誤給前端。

http 的 body 部分會爲上傳文件的數據的開始和結尾插入邊界,例如,chrome

------WebKitFormBoundarycKtZKQMmA6QfpeMW
Content-Disposition: form-data; name="file"; filename="bt.jpg"
Content-Type: image/jpeg


------WebKitFormBoundarycKtZKQMmA6QfpeMW--

而且,在文件內容以前,是文件的元數據,例如名詞,類型,還有大小。

後端能夠根據邊界的檢驗,識別上傳的文件,讀取元數據中的文件屬性,從而爲驗證提供數據。

有不少文件上傳框架會將文件寫入臨時文件夾後,再作驗證。實際上是很是沒有必要的。徹底能夠在 http 數據開頭的一部分(數據並非一塊兒傳送,而是相似於流的方式)抵達服務器時就完成驗證,從而儘早的返回錯誤,避免沒必要要的數據操做(所謂優化--能不作,儘可能不作。)。

爲何叫二三事

也許叫xxx大全會好一點,不過本人孤傲的不肯意拾人牙慧,只要叫作 二三事 了。所謂 二三,實際上是一堆事。有敘述,有感嘆,有建議。固然,也有吐槽。

後記

補充

  • 除了 file 表單,file對象還能夠從拖拽事件中獲取。

e.dataTransfer.files
  • http body中,上傳文件的邊界能夠由程序指定

var boundary = 'fdfrefdrerefdfd';
xhr.setRequestHeader("Content-Type", "multipart/form-data, boundary="+boundary); // simulate a file MIME POST request.  
xhr.setRequestHeader("Content-Length", fileSize);  
   
var body = '';  
body += "--" + boundary + "\r\n";  
body += "Content-Disposition: form-data; name=\""+dropbox.getAttribute('name')+"\"; filename=\"" + fileName + "\"\r\n";  
body += "Content-Type: "+fileType+"\r\n\r\n";  
body += fileData + "\r\n";  
body += "--" + boundary + "--\r\n";  
   
xhr.sendAsBinary(body);
相關文章
相關標籤/搜索