为什么要学习……媒体类型
媒体类型用于传达通过 Web 发送的数据类型。它们用于
- 在 HTTP 响应中声明发送的内容类型
- 在请求中声明客户端可以解释哪些类型的内容
- 在 POST 请求中声明所发送内容的类型。
基本上,网络上哪里有数据,哪里就会有一个媒体类型来告诉你它是什么类型的数据。如果没有,那也应该有一个。
本文将解释如何解释媒体类型,并解释如何在 HTTP 标头中使用它们以确保我们获得能够理解的数据。
这是给谁的?
本指南面向有兴趣了解如何在 Web 上传输数据的 Web 开发者。了解一些 HTTP 知识(尤其是 HTTP 标头)会有所帮助,但并非必需。
我以前在哪里见过媒体类型?
我第一次看到媒体类型是在我编写第一个 HTML 文件时 - 特别是为了获取一些 CSS:1
<link rel="stylesheet" src="style.css" type="text/css" />
这是我们的第一个媒体类型- text/css
。
我下次遇到它们是在从服务器返回 JSON 时:
response.contentType = "application/json"
还有一个—— application/json
。
所以它们对我们来说并不完全陌生——它们是一种表达“文件里全是 CSS”和“我要给你发送一些 JSON”的方式。我们来看一下语法,以这两种类型为例。
媒体类型语法
媒体类型由两个用 分隔的字段组成/
。第一个是type
,第二个是subtype
。
我们一眼就能看出,CSS(text/css
)有一个类型text
和一个子类型css
。该text
类型非常广泛——它表示媒体只是文本,涵盖了相当多类型的内容——例如:
text/html
——这就是您现在正在阅读的内容!text/plain
- 这只是纯文本!text/markdown
- 我写的这个将会变成text/html
text/csv
...
你明白了。
application/json
有一个子类型json
,这很合适,因为它是JSON。该application
类型告诉我们这是一种旨在由应用程序处理的类型。这很有道理,因为它是 JSON,旨在由计算机读取。
(但 CSS 不是只能由应用程序处理吗?是的,确实如此。也许它应该是application/css
。也许它应该是text/json
。谁知道呢。媒体类型有点不稳定。整个网络都有点不稳定。不要太担心。作为一名 Web 开发人员,一部分就是要学会忍受在我们中的一些人出生之前就存在的相当多的混乱和模糊性。学会忍受它。)
其他有趣的application
类型
application/pdf
对于 PDFapplication/zip
对于 zip 文件- 还有很多很多……
所有类型
我们已经看到了text
和application
类型 - 这里列出了您可能会遇到的类型以及示例:
类型 | 目的 | 例子 |
---|---|---|
application |
应用程序消费媒体 | application/json |
audio |
音频媒体 | audio/mp3 |
font |
字体格式 | font/ttf |
image |
视觉媒体 | image/gif |
multipart |
需要分批发送的媒体 | multipart/form-data |
text |
人类可读的文本 | text/html |
video |
视频媒体 | video/mp4 |
类型和子类型是媒体类型中唯一必需的部分,但也有几个可选部分。
参数
媒体类型可以在分号后标记一个参数列表,这些参数由以 分隔的键/值对组成=
。例如
text/plain;charset=UTF-8
是一种text/plain
使用 UTF-8 字符编码的媒体类型。如果我们想让我们的表情符号正常工作,我们需要记住声明我们使用的是 UTF-8,否则客户端会像 20 世纪 70 年代的穴居人一样,认为我们是在用 ASCII 码书写。
参数类型不受媒体类型规范的限制,2并且您可以根据需要拥有任意数量的参数类型:
text/plain;charset=UTF-8,blog-post=true,author=gypsydave5
它们通过上下文获得含义;例如,浏览器charset
在通过 HTTP 读取媒体类型时会查找并使用参数。稍后会详细介绍浏览器。
结构化语法名称后缀
这个有点奇怪,但也很酷,所以请耐心听我说完。看一下这个媒体类型:
image/svg+xml
这是 SVG 图像的类型 - 可缩放矢量图形。image
是类型,svg
是子类型 - 这正是我们所期望的。
但这+xml
到底是干什么用的呢?嗯,你可能已经知道,SVG 是用 XML 编写的——这是 SVG 最棒的地方之一。所以,这+xml
告诉我们 SVG 的语法是 XML;你可以说 XML 是 SVG 的“基础”语言。这被称为“结构化语法名称”,它巧妙地告诉我们,嘿,你可能不知道 SVG 是什么,但没关系,它底层就是 XML,所以你没问题。
(除非你是我,每次听到 XML 这个词都会感到恐慌,你觉得有些人认为它是人类可读的,这很荒谬,你希望整个世界都用 JSON 来表示,但那是另一个故事......)
瞧,我看得出来你对这些媒体类型一定很感兴趣。现在你已经了解了如何理解它们,你可以去看看所有已在互联网号码分配机构(IANA)注册的媒体类型的列表。快去读吧,现在就把它们全部读完。我等着呢。
有很多,对吧?别着急——你可能最多也就用到其中十个。不过,你看到那个适用于 SNES ROM 的了吗?
HTTP 消息头中的媒体类型
使用媒体类型最重要的地方是在 HTTP 消息中。您将使用它们来描述 HTTP 请求标头中所需的数据的媒体类型,以及HTTP 响应标头Accept
中要发送的数据类型。Content-Type
标题Accept
标Accept
头是客户端(如浏览器)告诉服务器它想要什么类型内容的方式。
最简单的情况是这样的:
Accept: application/json
如果我把这个放在请求的标头中,就等于明确地告诉服务器我想要 JSON 格式的数据。服务器不会有任何疑问。3
但是,假设你想从服务器获取一些音频,但不介意哪种子类型对你来说audio/mp3
都一样好audio/wav
。在这种情况下,你可以这样写:
Accept: audio/*
*
表示通配符——意思是“给我任何东西”。服务器可以合法地回复任何audio
带有子类型的媒体类型。
最后,如果你不关心服务器返回什么,你可以说
Accept: */*
标题Content-Type
Content-Type
是您应该添加到 HTTP 响应消息中的标头,用于告知客户端将在响应正文中收到什么。客户端通过它来了解如何解释您发送的消息。
标题Content-Type
应该具有一种媒体类型,因此它可以像这样简单:
Content-Type: text/html
希望这就是您现在正在阅读的页面的内容类型。
等一下,我不能只使用文件扩展名吗?
您可以想象您有一些可以通过 URL 获取的数据:
http://gypsydave5/data
如果我想获取 JSON 格式的数据,我可以请求:
http://gypsydave5/data.json
但如果我想要 XML,我可以这样做:
http://gypsydave5/data.xml
好处是,如果我习惯使用文件系统,这很容易理解 - 我可以通过扩展名来识别文件的类型,因此现在我可以使用 URL 执行相同的操作。
这有什么坏处?可能最烦人的是,即使你像上面那样提供了扩展,你仍然需要提供Content-Type
header。这是因为在 Web 上,内容信息不应该编码在 URL 中;它应该存在于Content-Type
header 中。
一些流行的框架确实这样做了,4但这不是个好主意。不要这样做。
最大的问题是您将失去执行内容协商的能力。
内容协商
假设你要去一家三明治店,你的朋友叫你去买个三明治。你说,当然可以,你想吃什么?他们说,我不知道,有什么?嗯……你说,我真的不知道——这是菜单,但他们通常到了这个时候就卖完了一些馅料。
好的,你的朋友说,这就是我想要的:
“我想要金枪鱼蛋黄酱或者纽约客——都可以。如果没有,我就点一份鸡蛋沙拉。如果没有鸡蛋沙拉,请给我拿点什么都行——我饿死了。”
听起来不错。当你去三明治店的时候,你可以用朋友的三明治需求来帮他们买一个他们喜欢的三明治。
同样的事情每天都在网络上发生:客户端会向服务器发送一系列媒体类型,以确保它们能找到自己喜欢的内容。不妨看看这个Accept
从网络浏览器发送的 HTTP 请求头:5
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
浏览器需要上述媒体类型之一。但它对于自己偏好哪种媒体类型有一些偏好,并通过q
参数来表达。6个 q
参数的值介于1
和之间0
,并且参数的默认值q
(未提供时)为1
。
以下是上述媒体类型及其值的写法q
。
媒体类型 | q值 |
---|---|
text/html |
1.0 |
application/xhtml+xml |
1.0 |
application/xml |
0.9 |
*/* |
0.8 |
一系列按偏好排序的选项。如果您或我要访问服务器并向浏览器获取一些内容,浏览器会告诉我们类似以下内容:
“我确实想要
text/html
和application/xhtml+xml
。但是,如果他们没有那些,我会选择一些甜的application/xml
。如果他们没有那个,那么无论如何,只要给我任何东西就可以了。”
这确实就是您希望浏览器能够做到的——您总是希望获得一些回报。
Accept: audio/*
就像我们被送往唱片店:
“你能帮我弄张涅槃乐队的新专辑吗?7我不在乎是黑胶唱片、磁带还是CD。好吧,8 Track 的就行。只要能让我听到就行——我不光想要海报。”
这就是内容协商——我们向服务器发出的请求已经告知了我们返回内容类型的偏好。服务器会根据这些偏好,判断可以返回哪些内容类型,并返回最匹配的类型。
内容协商和测试
编写测试构建 HTML 页面的数据比编写 HTML 本身更容易。如果您构建的应用程序允许客户端选择通过每个 URL 返回的内容类型,则可以通过请求 JSON 表示而不是 HTML 表示来测试“页面”的内容。简单多了!
推出自己的媒体类型
是的,你也可以发明自己的媒体类型,这并不像你想象的那么稀奇古怪。事实上,在编写 Web API 时,这是一种非常强大的技术。媒体类型规范中提供了未注册(也永远不应该注册)子类型的空间。它们以x-
: 8开头。
application/x-myapplication-orderstatus+json
这可能是我的应用程序中订单状态的媒体类型。+json
让每个人都知道它是用 JSON 编写的,但我们严格指定内容是其中的一部分myapplication
- 它不仅仅是任何旧的 JSON。
如果你不选择利用它传递关于媒体类型的额外信息,这只是虚荣。例如,如果你想更改application/x-myapplication-orderstatus+json
媒体类型的结构,你可以添加一个版本参数:9
application/x-myapplication-orderstatus+json;v=1.1
这在描述通过不断发展的 REST API 发送和接收的内容类型时非常有用。10
最后,您可以向 IANA 注册您的媒体类型,让全世界知道如何与您的应用程序进行交互。
概括
我们了解到:
- 媒体类型的语法及其含义。
- 如何在
Content-Type
标题中使用媒体类型来声明我们要发送的内容。 - 如何在
Accept
标题中使用它们来控制我们返回的内容类型。 - 如何使用它们与服务器执行内容协商。
媒体类型非常重要——它们有助于简化网络上的数据传递。使用它们——并且运用得当——将有助于您的 Web 应用程序更易于他人使用和理解。
问答
问:“等一下,我没有
Content-Type
在发送的 HTML 中添加标题,但我的浏览器仍然知道它是 HTML - 这是怎么回事?”
答:有多种方法可以确定数据的媒体类型;请阅读有关内容嗅探的内容。
问:等一下,您指的不是 MIME 类型吗?
不,我不知道。MIME 指的是多用途互联网邮件扩展 (Multipurpose Internet Mail Extensions),它是媒体类型最早被使用的地方。但由于它们并非仅用于“互联网邮件”(发给你我的电子邮件),所以正确的名称应该是“媒体类型”。人们仍然倾向于将 MIME、媒体和内容类型混用,但现在你知道了正确答案,你可以在聚会上沾沾自喜,甚至要求加薪。
-
这是 HTML4 的——HTML5 中不需要包含该类型。但这会毁掉这个完美的例子 。↩
-
除 之外
q
,这个是指定的。↩ -
事实上,如果服务器无法提供所要求的媒体类型,它应该返回 406:不可接受的代码……但这种情况很少发生 。↩
-
在这种情况下, Firefox。↩
-
这
q
代表着品质。我可没开玩笑 。↩ -
我非常欣赏流行文化 。↩
-
或
x.
。事实上,x.
IANA 更喜欢使用 ,但x-
使用更为广泛 。↩ -
您也可以尝试
application/x-myapplication-orderstatus_1.0+json
- 这取决于您想如何解析版本信息以及您的版本控制策略是什么(版本的兼容性如何) 。↩ -
今天的封面图片由 Julius Schnorr von Carolsfeld 创作,可以轻松包含标题和自定义媒体类型
Content-Type: text/x-commandments
↩