Hi gays, 你造Content-Disposition嗎?

背景

某個工做日的下午,一個前端小夥伴找我說:「個人線上的頁面原來是好好的,今天訪問的時候怎麼變成了下載了,我什麼都沒作呀?」正在這時,又有人反饋全部的web網頁訪問的時候都變成下載了。html

通過緊急的排查,發現問題的緣由是阿里雲某個 CDN 節點的回源請求的構建出現了問題,致使 OSS 源站沒法識別 CDN 請求,並對其插入 content-disposition頭,最終影響了瀏覽器對文件的處理邏輯。(本應該瀏覽器渲染的,被當作附件下載)。前端

因此今天要和你們聊的就是致使這起線上問題的 Content-disposition 究竟是何方神聖。node

Content-Disposition

Content-Disposition 有兩種應用場景。web

用在HTTP響應頭中

場景一是用在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的請求體中

還有一種場景是,當頁面上有表單,而且咱們選擇的表單提交方式爲 multipart/form-data 時,Content-Disposition 會出如今請求體中。其做用是說明對應的表單項的字段名是什麼,表單中上傳的文件名是什麼。在該場景下第一個參數老是固定不變的 form-data,另外有兩個可選參數。name 表示對應的表單項的字段名,filename 表示對應的上傳的文件名。參數之間使用 ; 進行分割。app

Content-Dispositionmultipart/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 在瀏覽器中下載保存請求的文件。
  • 經過實現一個 post 請求,來講明 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 !

公衆號二維碼
相關文章
相關標籤/搜索