1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
|
---
title: 协议升级机制
slug: Web/HTTP/Protocol_upgrade_mechanism
translation_of: Web/HTTP/Protocol_upgrade_mechanism
---
<p>{{HTTPSidebar}}</p>
<p><a href="/en/HTTP" title="en/HTTP">HTTP协议</a> 提供了一种特殊的机制,这一机制允许将一个已建立的连接升级成新的、不相容的协议。这篇指南涵盖了其工作原理和使用场景。</p>
<p>通常来说这一机制总是由客户端发起的 (不过也有例外,比如说可以由服务端发起<a href="#server-initiated_upgrade_to_tls">升级到传输层安全协议(TLS)</a>), 服务端可以选择是否要升级到新协议。借助这一技术,连接可以以常用的协议启动(如HTTP/1.1),随后再升级到HTTP2甚至是WebSockets.</p>
<p>注意:HTTP/2 明确禁止使用此机制,这个机制只属于HTTP/1.1</p>
<h2 id="升级HTTP1.1的链接">升级HTTP/1.1的链接</h2>
<p>协议升级请求总是由客户端发起的;暂时没有服务端请求协议更改的机制。当客户端试图升级到一个新的协议时,可以先发送一个普通的请求({{HTTPMethod("GET")}},{{HTTPMethod("POST")}}等),不过这个请求需要进行特殊配置以包含升级请求。</p>
<p>特别这个请求需要添加两项额外的header:</p>
<dl>
<dt><code><a href="/en-US/docs/Web/HTTP/Headers/Connection">Connection: Upgrade</a></code></dt>
<dd>设置 <code>Connection</code> 头的值为 <code>"Upgrade"</code> 来指示这是一个升级请求.</dd>
<dt><code><a href="/en-US/docs/Web/HTTP/Headers/Upgrade">Upgrade: <em>protocols</em></a></code></dt>
<dd><code>Upgrade</code> 头指定一项或多项协议名,按优先级排序,以逗号分隔。</dd>
</dl>
<p>一个典型的包含升级请求的例子差不多是这样的:</p>
<pre class="notranslate">GET /index.html HTTP/1.1
Host: www.example.com
Connection: upgrade
Upgrade: example/1, foo/2</pre>
<p>根据之前的请求的协议,可能需要其他头部信息,例如:从HTTP/1.1升级到<a href="/en-US/docs/Web/API/WebSocket">WebSocket</a> 允许配置有关 WebSocket 连接的头部详细信息,以及在连接时提供一定程度的安全性。查看 <a href="#升级到websocket协议的连接">升级到WebSocket协议的连接</a> 获取更多信息。</p>
<p>如果服务器决定升级这次连接,就会返回一个 {{HTTPStatus(101, "101 Switching Protocols")}} 响应状态码,和一个要切换到的协议的头部字段Upgrade。 如果服务器没有(或者不能)升级这次连接,它会忽略客户端发送的 <code>"Upgrade</code> 头部字段,返回一个常规的响应:例如一个{{HTTPStatus(200, "200 OK")}}).</p>
<p>服务在发送 {{HTTPStatus(101)}} 状态码之后,就可以使用新的协议,并可以根据需要执行任何其他协议指定的握手。实际上,一旦这次升级完成了,连接就变成了双向管道。并且可以通过新协议完成启动升级的请求。</p>
<p>由HTTP/1.1请求建立的连接可以升级为HTTP/2协议的连接,但是反过来不可以。事实上HTTP/2已经不再支持101状态码了,也不再支持任何连接升级机制。</p>
<h2 id="升级机制的常用场合">升级机制的常用场合</h2>
<p>此处将介绍最常用到 <code>Upgrade</code> header的场合。</p>
<h3 id="升级到WebSocket协议的连接">升级到WebSocket协议的连接</h3>
<p>至今为止最经常会需要升级一个HTTP连接的场合就是使用WebSocket。当你用 <a href="/en-US/docs/Web/API/WebSocket">WebSocket API</a> 以及其他大部分实现WebSockets的库去建立WebSocket连接时,基本上都不用操心升级的过程,因为这些API已经实现了这一步。比如,用如下API打开一个WebSocket连接:</p>
<pre class="notranslate">webSocket = new WebSocket("ws://destination.server.ext", "optionalProtocol");</pre>
<p>{{domxref("WebSocket.WebSocket", "WebSocket()")}} 构造函数已经自动完成了发送初始 HTTP/1.1 请求,处理握手及升级过程。</p>
<p>你也可以用 <code>"wss://"</code> 地址格式来打开安全的WebSocket连接。</p>
<p>如果想要自己重头实现WebSocket 连接,就必须要处理握手和升级过程。在创建初始HTTP/1.1会话之后你需要发送另一个HTTP标准请求,但在headers中要带上{{HTTPHeader("Upgrade")}} and {{HTTPHeader("Connection")}},也就是:</p>
<pre class="notranslate">Connection: Upgrade
Upgrade: websocket</pre>
<h3 id="WebSocket_专有的_headers">WebSocket 专有的 headers</h3>
<p>以下headers是在WebSocket升级过程中会出现的。除了 {{HTTPHeader("Upgrade")}} 和 {{HTTPHeader("Connection")}} headers, 其他的一般浏览器和服务器都会在交互过程中处理好。</p>
<h4 id="HTTPHeaderSec-WebSocket-Extensions">{{HTTPHeader("Sec-WebSocket-Extensions")}}</h4>
<p>用于指定一个或多个请求服务器使用的协议级WebSocket扩展。允许在一个请求中使用多个Sec-WebSocket-Extension头,结果跟在一个头文件中包含了所有列出的扩展一样。</p>
<pre class="notranslate">Sec-WebSocket-Extensions: <em>extensions</em></pre>
<dl>
<dt><code>extensions</code></dt>
<dd>指需要(或支持)的扩展的逗号分隔列表。这些值来自<a href="https://www.iana.org/assignments/websocket/websocket.xml#extension-name">IANA WebSocket 扩展名注册表</a>。带参数的扩展使用分号表示。</dd>
</dl>
<p>例如:</p>
<pre class="notranslate">Sec-WebSocket-Extensions: superspeed, colormode; depth=16</pre>
<h4 id="HTTPHeaderSec-WebSocket-Key">{{HTTPHeader("Sec-WebSocket-Key")}}</h4>
<p>Provides information to the server which is needed in order to confirm that the client is entitled to request an upgrade to WebSocket. This header can be used when insecure (HTTP) clients wish to upgrade, in order to offer some degree of protection against abuse. The value of the key is computed using an algorithm defined in the WebSocket specification, so this <em>does not provide security</em>. Instead, it helps to prevent non-WebSocket clients from inadvertently, or through misuse, requesting a WebSocket connection. In essence, then, this key simply confirms that "Yes, I really mean to open a WebSocket connection."</p>
<p>This header is automatically added by clients that choose to use it; it cannot be added using the {{domxref("XMLHttpRequest.setRequestHeader()")}} method.</p>
<pre class="notranslate">Sec-WebSocket-Key: <em>key</em></pre>
<dl>
<dt><code>key</code></dt>
<dd>The key for this request to upgrade. The client adds this if it wishes to do so, and the server will include in the response a key of its own, which the client will validate before delivering the upgrade reponse to you.</dd>
</dl>
<p>The server's response's <code>Sec-WebSocket-Accept</code> header will have a value computed based upon the specified <code>key</code>.</p>
<h4 id="HTTPHeaderSec-WebSocket-Protocol">{{HTTPHeader("Sec-WebSocket-Protocol")}}</h4>
<p>The <code>Sec-WebSocket-Protocol</code> header specifies one or more WebSocket protocols that you wish to use, in order of preference. The first one that is supported by the server will be selected and returned by the server in a <code>Sec-WebSocket-Protocol</code> header included in the response. You can use this more than once in the header, as well; the result is the same as if you used a comma-delineated list of subprotocol identifiers in a single header.</p>
<pre class="notranslate">Sec-WebSocket-Protocol: <em>subprotocols</em></pre>
<dl>
<dt><code>subprotocols</code></dt>
<dd>A comma-separated list of subprotocol names, in the order of preference. The subprotocols may be selected from the <a href="https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name">IANA WebSocket Subprotocol Name Registry</a> or may be a custom name jointly understood by the client and the server.</dd>
</dl>
<h4 id="HTTPHeaderSec-WebSocket-Version">{{HTTPHeader("Sec-WebSocket-Version")}}</h4>
<h5 id="Request_header">Request header</h5>
<p>Specifies the WebSocket protocol version the client wishes to use, so the server can confirm whether or not that version is supported on its end.</p>
<pre class="notranslate">Sec-WebSocket-Version: <em>version</em></pre>
<dl>
<dt><code>version</code></dt>
<dd>The WebSocket protocol version the client wishes to use when communicating with the server. This number should be the most recent version possible listed in the <a href="https://www.iana.org/assignments/websocket/websocket.xml#version-number">IANA WebSocket Version Number Registry</a>. The most recent final version of the WebSocket protocol is version 13.</dd>
</dl>
<h5 id="Response_header">Response header</h5>
<p>If the server can't communicate using the specified version of the WebSocket protocol, it will respond with an error (such as 426 Upgrade Required) that includes in its headers a <code>Sec-WebSocket-Version</code> header with a comma-separated list of the supported protocol versions. If the server does support the requested protocol version, no <code>Sec-WebSocket-Version</code> header is included in the response.</p>
<pre class="notranslate">Sec-WebSocket-Version: <em>supportedVersions</em></pre>
<dl>
<dt><code>supportedVersions</code></dt>
<dd>A comma-delineated list of the WebSocket protocol versions supported by the server.</dd>
</dl>
<h3 id="Response-only_headers">Response-only headers</h3>
<p>The response from the server may include these.</p>
<h4 id="HTTPHeaderSec-WebSocket-Accept">{{HTTPHeader("Sec-WebSocket-Accept")}}</h4>
<p>Included in the response message from the server during the opening handshake process when the server is willing to initiate a WebSocket connection. It will appear no more than once in the repsonse headers.</p>
<pre class="notranslate">Sec-WebSocket-Accept: <em>hash</em></pre>
<dl>
<dt><code>hash</code></dt>
<dd>If a <code>Sec-WebSocket-Key</code> header was provided, the value of this header is computed by taking the value of the key, concatenating the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" to it, taking the {{interwiki("wikipedia", "SHA-1")}} hash of that concatenated string, resulting in a 20-byte value. That value is then <a href="/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding">base64</a> encoded to obtain the value of this property.</dd>
</dl>
<h3 id="Client-initiated_upgrade_to_HTTP_over_TLS">Client-initiated upgrade to HTTP over TLS</h3>
<p>You can also upgrade an HTTP/1.1 connection to TLS/1.0. The main advantages to this are that you can avoid using URL redirection from "http://" to "https://" on the server and you can easily use TLS on virtual hosts. This may, however, introduce problems with proxy servers.</p>
<p>Upgrading an HTTP connection to use {{Glossary("TLS")}} uses the {{HTTPHeader("Upgrade")}} header with the token <code>"TLS/1.0"</code>. If the switch is made successfully, the original request (which included <code>Upgrade</code>) is completed as normal, but on the TLS connection.</p>
<p>The request to TLS can be made either optionally or mandatorily.</p>
<h4 id="Optional_upgrade">Optional upgrade</h4>
<p>To upgrade to TLS optionally (that is, allowing the connection to continue in cleartext if the upgrade to TLS fails), you simply use the <code>Upgrade</code> and {{HTTPHeader("Connection")}} headers as expected. For example, given the original request:</p>
<pre class="notranslate">GET http://destination.server.ext/secretpage.html HTTP/1.1
Host: destination.server.ext
Upgrade: TLS/1.0
Connection: Upgrade</pre>
<p>If the server <em>does not</em> support TLS upgrade, or is unable to upgrade to TLS at the time, it responds with a standard HTTP/1.1 response, such as:</p>
<pre class="notranslate">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-8
Content-Length: 31374
<html>
...
</html>
</pre>
<p>If the server <em>does</em> support TLS upgrade and wishes to permit the upgrade, it responds with the <code>"101 Switching Protocols"</code> response code, like this:</p>
<pre class="notranslate">HTTP/1.1 101 Switching Protocols
Upgrade: TLS/1.0, HTTP/1.1</pre>
<p>Once the TLS handshake is complete, the original request will be responded to as normal.</p>
<h4 id="Mandatory_upgrade">Mandatory upgrade</h4>
<p>To request a mandatory upgrade to TLS—that is, to upgrade and fail the connection if the upgrade is not successful—your first request must be an {{HTTPMethod("OPTIONS")}} request, like this:</p>
<pre class="notranslate">OPTIONS * HTTP/1.1
Host: destination.server.ext
Upgrade: TLS/1.0
Connection: Upgrade</pre>
<p>If the upgrade to TLS succeeds, the server will respond with <code>"101 Switching Protocols"</code> as described in the previous section. If the upgrade fails, the HTTP/1.1 connection will fail.</p>
<h3 id="Server-initiated_upgrade_to_TLS">Server-initiated upgrade to TLS</h3>
<p>This works roughly the same way as a client-initiated upgrade; an optional upgrade is requested by adding the {{HTTPHeader("Upgrade")}} header to any message. A mandatory upgrade, though, works slightly differently, in that it requests the upgrade by replying to a message it receives with the {{HTTPStatus(426)}} status code, like this:</p>
<pre class="notranslate">HTTP/1.1 426 Upgrade Required
Upgrade: TLS/1.1, HTTP/1.1
Connection: Upgrade
<html>
... Human-readable HTML page describing why the upgrade is required
and what to do if this text is seen ...
</html></pre>
<p>If the client receiving the <code>"426 Upgrade Required"</code> response is willing and able to upgrade to TLS, it should then start the same process covered above under <a href="#client-initiated_upgrade_to_http_over_tls">Client-initiated upgrade to HTTP over TLS</a>.</p>
<h2 id="References">References</h2>
<ul>
<li><a href="/en-US/docs/Web/API/WebSocket">WebSocket API</a></li>
<li><a href="/en-US/docs/Web/HTTP">HTTP</a></li>
<li>Specifications and RFCs:
<ul>
<li>{{RFC(2616)}}</li>
<li>{{RFC(6455)}}</li>
<li>{{RFC(2817)}}</li>
<li>{{RFC(7540)}}</li>
</ul>
</li>
</ul>
|