Protocol upgrade mechanism
在HTTP协议提供了一个特殊机制,使已建立的连接升级到一个新的,不兼容,协议。本指南介绍了它的工作原理,并提供了其使用场景的示例。
此机制始终由客户端启动(有一个例外:服务器可能需要升级到TLS),并且服务器可能接受或拒绝切换到新协议。这使得使用常用协议(如HTTP / 1.1)启动连接成为可能,然后请求连接切换到HTTP / 2或甚至到WebSocket。
握手
协议升级总是由客户请求; 没有为服务器提供请求协议更改的机制。当客户机希望升级到新的协议,它通过发送任何类型的正常请求发送到服务器(这样做GET
,POST
等)。但是,该请求需要专门配置以包含升级请求。
特别是,请求需要这两个额外的头文件:
Connection: Upgrade
该Connection
头被设置为"Upgrade"
以表示的升级要求。Upgrade:
protocols
所述Upgrade
标头指定的一个或多个以逗号分隔的协议名称,首选项的顺序。
根据请求的协议,可能需要其他头文件; 例如,WebSocket升级允许额外的头部配置关于WebSocket连接的细节,并在打开连接时提供一定程度的安全性。有关更多详细信息,请参阅升级到WebSocket连接。
服务器可拒绝升级-在这种情况下,它仅仅是忽略了"Upgrade"
头并发送回一个普通的响应("200 OK"
如果它可以提供所请求的资源时,30x
如果它要执行重定向,状态码40x
或50x
一个如果不能提供请求的资源) - 或接受升级。在这种情况下,它会发送一个"101 Switching Protocols"
带有升级标头的指定所选协议的标头。
在发送101
状态码之后,如果新协议要求它发生新的协议的最终握手,则服务器"Upgrade"
按照新的协议规则发送原始请求(包括标头的请求)所请求的答案。
101状态码
的101
状态代码被发送作为对包括一个请求的响应"Upgrade"
报头以信号通知请求的接收方愿意升级到所期望的协议之一。如果"101 Switching Protocols"
状态码被返回,则标题还必须包含描述所选协议的标题Connection
和Upgrade
标题。请参阅此机制的常见用法中的示例,以了解有关这种机制的更多信息。
虽然您可以使用协议升级机制将HTTP / 1.1连接升级到HTTP / 2,但您不能以其他方式。实际上,HTTP / 2不再支持101状态码,因为HTTP / 2没有升级机制。
这种机制的共同用途
这里我们看一下Upgrade
头部最常见的用例。
升级到HTTP/2连接
由于其广泛的支持,使用HTTP / 1.1启动连接的标准过程是,然后请求升级到HTTP / 2。这样,即使服务器不支持HTTP / 2,仍然可以正常工作。但是,只能升级到不安全(明文)HTTP / 2连接。这是通过使用目标协议名称来完成的h2c
,该名称代表“HTTP / 2 Cleartext”。这也需要指定HTTP2-Settings
标题字段。
GET / HTTP/1.1Host: destination.server.ext Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings: base64EncodedSettings
这里base64EncodedSettings
是一个HTTP / 2 "SETTINGS"
帧的有效载荷,它已经被base64url编码,所有的尾随"="
(等于)字符被删除,以便将其安全地包含在这个文本标题格式中。
所述base64url格式是不一样的标准Base64编码。这与标准的Base64差不多,但不完全相同。唯一的区别:为了确保所得到的字符串是在URL和文件名使用安全的字母表中的第62和第63个字符从改变"+"
和"/"
到"-"
(负)和"_"
(下划线),分别。
如果服务器由于某种原因无法切换到HTTP / 2,那么在正常处理请求后,它将回复标准的HTTP / 1回复。因此,如果请求是获取事实上存在的网页,那么您将"HTTP/1.1 200 OK"
在网页的其余部分之后获得标准响应。如果服务器能够切换到HTTP / 2,HTTP/1.1 101 Switching Protocols"
则会发送一个“ 响应” ,如下所示:
HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: h2c[standard HTTP/2 server connection preface, etc.]
在HTTP / 1.1头部和指示头部结尾的空白行之后,服务器将立即包含服务器连接前言,从一个SETTINGS
帧开始。
升级到WebSocket连接
到目前为止,升级HTTP连接最常见的用例是使用WebSocket,它通常通过升级HTTP或HTTPS连接来实现。请记住,如果您使用WebSocket API或任何执行WebSocket的库打开新连接,则大部分或所有这些都是为您完成的。例如,打开WebSocket连接非常简单:
webSocket = new WebSocket("ws://destination.server.ext", "optionalProtocol");
该WebSocket()
构造函数创建一个初始的HTTP / 1.1连接,然后处理握手和升级过程中的所有工作。
您也可以使用"wss://"
URL方案打开安全的WebSocket连接。
如果您需要从头开始创建WebSocket连接,则必须自己处理握手过程。在创建初始HTTP / 1.1会话之后,您需要通过添加标准请求Upgrade
和Connection
头部来请求升级,如下所示:
Connection: Upgrade Upgrade: websocket
WebSocket-特定的头文件
WebSocket升级过程涉及以下头文件。除了头文件Upgrade
和Connection
头文件,其余的文件通常都是可选的,或者在浏览器和服务器互相通话时为您处理。
Sec-WebSocket-Extensions
指定要求服务器使用的一个或多个协议级WebSocket扩展。Sec-WebSocket-Extension
允许在请求中使用多个头部; 结果与您在一个这样的标题中包含所有列出的扩展名相同。
Sec-WebSocket-Extensions: extensions
extensions
用逗号分隔的请求(或同意支持)的扩展名列表。这些应该从IANA WebSocket扩展名注册表中选择。带参数的扩展使用分号描述。
例如:
Sec-WebSocket-Extensions: superspeed, colormode; depth=16
Sec-WebSocket-Key
向服务器提供信息,以确认客户端有权请求升级到WebSocket。当不安全(HTTP)客户希望升级时,可以使用此头文件,以提供一定程度的防范滥用保护。使用WebSocket规范中定义的算法计算密钥的值,因此不提供安全性。相反,它有助于防止非WebSocket客户端无意中或通过滥用请求WebSocket连接。从本质上说,这个关键只是确认“是的,我真的打算打开一个WebSocket连接。”
这个标题是由选择使用它的客户端自动添加的; 它不能使用该XMLHttpRequest.setRequestHeader()
方法添加。
Sec-WebSocket-Key: key
key
此请求升级的关键。如果客户希望这样做,则客户端会添加该服务器,并且服务器会在响应中包含自己的密钥,客户端会在向您提供升级响应之前验证该密钥。
服务器的响应Sec-WebSocket-Accept
头将有一个基于指定的值计算出来的值key
。
Sec-WebSocket-Protocol
该Sec-WebSocket-Protocol
头指定要使用,按优先顺序的一个或多个的WebSocket协议。服务器支持的第一个服务器将被选中,并由服务器在Sec-WebSocket-Protocol
响应中包含的标题中返回。您也可以在标题中多次使用它,结果与在单个头中使用逗号分隔的子协议标识符列表相同。
Sec-WebSocket-Protocol: subprotocols
subprotocols
按照优先顺序,按逗号分隔的子协议名称列表。子协议可以从IANA WebSocket子协议名称注册中选择,也可以是客户和服务器共同理解的定制名称。
Sec-WebSocket-Version
请求标题
指定客户端希望使用的WebSocket协议版本,以便服务器可以确认该版本是否受支持。
Sec-WebSocket-Version: version
version
客户端希望在与服务器通信时使用的WebSocket协议版本。此号码应为IANA WebSocket版本号注册表中列出的最新版本。WebSocket协议的最新最终版本是版本13。
响应标题
如果服务器无法使用指定版本的WebSocket协议进行通信,它将响应一个错误(如需要升级426),在其标题中包含一个包含Sec-WebSocket-Version
受支持协议版本的逗号分隔列表的标题。如果服务器确实支持所请求的协议版本,Sec-WebSocket-Version
则响应中不会包含标题。
Sec-WebSocket-Version: supportedVersions
supportedVersions
服务器支持的WebSocket协议版本的逗号分隔列表。
仅响应头文件
服务器的响应可能包括这些。
Sec-WebSocket-Accept
在服务器愿意启动WebSocket连接时,在打开握手过程期间包含在服务器的响应消息中。它在repsonse头文件中只会出现一次。
Sec-WebSocket-Accept: hash
hash
如果Sec-WebSocket-Key
提供了头文件,则通过获取该密钥的值来计算此头文件的值,并将字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”连接到该字符串,并取该连接字符串的SHA-1散列值,结果在一个20字节的值。然后该值被base64编码以获得该属性的值。
客户端的启动通过TLS升级到HTTP
您也可以将HTTP / 1.1连接升级到TLS / 1.0。这样做的主要优点是可以避免在服务器上使用从“http://”到“https://”的URL重定向,并且可以在虚拟主机上轻松使用TLS。但是,这可能会导致代理服务器出现问题。
升级HTTP连接以使用TLS将Upgrade
标记与标记一起使用"TLS/1.0"
。如果交换机成功完成,原始请求(包括其中Upgrade
)将按照正常完成,但在TLS连接上完成。
对TLS的请求可以选择性地或强制性地进行。
可选升级
要升级到TLS(也就是说,如果升级到TLS失败,允许连接继续以明文方式),只需按预期方式使用Upgrade
和Connection
标题。例如,给出原始请求:
GET http://destination.server.ext/secretpage.html HTTP/1.1Host: destination.server.ext Upgrade: TLS/1.0Connection: Upgrade
如果服务器不支持TLS升级,或者无法在当时升级到TLS,则它会使用标准的HTTP / 1.1响应进行响应,例如:
HTTP/1.1 200 OK Date: Thu, 17 Aug 2017 21:07:44 GMT Server: Apache Last-Modified: Thu, 17 Aug 2017 08:30:15 GMT Content-Type: text/html; charset=utf-8Content-Length: 31374<html> ...</html>
如果服务器确实支持TLS升级并希望允许升级,它将使用"101 Switching Protocols"
响应代码进行响应,如下所示:
HTTP/1.1 101 Switching Protocols Upgrade: TLS/1.0, HTTP/1.1
一旦TLS握手完成,原始请求将被正常响应。
强制升级
要请求强制升级到TLS(即升级失败并在升级失败时连接失败),您的第一个请求必须是一个OPTIONS
请求,如下所示:
OPTIONS * HTTP/1.1Host: destination.server.ext Upgrade: TLS/1.0Connection: Upgrade
如果升级到TLS成功,服务器将"101 Switching Protocols"
按照上一节所述进行响应。如果升级失败,HTTP / 1.1连接将失败。
服务器的启动升级到TLS
这与客户端启动的升级大致相同,通过将Upgrade
头添加到任何消息来请求可选的升级。然而,强制升级的工作方式稍有不同,因为它会通过回复收到的带有426
状态码的消息来请求升级,如下所示:
HTTP/1.1 426 Upgrade Required Upgrade: TLS/1.1, HTTP/1.1Connection: Upgrade<html>... Human-readable HTML page describing why the upgrade is required and what to do if this text is seen ...</html>
如果接收到"426 Upgrade Required"
响应的客户端愿意且能够升级到TLS,则应该启动上面在客户端启动的升级到TLS的过程中所述的相同过程。
参考
- WebSocket API
- HTTP
- 规格和RFC:
- RFC 2616
- RFC 6455
- RFC 2817
- RFC 7540
在MDN上编辑此页面