1. 程式人生 > >Accept 與 Content-Type

Accept 與 Content-Type

alt form 技術分享 協商 int val options ack printing

原文:Accept 與 Content-Type

Accept

  • Accept 表示請求方希望的資源類型,或者能解解析識別的類型
  • Content-Type 表示實際發送的資源類型

這裏資源類型通過 MIME types 表示。

Accept 是瀏覽器發送的請求頭,用於表示想要的資源類型。根據請求的上下文不同,所設置的 Accept 請求頭會相應變化。服務器根據 content negotiation 規則選擇最合適的類型設置 Content-Type 並返回。

例如請求路由頁面時,Chrome 設置 Accept 為

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

對於頁面中的樣式文件 css,其 Accept 為:

Accept: text/css,*/*;q=0.1

可用的值有以下幾種:

  • <MIME_type>/<MIME_subtype>,精確指定,示例: text/html
  • <MIME_type>/*, 不限制子類型,比如 image/* 會匹配 image/png, image/svg, image/gif 以及其他任何圖片類型
  • */*
    任意 MIME 類型
  • ;q= (q-factor weighting) 多種類型組合的情況,通過指定權重(quality value)來表明每種類型的優先級

Quality value

Header 中逗號分隔的值,每項的權重,或優先級。
比如:

text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

表示:

優先級
text/htmlapplication/xhtml+xml 1.0
application/xml 0.9
*/* 0.8

Content-Type

用來表示資源的類型。某些情況下,瀏覽器會對資源的類型進行嗅探而忽略掉服務器返回的 Content-Type。如果想強制客戶端使用服務器返回的類型,可加上 X-Content-Type-Options:nosniff 響應頭。

支持的值有:

  • media-type,常見的 Content-Type
  • charset,指定資源編碼類型
  • boundary, 多個資源實例情況下,指定資源的分界符。比如 form 表單提交時分隔多個表單字段,見後面示例。

一般情況下,包含在由服務器發送給客戶端的響應頭裏。但也存在瀏覽器發送給服務器的情況,比如 POST 請求,表單提交這種由瀏覽器向服務器發送數據的情況下。

表單的提交類型

form 表單中,提交的內容類型通過 enctype 指定。包含兩種,

  • application/x-www-form-urlencoded 較古老的格式,只支持簡單文本,不支持文件上傳
    • multipart/form-data 較新,支持文件上傳,尺寸較大的二進制數據等

一個表單提交示例

通過 koa.js 搭建一個簡單的表單提交示例,以查看 Content-Type。

server.js

const { createReadStream } = require("fs");
const Koa = require("koa");
const app = new Koa();
const router = require("koa-router")();
const koaBody = require("koa-body");

router
  .get("/", async ctx => {
    ctx.type = "html";
    ctx.body = createReadStream("form.html");
  })
  .post(
    "/submit",
    koaBody({
      multipart: true
    }),
    ctx => {
      console.log("form data is:", ctx.request.body);
      ctx.body = JSON.stringify(ctx.request.body);
    }
  );

app.use(router.routes());

app.listen(3000);

console.log(`server started at http://localhost:3000`);

form.html

<form action="/submit" method="POST" enctype="multipart/form-data">
  foo:<input type="text" name="foo" /> 
  bar:<input type="text" name="bar" />
  <button type="submit">submit</button>
</form>

訪問頁面並提交後,可在 Chrome DevTools 網絡面板看到,/submit 這個 POST 請求相關的信息:

Request Headers

…
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryNgS8sggyuawQSr8W
…

Form Data

------WebKitFormBoundaryNgS8sggyuawQSr8W
Content-Disposition: form-data; name="foo"

1
------WebKitFormBoundaryNgS8sggyuawQSr8W
Content-Disposition: form-data; name="bar"

2
------WebKitFormBoundaryNgS8sggyuawQSr8W--

內容協商/Content Negotiation

前面提到客戶端通過設置 Accept 請求頭設置請求資源的類型,服務器根據 content negotiation 規則返回。

Content negotiation 是這麽一種機制,同一 URI可響應多種資源,客戶端可自行決定請求何種資源(譬如文檔的語言,圖片格式,文件編碼類型)。
?
技術分享圖片
來自 MDN 展示內容協商流程的圖片

內容協商包含兩種方式

  • 客戶端通過設置請求頭由服務器決定合適的類型進行返回(服務器驅動 )
  • 服務器通過設置響應頭中響應代碼為 300 (Multiple Choices)或 406 (Not Acceptable)作為備用方案(客戶端驅動)。

除了 Accept ,用於主動發起內容協商的請求頭還有

  • Accept-Charset
  • Accept-Encoding
  • Accept-Language

服務器驅動的內容協商

由客戶端發送一組期望的類型,服務器根據自己的算法決定出最合適的類型進行返回,具體實現因服務器類型而異。服務器驅動是最常見的方式,但其也有一些明顯的缺點:

  • 由於不是完全了解客戶端的兼容性,服務器的返回有時候存在局限性。相反,客戶端驅動的試是服務器返回多個選擇,客戶端根據自己的情況選用最合適的,因為客戶端自己最了解自己支持哪些。
  • 關於客戶端的信息在多次請求中會冗余(當然,請求頭冗余的情況在 HTTP/2 中所緩解),也存在安全隱患(HTTP fingerprinting)。
  • 多種類型的資源被返回後,服務端的緩存策略不再那麽有效並且實現會變得復雜

客戶端驅動的內容協商

技術分享圖片
來自 MDN 展示客戶端驅動內容協商流程的圖片

得到真實資源前多了一次選擇的請求。

參考

  • Accept
  • Difference between the Accept and Content-Type HTTP headers
  • Content negotiation
  • Content-Type

Accept 與 Content-Type