某個工做日的下午,一個前端小夥伴找我說:「個人線上的頁面原來是好好的,今天訪問的時候怎麼變成了下載了,我什麼都沒作呀?」正在這時,又有人反饋全部的web網頁訪問的時候都變成下載了。html
通過緊急的排查,發現問題的緣由是阿里雲某個 CDN
節點的回源請求的構建出現了問題,致使 OSS
源站沒法識別 CDN
請求,並對其插入 content-disposition
頭,最終影響了瀏覽器對文件的處理邏輯。(本應該瀏覽器渲染的,被當作附件下載)。前端
因此今天要和你們聊的就是致使這起線上問題的 Content-disposition
究竟是何方神聖。node
Content-Disposition
有兩種應用場景。web
場景一是用在HTTP的響應頭中,指示響應的內容該以何種形式展現。是之內聯的形式(即網頁或者頁面的一部分),仍是以附件的形式下載並保存到本地。express
Content-Disposition
的第一個參數的值有兩個:npm
inline
默認值,表示響應中的消息體會以頁面的一部分或者整個頁面的形式展現。attachment
表示響應的消息體應被下載到本地;大多數瀏覽器會出現一個「保存爲」的對話框。第二個參數是可選的 filename
。當響應的內第一個參數指定爲 attachment
時,瀏覽器會將響應中的內容下載下來,filename
能夠指定下載後的文件名。瀏覽器
Content-Disposition
在響應頭中可能會這樣出現:bash
// 正常解析渲染
Content-Disposition: inline
// 下載文件
Content-Disposition: attachment
// 下載文件,並將文件保存爲filename.jpg
Content-Disposition: attachment; filename="filename.jpg"
複製代碼
還有一種場景是,當頁面上有表單,而且咱們選擇的表單提交方式爲 multipart/form-data
時,Content-Disposition
會出如今請求體中。其做用是說明對應的表單項的字段名是什麼,表單中上傳的文件名是什麼。在該場景下第一個參數老是固定不變的 form-data
,另外有兩個可選參數。name
表示對應的表單項的字段名,filename
表示對應的上傳的文件名。參數之間使用 ;
進行分割。app
Content-Disposition
在 multipart/form-data
的請求體頭中可能會這樣出現:post
Content-Disposition: form-data
Content-Disposition: form-data; name="fieldName"
Content-Disposition: form-data; name="fieldName"; filename="filename.jpg"
複製代碼
接下來,咱們經過一個示例來加深一下對它的印象。該示例主要演示兩部分功能。
Content-Disposition: attachment
在瀏覽器中下載保存請求的文件。Content-Disposition: form-data
在請求體中的展現形式。建立 contentDispositionDemo
目錄,執行:
npm install express multiparty
複製代碼
服務端代碼咱們使用 express
來實現,同時使用 multiparty
模塊來處理 multipart/form-data
的請求。 服務端代碼以下:
// app.js
const express = require("express");
const multiparty = require("multiparty");
const app = express();
const port = 3000;
app.use(express.static("public"));
// 訪問的時候,彈窗提示下載attachment.html,並保存爲ddd.html
app.get("/attach", (req, res, next) => {
const options = {
root: __dirname,
headers: {
"Content-Disposition": "attachment;filename=ddd.html"
}
};
res.sendFile("/attachment.html", options, err => {
if (err) {
next(err);
} else {
console.log("Sent:", "attachment.html");
}
});
});
// form-data表單提交
app.post("/user", (req, res, next) => {
const form = new multiparty.Form();
var name;
var image;
form.on("error", next);
form.on("close", function(err) {
console.log(err);
console.log(name);
console.log(image);
res.send("資料提交成功!");
});
form.on("field", function(key, val) {
if (key !== "image") name = val;
});
form.on("part", function(part) {
if (!part.filename) return;
if (part.name !== "image") return part.resume();
image = {};
image.filename = part.filename;
image.size = 0;
part.on("data", function(buf) {
image.size += buf.length;
});
});
form.parse(req);
});
app.listen(port, () => console.log(`App listening on port ${port}!`));
複製代碼
接下來咱們建立一個簡單的表單提交頁面,用戶須要填寫姓名和一張照片。咱們將該頁面放在 public
目錄下,頁面代碼以下:
<!--public/index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>content-disposition 示例</title>
<style>
form {
padding: 20px;
border: 1px gray solid;
background: yellowgreen;
}
.form-item {
padding: 10px 0;
}
#commit {
display: block;
width: 80px;
height: 30px;
text-align: center;
line-height: 30px;
border: 1px solid #666;
cursor: pointer;
background: #eee;
}
</style>
</head>
<body>
<h1>請提交您的我的資料</h1>
<form>
<div class="form-item">
<label for="name">姓名:</label>
<input type="text" name="name" id="name" />
</div>
<div class="form-item">
<label for="pic">照片:</label>
<input type="file" name="image" id="image" />
</div>
<div class="form-item"><span id="commit">提交</span></div>
</form>
</body>
<script>
function upload() {
var formData = new FormData();
var name = document.querySelector("#name").value;
var image = document.querySelector("#image").files[0];
formData.append("name", name);
formData.append("image", image);
var xhr = new XMLHttpRequest();
xhr.open("POST", "/user");
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
console.log(xhr.responseText);
alert(xhr.responseText);
}
};
xhr.send(formData);
}
document.querySelector("#commit").addEventListener(
"click",
function() {
upload();
},
false
);
</script>
</html>
複製代碼
另外,再建立一個HTML頁面,該頁面用於瀏覽器訪問的時候下載。具體代碼內容隨意:
<!--attachment.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>保存文件</title>
</head>
<body>
保存文件
</body>
</html>
複製代碼
執行 node app.js
啓動服務,當咱們訪問 localhost:3000/attach
的時候,能夠發現瀏覽器直接下載了 attachment.html
,並將其保存爲了 ddd.html
。
如下爲頁面下載的截圖:
響如下爲響應頭的截圖:
訪問localhost:3000
,在首頁的表單中輸入信息並提交,而後咱們看到下圖中 Content-Disposition
的展現形式:
因爲當咱們使用 Content-Disposition
默認值爲 inline
的時候,在請求的響應頭中是不顯示的,因此我猜不少前端的小夥伴可能對 Content-Disposition
瞭解不夠。其主要用途仍是用來告知瀏覽器如何處理響應中的內容。固然,出現文章開篇的事故是個意外。經過該文章對其有了較爲詳細的瞭解後,相信前端小夥伴們均可以熟練應用它來達到不一樣的目的了。
快狗打車前端團隊專一前端技術分享,按期推送高質量文章,歡迎關注點贊。
文章同步發佈在公衆號喲,想要第一時間獲得最新的資訊,just scan it !