OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of dart.io; | 5 part of dart.io; |
6 | 6 |
7 /** | 7 /** |
8 * WebSocket status codes used when closing a WebSocket connection. | 8 * WebSocket status codes used when closing a WebSocket connection. |
9 */ | 9 */ |
10 abstract class WebSocketStatus { | 10 abstract class WebSocketStatus { |
11 static const int NORMAL_CLOSURE = 1000; | 11 static const int NORMAL_CLOSURE = 1000; |
12 static const int GOING_AWAY = 1001; | 12 static const int GOING_AWAY = 1001; |
13 static const int PROTOCOL_ERROR = 1002; | 13 static const int PROTOCOL_ERROR = 1002; |
14 static const int UNSUPPORTED_DATA = 1003; | 14 static const int UNSUPPORTED_DATA = 1003; |
15 static const int RESERVED_1004 = 1004; | 15 static const int RESERVED_1004 = 1004; |
16 static const int NO_STATUS_RECEIVED = 1005; | 16 static const int NO_STATUS_RECEIVED = 1005; |
17 static const int ABNORMAL_CLOSURE = 1006; | 17 static const int ABNORMAL_CLOSURE = 1006; |
18 static const int INVALID_FRAME_PAYLOAD_DATA = 1007; | 18 static const int INVALID_FRAME_PAYLOAD_DATA = 1007; |
19 static const int POLICY_VIOLATION = 1008; | 19 static const int POLICY_VIOLATION = 1008; |
20 static const int MESSAGE_TOO_BIG = 1009; | 20 static const int MESSAGE_TOO_BIG = 1009; |
21 static const int MISSING_MANDATORY_EXTENSION = 1010; | 21 static const int MISSING_MANDATORY_EXTENSION = 1010; |
22 static const int INTERNAL_SERVER_ERROR = 1011; | 22 static const int INTERNAL_SERVER_ERROR = 1011; |
23 static const int RESERVED_1015 = 1015; | 23 static const int RESERVED_1015 = 1015; |
24 } | 24 } |
25 | 25 |
26 /** | 26 /** |
| 27 * The [CompressionOptions] class allows you to control |
| 28 * the options of WebSocket compression. |
| 29 */ |
| 30 class CompressionOptions { |
| 31 /** |
| 32 * Default WebSocket Compression options. |
| 33 * Compression will be enabled with the following options: |
| 34 * clientNoContextTakeover: false |
| 35 * serverNoContextTakeover: false |
| 36 * clientMaxWindowBits: 15 |
| 37 * serverMaxWindowBits: 15 |
| 38 */ |
| 39 static const CompressionOptions DEFAULT = const CompressionOptions(); |
| 40 |
| 41 /** |
| 42 * Disables WebSocket Compression. |
| 43 */ |
| 44 static const CompressionOptions OFF = |
| 45 const CompressionOptions(enabled: false); |
| 46 |
| 47 /** |
| 48 * Control whether the client will reuse it's compression instances. |
| 49 */ |
| 50 final bool clientNoContextTakeover; |
| 51 |
| 52 /** |
| 53 * Control whether the server will reuse it's compression instances. |
| 54 */ |
| 55 final bool serverNoContextTakeover; |
| 56 |
| 57 /** |
| 58 * Sets the Max Window Bits for the Client. |
| 59 */ |
| 60 final int clientMaxWindowBits; |
| 61 |
| 62 /** |
| 63 * Sets the Max Window Bits for the Server. |
| 64 */ |
| 65 final int serverMaxWindowBits; |
| 66 |
| 67 /** |
| 68 * Enables or disables WebSocket compression. |
| 69 */ |
| 70 final bool enabled; |
| 71 |
| 72 const CompressionOptions( |
| 73 {this.clientNoContextTakeover: false, |
| 74 this.serverNoContextTakeover: false, |
| 75 this.clientMaxWindowBits: _WebSocketImpl.DEFAULT_WINDOW_BITS, |
| 76 this.serverMaxWindowBits: _WebSocketImpl.DEFAULT_WINDOW_BITS, |
| 77 this.enabled: true}); |
| 78 |
| 79 /// Parses list of requested server headers to return server compression |
| 80 /// response headers. Uses [serverMaxWindowBits] value if set, otherwise will |
| 81 /// attempt to use value from headers. Defaults to |
| 82 /// [WebSocket.DEFAULT_WINDOW_BITS] |
| 83 List _createServerResponseHeader(HeaderValue requested) { |
| 84 var info = new List(2); |
| 85 |
| 86 int mwb; |
| 87 var part = requested.parameters[_serverMaxWindowBits]; |
| 88 if (part != null) { |
| 89 if (part.length >= 2 && part.startsWith('0')) { |
| 90 throw new ArgumentError("Illegal 0 padding on value."); |
| 91 } else { |
| 92 mwb = serverMaxWindowBits == null |
| 93 ? int.parse(part, |
| 94 onError: (source) => _WebSocketImpl.DEFAULT_WINDOW_BITS) |
| 95 : serverMaxWindowBits; |
| 96 info[0] = "; server_max_window_bits=${mwb}"; |
| 97 info[1] = mwb; |
| 98 } |
| 99 } else { |
| 100 info[1] = _WebSocketImpl.DEFAULT_WINDOW_BITS; |
| 101 } |
| 102 return info; |
| 103 } |
| 104 |
| 105 /// Returns default values for client compression request headers. |
| 106 List _createClientRequestHeader(HeaderValue requested) { |
| 107 var info = new List(2); |
| 108 |
| 109 info[1] = _WebSocketImpl.DEFAULT_WINDOW_BITS; |
| 110 if (requested != null && |
| 111 requested.parameters[_clientMaxWindowBits] != null) { |
| 112 info[0] = "; client_max_window_bits=${info[1]}"; |
| 113 } else { |
| 114 info[0] = "; client_max_window_bits"; |
| 115 } |
| 116 |
| 117 return info; |
| 118 } |
| 119 |
| 120 /// Create a Compression Header. If [requested] is null or contains |
| 121 /// client request headers, returns Client compression request headers. |
| 122 /// If [requested] contains server response headers this method returns |
| 123 /// a Server compression response header. |
| 124 List _createHeader([HeaderValue requested]) { |
| 125 if (!enabled) { |
| 126 return ["", 0]; |
| 127 } |
| 128 |
| 129 var info = new List(2); |
| 130 var header = _WebSocketImpl.PER_MESSAGE_DEFLATE; |
| 131 |
| 132 if (clientNoContextTakeover && |
| 133 (requested != null && |
| 134 requested.parameters.containsKey(_clientNoContextTakeover))) { |
| 135 header += "; client_no_context_takeover"; |
| 136 } |
| 137 |
| 138 if (serverNoContextTakeover && |
| 139 (requested != null && |
| 140 requested.parameters.containsKey(_serverNoContextTakeover))) { |
| 141 header += "; server_no_context_takeover"; |
| 142 } |
| 143 |
| 144 if (requested == null || |
| 145 requested.parameters.containsKey(_clientMaxWindowBits)) { |
| 146 var clientList = _createClientRequestHeader(requested); |
| 147 header += clientList[0]; |
| 148 info[1] = clientList[1]; |
| 149 } else { |
| 150 var headerList = _createServerResponseHeader(requested); |
| 151 header += headerList[0]; |
| 152 info[1] = headerList[1]; |
| 153 } |
| 154 |
| 155 info[0] = header; |
| 156 |
| 157 return info; |
| 158 } |
| 159 } |
| 160 |
| 161 /** |
27 * The [WebSocketTransformer] provides the ability to upgrade a | 162 * The [WebSocketTransformer] provides the ability to upgrade a |
28 * [HttpRequest] to a [WebSocket] connection. It supports both | 163 * [HttpRequest] to a [WebSocket] connection. It supports both |
29 * upgrading a single [HttpRequest] and upgrading a stream of | 164 * upgrading a single [HttpRequest] and upgrading a stream of |
30 * [HttpRequest]s. | 165 * [HttpRequest]s. |
31 * | 166 * |
32 * To upgrade a single [HttpRequest] use the static [upgrade] method. | 167 * To upgrade a single [HttpRequest] use the static [upgrade] method. |
33 * | 168 * |
34 * HttpServer server; | 169 * HttpServer server; |
35 * server.listen((request) { | 170 * server.listen((request) { |
36 * if (...) { | 171 * if (...) { |
37 * WebSocketTransformer.upgrade(request).then((websocket) { | 172 * WebSocketTransformer.upgrade(request).then((websocket) { |
38 * ... | 173 * ... |
39 * }); | 174 * }); |
40 * } else { | 175 * } else { |
41 * // Do normal HTTP request processing. | 176 * // Do normal HTTP request processing. |
42 * } | 177 * } |
43 * }); | 178 * }); |
44 * | 179 * |
45 * To transform a stream of [HttpRequest] events as it implements a | 180 * To transform a stream of [HttpRequest] events as it implements a |
46 * stream transformer that transforms a stream of HttpRequest into a | 181 * stream transformer that transforms a stream of HttpRequest into a |
47 * stream of WebSockets by upgrading each HttpRequest from the HTTP or | 182 * stream of WebSockets by upgrading each HttpRequest from the HTTP or |
48 * HTTPS server, to the WebSocket protocol. | 183 * HTTPS server, to the WebSocket protocol. |
49 * | 184 * |
50 * server.transform(new WebSocketTransformer()).listen((webSocket) => ...); | 185 * server.transform(new WebSocketTransformer()).listen((webSocket) => ...); |
51 * | 186 * |
52 * This transformer strives to implement WebSockets as specified by RFC6455. | 187 * This transformer strives to implement WebSockets as specified by RFC6455. |
53 */ | 188 */ |
54 abstract class WebSocketTransformer | 189 abstract class WebSocketTransformer |
55 implements StreamTransformer<HttpRequest, WebSocket> { | 190 implements StreamTransformer<HttpRequest, WebSocket> { |
56 | |
57 /** | 191 /** |
58 * Create a new [WebSocketTransformer]. | 192 * Create a new [WebSocketTransformer]. |
59 * | 193 * |
60 * If [protocolSelector] is provided, [protocolSelector] will be called to | 194 * If [protocolSelector] is provided, [protocolSelector] will be called to |
61 * select what protocol to use, if any were provided by the client. | 195 * select what protocol to use, if any were provided by the client. |
62 * [protocolSelector] is should return either a [String] or a [Future] | 196 * [protocolSelector] is should return either a [String] or a [Future] |
63 * completing with a [String]. The [String] must exist in the list of | 197 * completing with a [String]. The [String] must exist in the list of |
64 * protocols. | 198 * protocols. |
| 199 * |
| 200 * If [compression] is provided, the [WebSocket] created will be configured |
| 201 * to negotiate with the specified [CompressionOptions]. If none is specified |
| 202 * then the [WebSocket] will be created with the default [CompressionOptions]. |
65 */ | 203 */ |
66 factory WebSocketTransformer({protocolSelector(List<String> protocols)}) | 204 factory WebSocketTransformer( |
67 => new _WebSocketTransformerImpl(protocolSelector); | 205 {protocolSelector(List<String> protocols), |
| 206 CompressionOptions compression: CompressionOptions.DEFAULT}) => |
| 207 new _WebSocketTransformerImpl(protocolSelector, compression); |
68 | 208 |
69 /** | 209 /** |
70 * Upgrades a [HttpRequest] to a [WebSocket] connection. If the | 210 * Upgrades a [HttpRequest] to a [WebSocket] connection. If the |
71 * request is not a valid WebSocket upgrade request an HTTP response | 211 * request is not a valid WebSocket upgrade request an HTTP response |
72 * with status code 500 will be returned. Otherwise the returned | 212 * with status code 500 will be returned. Otherwise the returned |
73 * future will complete with the [WebSocket] when the upgrade pocess | 213 * future will complete with the [WebSocket] when the upgrade pocess |
74 * is complete. | 214 * is complete. |
75 * | 215 * |
76 * If [protocolSelector] is provided, [protocolSelector] will be called to | 216 * If [protocolSelector] is provided, [protocolSelector] will be called to |
77 * select what protocol to use, if any were provided by the client. | 217 * select what protocol to use, if any were provided by the client. |
78 * [protocolSelector] is should return either a [String] or a [Future] | 218 * [protocolSelector] is should return either a [String] or a [Future] |
79 * completing with a [String]. The [String] must exist in the list of | 219 * completing with a [String]. The [String] must exist in the list of |
80 * protocols. | 220 * protocols. |
| 221 * |
| 222 * If [compression] is provided, the [WebSocket] created will be configured |
| 223 * to negotiate with the specified [CompressionOptions]. If none is specified |
| 224 * then the [WebSocket] will be created with the default [CompressionOptions]. |
81 */ | 225 */ |
82 static Future<WebSocket> upgrade(HttpRequest request, | 226 static Future<WebSocket> upgrade(HttpRequest request, |
83 {protocolSelector(List<String> protocols)}) { | 227 {protocolSelector(List<String> protocols), |
84 return _WebSocketTransformerImpl._upgrade(request, protocolSelector); | 228 CompressionOptions compression: CompressionOptions.DEFAULT}) { |
| 229 return _WebSocketTransformerImpl._upgrade( |
| 230 request, protocolSelector, compression); |
85 } | 231 } |
86 | 232 |
87 /** | 233 /** |
88 * Checks whether the request is a valid WebSocket upgrade request. | 234 * Checks whether the request is a valid WebSocket upgrade request. |
89 */ | 235 */ |
90 static bool isUpgradeRequest(HttpRequest request) { | 236 static bool isUpgradeRequest(HttpRequest request) { |
91 return _WebSocketTransformerImpl._isUpgradeRequest(request); | 237 return _WebSocketTransformerImpl._isUpgradeRequest(request); |
92 } | 238 } |
93 } | 239 } |
94 | 240 |
95 | |
96 /** | 241 /** |
97 * A two-way HTTP communication object for client or server applications. | 242 * A two-way HTTP communication object for client or server applications. |
98 * | 243 * |
99 * The stream exposes the messages received. A text message will be of type | 244 * The stream exposes the messages received. A text message will be of type |
100 * [:String:] and a binary message will be of type [:List<int>:]. | 245 * [:String:] and a binary message will be of type [:List<int>:]. |
101 */ | 246 */ |
102 abstract class WebSocket implements Stream, StreamSink { | 247 abstract class WebSocket implements Stream, StreamSink { |
103 /** | 248 /** |
104 * Possible states of the connection. | 249 * Possible states of the connection. |
105 */ | 250 */ |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
145 * - `sec-websocket-protocol` | 290 * - `sec-websocket-protocol` |
146 * - `sec-websocket-version` | 291 * - `sec-websocket-version` |
147 * - `upgrade` | 292 * - `upgrade` |
148 * | 293 * |
149 * If any of these are passed in the `headers` map they will be ignored. | 294 * If any of these are passed in the `headers` map they will be ignored. |
150 * | 295 * |
151 * If the `url` contains user information this will be passed as basic | 296 * If the `url` contains user information this will be passed as basic |
152 * authentication when setting up the connection. | 297 * authentication when setting up the connection. |
153 */ | 298 */ |
154 static Future<WebSocket> connect(String url, | 299 static Future<WebSocket> connect(String url, |
155 {Iterable<String> protocols, | 300 {Iterable<String> protocols, |
156 Map<String, dynamic> headers}) => | 301 Map<String, dynamic> headers, |
157 _WebSocketImpl.connect(url, protocols, headers); | 302 CompressionOptions compression: CompressionOptions.DEFAULT}) => |
| 303 _WebSocketImpl.connect(url, protocols, headers, compression: compression); |
158 | 304 |
159 @Deprecated('This constructor will be removed in Dart 2.0. Use `implements`' | 305 @Deprecated('This constructor will be removed in Dart 2.0. Use `implements`' |
160 ' instead of `extends` if implementing this abstract class.') | 306 ' instead of `extends` if implementing this abstract class.') |
161 WebSocket(); | 307 WebSocket(); |
162 | 308 |
163 /** | 309 /** |
164 * Creates a WebSocket from an already-upgraded socket. | 310 * Creates a WebSocket from an already-upgraded socket. |
165 * | 311 * |
166 * The initial WebSocket handshake must have occurred prior to this call. A | 312 * The initial WebSocket handshake must have occurred prior to this call. A |
167 * WebSocket client can automatically perform the handshake using | 313 * WebSocket client can automatically perform the handshake using |
168 * [WebSocket.connect], while a server can do so using | 314 * [WebSocket.connect], while a server can do so using |
169 * [WebSocketTransformer.upgrade]. To manually upgrade an [HttpRequest], | 315 * [WebSocketTransformer.upgrade]. To manually upgrade an [HttpRequest], |
170 * [HttpRequest.detachSocket] may be called. | 316 * [HttpRequest.detachSocket] may be called. |
171 * | 317 * |
172 * [protocol] should be the protocol negotiated by this handshake, if any. | 318 * [protocol] should be the protocol negotiated by this handshake, if any. |
173 * | 319 * |
174 * [serverSide] must be passed explicitly. If it's `false`, the WebSocket will | 320 * [serverSide] must be passed explicitly. If it's `false`, the WebSocket will |
175 * act as the client and mask the messages it sends. If it's `true`, it will | 321 * act as the client and mask the messages it sends. If it's `true`, it will |
176 * act as the server and will not mask its messages. | 322 * act as the server and will not mask its messages. |
| 323 * |
| 324 * If [compression] is provided, the [WebSocket] created will be configured |
| 325 * to negotiate with the specified [CompressionOptions]. If none is specified |
| 326 * then the [WebSocket] will be created with the default [CompressionOptions]. |
177 */ | 327 */ |
178 factory WebSocket.fromUpgradedSocket(Socket socket, {String protocol, | 328 factory WebSocket.fromUpgradedSocket(Socket socket, |
179 bool serverSide}) { | 329 {String protocol, |
| 330 bool serverSide, |
| 331 CompressionOptions compression: CompressionOptions.DEFAULT}) { |
180 if (serverSide == null) { | 332 if (serverSide == null) { |
181 throw new ArgumentError("The serverSide argument must be passed " | 333 throw new ArgumentError("The serverSide argument must be passed " |
182 "explicitly to WebSocket.fromUpgradedSocket."); | 334 "explicitly to WebSocket.fromUpgradedSocket."); |
183 } | 335 } |
184 return new _WebSocketImpl._fromSocket(socket, protocol, serverSide); | 336 return new _WebSocketImpl._fromSocket( |
| 337 socket, protocol, compression, serverSide); |
185 } | 338 } |
186 | 339 |
187 /** | 340 /** |
188 * Returns the current state of the connection. | 341 * Returns the current state of the connection. |
189 */ | 342 */ |
190 int get readyState; | 343 int get readyState; |
191 | 344 |
192 /** | 345 /** |
193 * The extensions property is initially the empty string. After the | 346 * The extensions property is initially the empty string. After the |
194 * WebSocket connection is established this string reflects the | 347 * WebSocket connection is established this string reflects the |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
231 void add(data); | 384 void add(data); |
232 | 385 |
233 /** | 386 /** |
234 * Sends data from a stream on WebSocket connection. Each data event from | 387 * Sends data from a stream on WebSocket connection. Each data event from |
235 * [stream] will be send as a single WebSocket frame. The data from [stream] | 388 * [stream] will be send as a single WebSocket frame. The data from [stream] |
236 * must be either [:String:]s, or [:List<int>:]s holding bytes. | 389 * must be either [:String:]s, or [:List<int>:]s holding bytes. |
237 */ | 390 */ |
238 Future addStream(Stream stream); | 391 Future addStream(Stream stream); |
239 } | 392 } |
240 | 393 |
241 | |
242 class WebSocketException implements IOException { | 394 class WebSocketException implements IOException { |
243 final String message; | 395 final String message; |
| 396 |
244 const WebSocketException([this.message = ""]); | 397 const WebSocketException([this.message = ""]); |
| 398 |
245 String toString() => "WebSocketException: $message"; | 399 String toString() => "WebSocketException: $message"; |
246 } | 400 } |
OLD | NEW |