Protocol upgrade mechanism

优质
小牛编辑
135浏览
2023-12-01

在HTTP协议提供了一个特殊机制,使已建立的连接升级到一个新的,不兼容,协议。本指南介绍了它的工作原理,并提供了其使用场景的示例。

此机制始终由客户端启动(有一个例外:服务器可能需要升级到TLS),并且服务器可能接受或拒绝切换到新协议。这使得使用常用协议(如HTTP / 1.1)启动连接成为可能,然后请求连接切换到HTTP / 2或甚至到WebSocket。

握手

协议升级总是由客户请求; 没有为服务器提供请求协议更改的机制。当客户机希望升级到新的协议,它通过发送任何类型的正常请求发送到服务器(这样做GETPOST等)。但是,该请求需要专门配置以包含升级请求。

特别是,请求需要这两个额外的头文件:

Connection: UpgradeConnection头被设置为"Upgrade"以表示的升级要求。Upgrade: protocols所述Upgrade标头指定的一个或多个以逗号分隔的协议名称,首选项的顺序。

根据请求的协议,可能需要其他头文件; 例如,WebSocket升级允许额外的头部配置关于WebSocket连接的细节,并在打开连接时提供一定程度的安全性。有关更多详细信息,请参阅升级到WebSocket连接。

服务器可拒绝升级-在这种情况下,它仅仅是忽略了"Upgrade"头并发送回一个普通的响应("200 OK"如果它可以提供所请求的资源时,30x如果它要执行重定向,状态码40x50x一个如果不能提供请求的资源) - 或接受升级。在这种情况下,它会发送一个"101 Switching Protocols"带有升级标头的指定所选协议的标头。

在发送101状态码之后,如果新协议要求它发生新的协议的最终握手,则服务器"Upgrade"按照新的协议规则发送原始请求(包括标头的请求)所请求的答案。

101状态码

101状态代码被发送作为对包括一个请求的响应"Upgrade"报头以信号通知请求的接收方愿意升级到所期望的协议之一。如果"101 Switching Protocols"状态码被返回,则标题还必须包含描述所选协议的标题ConnectionUpgrade标题。请参阅此机制的常见用法中的示例,以了解有关这种机制的更多信息。

虽然您可以使用协议升级机制将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会话之后,您需要通过添加标准请求UpgradeConnection头部来请求升级,如下所示:

Connection: Upgrade
Upgrade: websocket

WebSocket-特定的头文件

WebSocket升级过程涉及以下头文件。除了头文件UpgradeConnection头文件,其余的文件通常都是可选的,或者在浏览器和服务器互相通话时为您处理。

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失败,允许连接继续以明文方式),只需按预期方式使用UpgradeConnection标题。例如,给出原始请求:

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上编辑此页面