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 |