OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 part of dart.io; |
| 6 |
| 7 /** |
| 8 * WebSocket status codes used when closing a WebSocket connection. |
| 9 */ |
| 10 abstract class WebSocketStatus { |
| 11 static const int NORMAL_CLOSURE = 1000; |
| 12 static const int GOING_AWAY = 1001; |
| 13 static const int PROTOCOL_ERROR = 1002; |
| 14 static const int UNSUPPORTED_DATA = 1003; |
| 15 static const int RESERVED_1004 = 1004; |
| 16 static const int NO_STATUS_RECEIVED = 1005; |
| 17 static const int ABNORMAL_CLOSURE = 1006; |
| 18 static const int INVALID_FRAME_PAYLOAD_DATA = 1007; |
| 19 static const int POLICY_VIOLATION = 1008; |
| 20 static const int MESSAGE_TOO_BIG = 1009; |
| 21 static const int MISSING_MANDATORY_EXTENSION = 1010; |
| 22 static const int INTERNAL_SERVER_ERROR = 1011; |
| 23 static const int RESERVED_1015 = 1015; |
| 24 } |
| 25 |
| 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, |
| 76 this.serverMaxWindowBits, |
| 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]. Returns a [_CompressionMaxWindowBits] |
| 83 /// object which contains the response headers and negotiated max window bits. |
| 84 _CompressionMaxWindowBits _createServerResponseHeader(HeaderValue requested) { |
| 85 var info = new _CompressionMaxWindowBits(); |
| 86 |
| 87 int mwb; |
| 88 String part; |
| 89 if (requested?.parameters != null) { |
| 90 part = requested.parameters[_serverMaxWindowBits]; |
| 91 } |
| 92 if (part != null) { |
| 93 if (part.length >= 2 && part.startsWith('0')) { |
| 94 throw new ArgumentError("Illegal 0 padding on value."); |
| 95 } else { |
| 96 mwb = serverMaxWindowBits == null |
| 97 ? int.parse(part, |
| 98 onError: (source) => _WebSocketImpl.DEFAULT_WINDOW_BITS) |
| 99 : serverMaxWindowBits; |
| 100 info.headerValue = "; server_max_window_bits=${mwb}"; |
| 101 info.maxWindowBits = mwb; |
| 102 } |
| 103 } else { |
| 104 info.headerValue = ""; |
| 105 info.maxWindowBits = _WebSocketImpl.DEFAULT_WINDOW_BITS; |
| 106 } |
| 107 return info; |
| 108 } |
| 109 |
| 110 /// Returns default values for client compression request headers. |
| 111 String _createClientRequestHeader(HeaderValue requested, int size) { |
| 112 var info = ""; |
| 113 |
| 114 // If responding to a valid request, specify size |
| 115 if (requested != null) { |
| 116 info = "; client_max_window_bits=$size"; |
| 117 } else { |
| 118 // Client request. Specify default |
| 119 if (clientMaxWindowBits == null) { |
| 120 info = "; client_max_window_bits"; |
| 121 } else { |
| 122 info = "; client_max_window_bits=$clientMaxWindowBits"; |
| 123 } |
| 124 if (serverMaxWindowBits != null) { |
| 125 info += "; server_max_window_bits=$serverMaxWindowBits"; |
| 126 } |
| 127 } |
| 128 |
| 129 return info; |
| 130 } |
| 131 |
| 132 /// Create a Compression Header. If [requested] is null or contains |
| 133 /// client request headers, returns Client compression request headers with |
| 134 /// default settings for `client_max_window_bits` header value. |
| 135 /// If [requested] contains server response headers this method returns |
| 136 /// a Server compression response header negotiating the max window bits |
| 137 /// for both client and server as requested server_max_window_bits value. |
| 138 /// This method returns a [_CompressionMaxWindowBits] object with the |
| 139 /// response headers and negotiated maxWindowBits value. |
| 140 _CompressionMaxWindowBits _createHeader([HeaderValue requested]) { |
| 141 var info = new _CompressionMaxWindowBits("", 0); |
| 142 if (!enabled) { |
| 143 return info; |
| 144 } |
| 145 |
| 146 info.headerValue = _WebSocketImpl.PER_MESSAGE_DEFLATE; |
| 147 |
| 148 if (clientNoContextTakeover && |
| 149 (requested == null || (requested != null && |
| 150 requested.parameters.containsKey(_clientNoContextTakeover)))) { |
| 151 info.headerValue += "; client_no_context_takeover"; |
| 152 } |
| 153 |
| 154 if (serverNoContextTakeover && |
| 155 (requested == null || (requested != null && |
| 156 requested.parameters.containsKey(_serverNoContextTakeover)))) { |
| 157 info.headerValue += "; server_no_context_takeover"; |
| 158 } |
| 159 |
| 160 var headerList = _createServerResponseHeader(requested); |
| 161 info.headerValue += headerList.headerValue; |
| 162 info.maxWindowBits = headerList.maxWindowBits; |
| 163 |
| 164 info.headerValue += |
| 165 _createClientRequestHeader(requested, info.maxWindowBits); |
| 166 |
| 167 return info; |
| 168 } |
| 169 } |
| 170 |
| 171 /** |
| 172 * The [WebSocketTransformer] provides the ability to upgrade a |
| 173 * [HttpRequest] to a [WebSocket] connection. It supports both |
| 174 * upgrading a single [HttpRequest] and upgrading a stream of |
| 175 * [HttpRequest]s. |
| 176 * |
| 177 * To upgrade a single [HttpRequest] use the static [upgrade] method. |
| 178 * |
| 179 * HttpServer server; |
| 180 * server.listen((request) { |
| 181 * if (...) { |
| 182 * WebSocketTransformer.upgrade(request).then((websocket) { |
| 183 * ... |
| 184 * }); |
| 185 * } else { |
| 186 * // Do normal HTTP request processing. |
| 187 * } |
| 188 * }); |
| 189 * |
| 190 * To transform a stream of [HttpRequest] events as it implements a |
| 191 * stream transformer that transforms a stream of HttpRequest into a |
| 192 * stream of WebSockets by upgrading each HttpRequest from the HTTP or |
| 193 * HTTPS server, to the WebSocket protocol. |
| 194 * |
| 195 * server.transform(new WebSocketTransformer()).listen((webSocket) => ...); |
| 196 * |
| 197 * This transformer strives to implement WebSockets as specified by RFC6455. |
| 198 */ |
| 199 abstract class WebSocketTransformer |
| 200 implements StreamTransformer<HttpRequest, WebSocket> { |
| 201 /** |
| 202 * Create a new [WebSocketTransformer]. |
| 203 * |
| 204 * If [protocolSelector] is provided, [protocolSelector] will be called to |
| 205 * select what protocol to use, if any were provided by the client. |
| 206 * [protocolSelector] is should return either a [String] or a [Future] |
| 207 * completing with a [String]. The [String] must exist in the list of |
| 208 * protocols. |
| 209 * |
| 210 * If [compression] is provided, the [WebSocket] created will be configured |
| 211 * to negotiate with the specified [CompressionOptions]. If none is specified |
| 212 * then the [WebSocket] will be created with the default [CompressionOptions]. |
| 213 */ |
| 214 factory WebSocketTransformer( |
| 215 {protocolSelector(List<String> protocols), |
| 216 CompressionOptions compression: CompressionOptions.DEFAULT}) => |
| 217 new _WebSocketTransformerImpl(protocolSelector, compression); |
| 218 |
| 219 /** |
| 220 * Upgrades a [HttpRequest] to a [WebSocket] connection. If the |
| 221 * request is not a valid WebSocket upgrade request an HTTP response |
| 222 * with status code 500 will be returned. Otherwise the returned |
| 223 * future will complete with the [WebSocket] when the upgrade pocess |
| 224 * is complete. |
| 225 * |
| 226 * If [protocolSelector] is provided, [protocolSelector] will be called to |
| 227 * select what protocol to use, if any were provided by the client. |
| 228 * [protocolSelector] is should return either a [String] or a [Future] |
| 229 * completing with a [String]. The [String] must exist in the list of |
| 230 * protocols. |
| 231 * |
| 232 * If [compression] is provided, the [WebSocket] created will be configured |
| 233 * to negotiate with the specified [CompressionOptions]. If none is specified |
| 234 * then the [WebSocket] will be created with the default [CompressionOptions]. |
| 235 */ |
| 236 static Future<WebSocket> upgrade(HttpRequest request, |
| 237 {protocolSelector(List<String> protocols), |
| 238 CompressionOptions compression: CompressionOptions.DEFAULT}) { |
| 239 return _WebSocketTransformerImpl._upgrade( |
| 240 request, protocolSelector, compression); |
| 241 } |
| 242 |
| 243 /** |
| 244 * Checks whether the request is a valid WebSocket upgrade request. |
| 245 */ |
| 246 static bool isUpgradeRequest(HttpRequest request) { |
| 247 return _WebSocketTransformerImpl._isUpgradeRequest(request); |
| 248 } |
| 249 } |
| 250 |
| 251 /** |
| 252 * A two-way HTTP communication object for client or server applications. |
| 253 * |
| 254 * The stream exposes the messages received. A text message will be of type |
| 255 * [:String:] and a binary message will be of type [:List<int>:]. |
| 256 */ |
| 257 abstract class WebSocket implements Stream, StreamSink { |
| 258 /** |
| 259 * Possible states of the connection. |
| 260 */ |
| 261 static const int CONNECTING = 0; |
| 262 static const int OPEN = 1; |
| 263 static const int CLOSING = 2; |
| 264 static const int CLOSED = 3; |
| 265 |
| 266 /** |
| 267 * Set and get the interval for sending ping signals. If a ping message is not |
| 268 * answered by a pong message from the peer, the `WebSocket` is assumed |
| 269 * disconnected and the connection is closed with a |
| 270 * [WebSocketStatus.GOING_AWAY] close code. When a ping signal is sent, the |
| 271 * pong message must be received within [pingInterval]. |
| 272 * |
| 273 * There are never two outstanding pings at any given time, and the next ping |
| 274 * timer starts when the pong is received. |
| 275 * |
| 276 * Set the [pingInterval] to `null` to disable sending ping messages. |
| 277 * |
| 278 * The default value is `null`. |
| 279 */ |
| 280 Duration pingInterval; |
| 281 |
| 282 /** |
| 283 * Create a new WebSocket connection. The URL supplied in [url] |
| 284 * must use the scheme `ws` or `wss`. |
| 285 * |
| 286 * The [protocols] argument is specifying the subprotocols the |
| 287 * client is willing to speak. |
| 288 * |
| 289 * The [headers] argument is specifying additional HTTP headers for |
| 290 * setting up the connection. This would typically be the `Origin` |
| 291 * header and potentially cookies. The keys of the map are the header |
| 292 * fields and the values are either String or List<String>. |
| 293 * |
| 294 * If [headers] is provided, there are a number of headers |
| 295 * which are controlled by the WebSocket connection process. These |
| 296 * headers are: |
| 297 * |
| 298 * - `connection` |
| 299 * - `sec-websocket-key` |
| 300 * - `sec-websocket-protocol` |
| 301 * - `sec-websocket-version` |
| 302 * - `upgrade` |
| 303 * |
| 304 * If any of these are passed in the `headers` map they will be ignored. |
| 305 * |
| 306 * If the `url` contains user information this will be passed as basic |
| 307 * authentication when setting up the connection. |
| 308 */ |
| 309 static Future<WebSocket> connect(String url, |
| 310 {Iterable<String> protocols, |
| 311 Map<String, dynamic> headers, |
| 312 CompressionOptions compression: CompressionOptions.DEFAULT}) => |
| 313 _WebSocketImpl.connect(url, protocols, headers, compression: compression); |
| 314 |
| 315 @Deprecated('This constructor will be removed in Dart 2.0. Use `implements`' |
| 316 ' instead of `extends` if implementing this abstract class.') |
| 317 WebSocket(); |
| 318 |
| 319 /** |
| 320 * Creates a WebSocket from an already-upgraded socket. |
| 321 * |
| 322 * The initial WebSocket handshake must have occurred prior to this call. A |
| 323 * WebSocket client can automatically perform the handshake using |
| 324 * [WebSocket.connect], while a server can do so using |
| 325 * [WebSocketTransformer.upgrade]. To manually upgrade an [HttpRequest], |
| 326 * [HttpRequest.detachSocket] may be called. |
| 327 * |
| 328 * [protocol] should be the protocol negotiated by this handshake, if any. |
| 329 * |
| 330 * [serverSide] must be passed explicitly. If it's `false`, the WebSocket will |
| 331 * act as the client and mask the messages it sends. If it's `true`, it will |
| 332 * act as the server and will not mask its messages. |
| 333 * |
| 334 * If [compression] is provided, the [WebSocket] created will be configured |
| 335 * to negotiate with the specified [CompressionOptions]. If none is specified |
| 336 * then the [WebSocket] will be created with the default [CompressionOptions]. |
| 337 */ |
| 338 factory WebSocket.fromUpgradedSocket(Socket socket, |
| 339 {String protocol, |
| 340 bool serverSide, |
| 341 CompressionOptions compression: CompressionOptions.DEFAULT}) { |
| 342 if (serverSide == null) { |
| 343 throw new ArgumentError("The serverSide argument must be passed " |
| 344 "explicitly to WebSocket.fromUpgradedSocket."); |
| 345 } |
| 346 return new _WebSocketImpl._fromSocket( |
| 347 socket, protocol, compression, serverSide); |
| 348 } |
| 349 |
| 350 /** |
| 351 * Returns the current state of the connection. |
| 352 */ |
| 353 int get readyState; |
| 354 |
| 355 /** |
| 356 * The extensions property is initially the empty string. After the |
| 357 * WebSocket connection is established this string reflects the |
| 358 * extensions used by the server. |
| 359 */ |
| 360 String get extensions; |
| 361 |
| 362 /** |
| 363 * The protocol property is initially the empty string. After the |
| 364 * WebSocket connection is established the value is the subprotocol |
| 365 * selected by the server. If no subprotocol is negotiated the |
| 366 * value will remain [:null:]. |
| 367 */ |
| 368 String get protocol; |
| 369 |
| 370 /** |
| 371 * The close code set when the WebSocket connection is closed. If |
| 372 * there is no close code available this property will be [:null:] |
| 373 */ |
| 374 int get closeCode; |
| 375 |
| 376 /** |
| 377 * The close reason set when the WebSocket connection is closed. If |
| 378 * there is no close reason available this property will be [:null:] |
| 379 */ |
| 380 String get closeReason; |
| 381 |
| 382 /** |
| 383 * Closes the WebSocket connection. Set the optional [code] and [reason] |
| 384 * arguments to send close information to the remote peer. If they are |
| 385 * omitted, the peer will see [WebSocketStatus.NO_STATUS_RECEIVED] code |
| 386 * with no reason. |
| 387 */ |
| 388 Future close([int code, String reason]); |
| 389 |
| 390 /** |
| 391 * Sends data on the WebSocket connection. The data in [data] must |
| 392 * be either a [:String:], or a [:List<int>:] holding bytes. |
| 393 */ |
| 394 void add(data); |
| 395 |
| 396 /** |
| 397 * Sends data from a stream on WebSocket connection. Each data event from |
| 398 * [stream] will be send as a single WebSocket frame. The data from [stream] |
| 399 * must be either [:String:]s, or [:List<int>:]s holding bytes. |
| 400 */ |
| 401 Future addStream(Stream stream); |
| 402 } |
| 403 |
| 404 class WebSocketException implements IOException { |
| 405 final String message; |
| 406 |
| 407 const WebSocketException([this.message = ""]); |
| 408 |
| 409 String toString() => "WebSocketException: $message"; |
| 410 } |
OLD | NEW |