mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 10:49:00 +00:00
75de086f1d
performNnbdChecks is turned on. Bug:40424 Change-Id: I97a283c4a2d54f570def04d938baa9a4ced08667 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/137250 Commit-Queue: Siva Annamalai <asiva@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
493 lines
18 KiB
Dart
493 lines
18 KiB
Dart
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
part of dart._http;
|
|
|
|
/**
|
|
* WebSocket status codes used when closing a WebSocket connection.
|
|
*/
|
|
abstract class WebSocketStatus {
|
|
static const int normalClosure = 1000;
|
|
static const int goingAway = 1001;
|
|
static const int protocolError = 1002;
|
|
static const int unsupportedData = 1003;
|
|
static const int reserved1004 = 1004;
|
|
static const int noStatusReceived = 1005;
|
|
static const int abnormalClosure = 1006;
|
|
static const int invalidFramePayloadData = 1007;
|
|
static const int policyViolation = 1008;
|
|
static const int messageTooBig = 1009;
|
|
static const int missingMandatoryExtension = 1010;
|
|
static const int internalServerError = 1011;
|
|
static const int reserved1015 = 1015;
|
|
|
|
@Deprecated("Use normalClosure instead")
|
|
static const int NORMAL_CLOSURE = normalClosure;
|
|
@Deprecated("Use goingAway instead")
|
|
static const int GOING_AWAY = goingAway;
|
|
@Deprecated("Use protocolError instead")
|
|
static const int PROTOCOL_ERROR = protocolError;
|
|
@Deprecated("Use unsupportedData instead")
|
|
static const int UNSUPPORTED_DATA = unsupportedData;
|
|
@Deprecated("Use reserved1004 instead")
|
|
static const int RESERVED_1004 = reserved1004;
|
|
@Deprecated("Use noStatusReceived instead")
|
|
static const int NO_STATUS_RECEIVED = noStatusReceived;
|
|
@Deprecated("Use abnormalClosure instead")
|
|
static const int ABNORMAL_CLOSURE = abnormalClosure;
|
|
@Deprecated("Use invalidFramePayloadData instead")
|
|
static const int INVALID_FRAME_PAYLOAD_DATA = invalidFramePayloadData;
|
|
@Deprecated("Use policyViolation instead")
|
|
static const int POLICY_VIOLATION = policyViolation;
|
|
@Deprecated("Use messageTooBig instead")
|
|
static const int MESSAGE_TOO_BIG = messageTooBig;
|
|
@Deprecated("Use missingMandatoryExtension instead")
|
|
static const int MISSING_MANDATORY_EXTENSION = missingMandatoryExtension;
|
|
@Deprecated("Use internalServerError instead")
|
|
static const int INTERNAL_SERVER_ERROR = internalServerError;
|
|
@Deprecated("Use reserved1015 instead")
|
|
static const int RESERVED_1015 = reserved1015;
|
|
}
|
|
|
|
/// Options controlling compression in a [WebSocket].
|
|
///
|
|
/// A [CompressionOptions] instance can be passed to [WebSocket.connect], or
|
|
/// used in other similar places where [WebSocket] compression is configured.
|
|
///
|
|
/// In most cases the default [compressionDefault] is sufficient, but in some
|
|
/// situations, it might be desirable to use different compression parameters,
|
|
/// for example to preserve memory on small devices.
|
|
class CompressionOptions {
|
|
/// Default [WebSocket] compression configuration.
|
|
///
|
|
/// Enables compression with default window sizes and no reuse. This is the
|
|
/// default options used by [WebSocket.connect] if no [CompressionOptions] is
|
|
/// supplied.
|
|
///
|
|
/// * `clientNoContextTakeover`: false
|
|
/// * `serverNoContextTakeover`: false
|
|
/// * `clientMaxWindowBits`: null (default maximal window size of 15 bits)
|
|
/// * `serverMaxWindowBits`: null (default maximal window size of 15 bits)
|
|
static const CompressionOptions compressionDefault =
|
|
const CompressionOptions();
|
|
@Deprecated("Use compressionDefault instead")
|
|
static const CompressionOptions DEFAULT = compressionDefault;
|
|
|
|
/// No-compression configuration.
|
|
///
|
|
/// Disables compression when used as compression configuration for a
|
|
/// [WebSocket].
|
|
static const CompressionOptions compressionOff =
|
|
const CompressionOptions(enabled: false);
|
|
@Deprecated("Use compressionOff instead")
|
|
static const CompressionOptions OFF = compressionOff;
|
|
|
|
/// Whether the client will reuse its compression instances.
|
|
final bool clientNoContextTakeover;
|
|
|
|
/// Whether the server will reuse its compression instances.
|
|
final bool serverNoContextTakeover;
|
|
|
|
/// The maximal window size bit count requested by the client.
|
|
///
|
|
/// The windows size for the compression is always a power of two, so the
|
|
/// number of bits precisely determines the window size.
|
|
///
|
|
/// If set to `null`, the client has no preference, and the compression can
|
|
/// use up to its default maximum window size of 15 bits depending on the
|
|
/// server's preference.
|
|
final int? clientMaxWindowBits;
|
|
|
|
/// The maximal window size bit count requested by the server.
|
|
///
|
|
/// The windows size for the compression is always a power of two, so the
|
|
/// number of bits precisely determines the window size.
|
|
///
|
|
/// If set to `null`, the server has no preference, and the compression can
|
|
/// use up to its default maximum window size of 15 bits depending on the
|
|
/// client's preference.
|
|
final int? serverMaxWindowBits;
|
|
|
|
/// Whether WebSocket compression is enabled.
|
|
///
|
|
/// If not enabled, the remaining fields have no effect, and the
|
|
/// [compressionOff] instance can, and should, be reused instead of creating a
|
|
/// new instance with compression disabled.
|
|
final bool enabled;
|
|
|
|
const CompressionOptions(
|
|
{this.clientNoContextTakeover = false,
|
|
this.serverNoContextTakeover = false,
|
|
this.clientMaxWindowBits,
|
|
this.serverMaxWindowBits,
|
|
this.enabled = true});
|
|
|
|
/// Parses list of requested server headers to return server compression
|
|
/// response headers.
|
|
///
|
|
/// Uses [serverMaxWindowBits] value if set, otherwise will attempt to use
|
|
/// value from headers. Defaults to [WebSocket.DEFAULT_WINDOW_BITS]. Returns a
|
|
/// [_CompressionMaxWindowBits] object which contains the response headers and
|
|
/// negotiated max window bits.
|
|
_CompressionMaxWindowBits _createServerResponseHeader(
|
|
HeaderValue? requested) {
|
|
var info = new _CompressionMaxWindowBits("", 0);
|
|
|
|
String? part = requested?.parameters[_serverMaxWindowBits];
|
|
if (part != null) {
|
|
if (part.length >= 2 && part.startsWith('0')) {
|
|
throw new ArgumentError("Illegal 0 padding on value.");
|
|
} else {
|
|
int mwb = serverMaxWindowBits ??
|
|
int.tryParse(part) ??
|
|
_WebSocketImpl.DEFAULT_WINDOW_BITS;
|
|
info.headerValue = "; server_max_window_bits=${mwb}";
|
|
info.maxWindowBits = mwb;
|
|
}
|
|
} else {
|
|
info.headerValue = "";
|
|
info.maxWindowBits = _WebSocketImpl.DEFAULT_WINDOW_BITS;
|
|
}
|
|
return info;
|
|
}
|
|
|
|
/// Returns default values for client compression request headers.
|
|
String _createClientRequestHeader(HeaderValue? requested, int size) {
|
|
var info = "";
|
|
|
|
// If responding to a valid request, specify size
|
|
if (requested != null) {
|
|
info = "; client_max_window_bits=$size";
|
|
} else {
|
|
// Client request. Specify default
|
|
if (clientMaxWindowBits == null) {
|
|
info = "; client_max_window_bits";
|
|
} else {
|
|
info = "; client_max_window_bits=$clientMaxWindowBits";
|
|
}
|
|
if (serverMaxWindowBits != null) {
|
|
info += "; server_max_window_bits=$serverMaxWindowBits";
|
|
}
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
/// Create a Compression Header.
|
|
///
|
|
/// If [requested] is null or contains client request headers, returns Client
|
|
/// compression request headers with default settings for
|
|
/// `client_max_window_bits` header value. If [requested] contains server
|
|
/// response headers this method returns a Server compression response header
|
|
/// negotiating the max window bits for both client and server as requested
|
|
/// `server_max_window_bits` value. This method returns a
|
|
/// [_CompressionMaxWindowBits] object with the response headers and
|
|
/// negotiated `maxWindowBits` value.
|
|
_CompressionMaxWindowBits _createHeader([HeaderValue? requested]) {
|
|
var info = new _CompressionMaxWindowBits("", 0);
|
|
if (!enabled) {
|
|
return info;
|
|
}
|
|
|
|
info.headerValue = _WebSocketImpl.PER_MESSAGE_DEFLATE;
|
|
|
|
if (clientNoContextTakeover &&
|
|
(requested == null ||
|
|
(requested != null &&
|
|
requested.parameters.containsKey(_clientNoContextTakeover)))) {
|
|
info.headerValue += "; client_no_context_takeover";
|
|
}
|
|
|
|
if (serverNoContextTakeover &&
|
|
(requested == null ||
|
|
(requested != null &&
|
|
requested.parameters.containsKey(_serverNoContextTakeover)))) {
|
|
info.headerValue += "; server_no_context_takeover";
|
|
}
|
|
|
|
var headerList = _createServerResponseHeader(requested);
|
|
info.headerValue += headerList.headerValue;
|
|
info.maxWindowBits = headerList.maxWindowBits;
|
|
|
|
info.headerValue +=
|
|
_createClientRequestHeader(requested, info.maxWindowBits);
|
|
|
|
return info;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The [WebSocketTransformer] provides the ability to upgrade a
|
|
* [HttpRequest] to a [WebSocket] connection. It supports both
|
|
* upgrading a single [HttpRequest] and upgrading a stream of
|
|
* [HttpRequest]s.
|
|
*
|
|
* To upgrade a single [HttpRequest] use the static [upgrade] method.
|
|
*
|
|
* HttpServer server;
|
|
* server.listen((request) {
|
|
* if (...) {
|
|
* WebSocketTransformer.upgrade(request).then((websocket) {
|
|
* ...
|
|
* });
|
|
* } else {
|
|
* // Do normal HTTP request processing.
|
|
* }
|
|
* });
|
|
*
|
|
* To transform a stream of [HttpRequest] events as it implements a
|
|
* stream transformer that transforms a stream of HttpRequest into a
|
|
* stream of WebSockets by upgrading each HttpRequest from the HTTP or
|
|
* HTTPS server, to the WebSocket protocol.
|
|
*
|
|
* server.transform(new WebSocketTransformer()).listen((webSocket) => ...);
|
|
*
|
|
* This transformer strives to implement WebSockets as specified by RFC6455.
|
|
*/
|
|
abstract class WebSocketTransformer
|
|
implements StreamTransformer<HttpRequest, WebSocket> {
|
|
/**
|
|
* Create a new [WebSocketTransformer].
|
|
*
|
|
* If [protocolSelector] is provided, [protocolSelector] will be called to
|
|
* select what protocol to use, if any were provided by the client.
|
|
* [protocolSelector] is should return either a [String] or a [Future]
|
|
* completing with a [String]. The [String] must exist in the list of
|
|
* protocols.
|
|
*
|
|
* If [compression] is provided, the [WebSocket] created will be configured
|
|
* to negotiate with the specified [CompressionOptions]. If none is specified
|
|
* then the [WebSocket] will be created with the default [CompressionOptions].
|
|
*/
|
|
factory WebSocketTransformer(
|
|
{/*String|Future<String>*/ protocolSelector(List<String> protocols)?,
|
|
CompressionOptions compression = CompressionOptions.compressionDefault}) {
|
|
return new _WebSocketTransformerImpl(protocolSelector, compression);
|
|
}
|
|
|
|
/**
|
|
* Upgrades a [HttpRequest] to a [WebSocket] connection. If the
|
|
* request is not a valid WebSocket upgrade request an HTTP response
|
|
* with status code 500 will be returned. Otherwise the returned
|
|
* future will complete with the [WebSocket] when the upgrade process
|
|
* is complete.
|
|
*
|
|
* If [protocolSelector] is provided, [protocolSelector] will be called to
|
|
* select what protocol to use, if any were provided by the client.
|
|
* [protocolSelector] is should return either a [String] or a [Future]
|
|
* completing with a [String]. The [String] must exist in the list of
|
|
* protocols.
|
|
*
|
|
* If [compression] is provided, the [WebSocket] created will be configured
|
|
* to negotiate with the specified [CompressionOptions]. If none is specified
|
|
* then the [WebSocket] will be created with the default [CompressionOptions].
|
|
*/
|
|
static Future<WebSocket> upgrade(HttpRequest request,
|
|
{protocolSelector(List<String> protocols)?,
|
|
CompressionOptions compression = CompressionOptions.compressionDefault}) {
|
|
return _WebSocketTransformerImpl._upgrade(
|
|
request, protocolSelector, compression);
|
|
}
|
|
|
|
/**
|
|
* Checks whether the request is a valid WebSocket upgrade request.
|
|
*/
|
|
static bool isUpgradeRequest(HttpRequest request) {
|
|
return _WebSocketTransformerImpl._isUpgradeRequest(request);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A two-way HTTP communication object for client or server applications.
|
|
*
|
|
* The stream exposes the messages received. A text message will be of type
|
|
* `String` and a binary message will be of type `List<int>`.
|
|
*/
|
|
abstract class WebSocket
|
|
implements
|
|
Stream<dynamic /*String|List<int>*/ >,
|
|
StreamSink<dynamic /*String|List<int>*/ > {
|
|
/**
|
|
* Possible states of the connection.
|
|
*/
|
|
static const int connecting = 0;
|
|
static const int open = 1;
|
|
static const int closing = 2;
|
|
static const int closed = 3;
|
|
|
|
@Deprecated("Use connecting instead")
|
|
static const int CONNECTING = connecting;
|
|
@Deprecated("Use open instead")
|
|
static const int OPEN = open;
|
|
@Deprecated("Use closing instead")
|
|
static const int CLOSING = closing;
|
|
@Deprecated("Use closed instead")
|
|
static const int CLOSED = closed;
|
|
|
|
/**
|
|
* Set and get the interval for sending ping signals. If a ping message is not
|
|
* answered by a pong message from the peer, the `WebSocket` is assumed
|
|
* disconnected and the connection is closed with a
|
|
* [WebSocketStatus.goingAway] close code. When a ping signal is sent, the
|
|
* pong message must be received within [pingInterval].
|
|
*
|
|
* There are never two outstanding pings at any given time, and the next ping
|
|
* timer starts when the pong is received.
|
|
*
|
|
* Set the [pingInterval] to `null` to disable sending ping messages.
|
|
*
|
|
* The default value is `null`.
|
|
*/
|
|
Duration? pingInterval;
|
|
|
|
/**
|
|
* Create a new WebSocket connection. The URL supplied in [url]
|
|
* must use the scheme `ws` or `wss`.
|
|
*
|
|
* The [protocols] argument is specifying the subprotocols the
|
|
* client is willing to speak.
|
|
*
|
|
* The [headers] argument is specifying additional HTTP headers for
|
|
* setting up the connection. This would typically be the `Origin`
|
|
* header and potentially cookies. The keys of the map are the header
|
|
* fields and the values are either String or List<String>.
|
|
*
|
|
* If [headers] is provided, there are a number of headers
|
|
* which are controlled by the WebSocket connection process. These
|
|
* headers are:
|
|
*
|
|
* - `connection`
|
|
* - `sec-websocket-key`
|
|
* - `sec-websocket-protocol`
|
|
* - `sec-websocket-version`
|
|
* - `upgrade`
|
|
*
|
|
* If any of these are passed in the `headers` map they will be ignored.
|
|
*
|
|
* If the `url` contains user information this will be passed as basic
|
|
* authentication when setting up the connection.
|
|
*/
|
|
static Future<WebSocket> connect(String url,
|
|
{Iterable<String>? protocols,
|
|
Map<String, dynamic>? headers,
|
|
CompressionOptions compression =
|
|
CompressionOptions.compressionDefault}) =>
|
|
_WebSocketImpl.connect(url, protocols, headers, compression: compression);
|
|
|
|
@Deprecated('This constructor will be removed in Dart 2.0. Use `implements`'
|
|
' instead of `extends` if implementing this abstract class.')
|
|
WebSocket();
|
|
|
|
/**
|
|
* Creates a WebSocket from an already-upgraded socket.
|
|
*
|
|
* The initial WebSocket handshake must have occurred prior to this call. A
|
|
* WebSocket client can automatically perform the handshake using
|
|
* [WebSocket.connect], while a server can do so using
|
|
* [WebSocketTransformer.upgrade]. To manually upgrade an [HttpRequest],
|
|
* [HttpResponse.detachSocket] may be called.
|
|
*
|
|
* [protocol] should be the protocol negotiated by this handshake, if any.
|
|
*
|
|
* [serverSide] must be passed explicitly. If it's `false`, the WebSocket will
|
|
* act as the client and mask the messages it sends. If it's `true`, it will
|
|
* act as the server and will not mask its messages.
|
|
*
|
|
* If [compression] is provided, the [WebSocket] created will be configured
|
|
* to negotiate with the specified [CompressionOptions]. If none is specified
|
|
* then the [WebSocket] will be created with the default [CompressionOptions].
|
|
*/
|
|
factory WebSocket.fromUpgradedSocket(Socket socket,
|
|
{String? protocol,
|
|
bool? serverSide,
|
|
CompressionOptions compression = CompressionOptions.compressionDefault}) {
|
|
if (serverSide == null) {
|
|
throw new ArgumentError("The serverSide argument must be passed "
|
|
"explicitly to WebSocket.fromUpgradedSocket.");
|
|
}
|
|
return new _WebSocketImpl._fromSocket(
|
|
socket, protocol, compression, serverSide);
|
|
}
|
|
|
|
/**
|
|
* Returns the current state of the connection.
|
|
*/
|
|
int get readyState;
|
|
|
|
/**
|
|
* The extensions property is initially the empty string. After the
|
|
* WebSocket connection is established this string reflects the
|
|
* extensions used by the server.
|
|
*/
|
|
String get extensions;
|
|
|
|
/**
|
|
* The protocol property is initially the empty string. After the
|
|
* WebSocket connection is established the value is the subprotocol
|
|
* selected by the server. If no subprotocol is negotiated the
|
|
* value will remain [:null:].
|
|
*/
|
|
String? get protocol;
|
|
|
|
/**
|
|
* The close code set when the WebSocket connection is closed. If
|
|
* there is no close code available this property will be [:null:]
|
|
*/
|
|
int? get closeCode;
|
|
|
|
/**
|
|
* The close reason set when the WebSocket connection is closed. If
|
|
* there is no close reason available this property will be [:null:]
|
|
*/
|
|
String? get closeReason;
|
|
|
|
/**
|
|
* Closes the WebSocket connection. Set the optional [code] and [reason]
|
|
* arguments to send close information to the remote peer. If they are
|
|
* omitted, the peer will see [WebSocketStatus.noStatusReceived] code
|
|
* with no reason.
|
|
*/
|
|
Future close([int? code, String? reason]);
|
|
|
|
/**
|
|
* Sends data on the WebSocket connection. The data in [data] must
|
|
* be either a `String`, or a `List<int>` holding bytes.
|
|
*/
|
|
void add(/*String|List<int>*/ data);
|
|
|
|
/**
|
|
* Sends data from a stream on WebSocket connection. Each data event from
|
|
* [stream] will be send as a single WebSocket frame. The data from [stream]
|
|
* must be either `String`s, or `List<int>`s holding bytes.
|
|
*/
|
|
Future addStream(Stream stream);
|
|
|
|
/**
|
|
* Sends a text message with the text represented by [bytes].
|
|
*
|
|
* The [bytes] should be valid UTF-8 encoded Unicode characters. If they are
|
|
* not, the receiving end will close the connection.
|
|
*/
|
|
void addUtf8Text(List<int> bytes);
|
|
|
|
/**
|
|
* Gets the user agent used for WebSocket connections.
|
|
*/
|
|
static String? get userAgent => _WebSocketImpl.userAgent;
|
|
|
|
/**
|
|
* Sets the user agent to use for WebSocket connections.
|
|
*/
|
|
static set userAgent(String? userAgent) {
|
|
_WebSocketImpl.userAgent = userAgent;
|
|
}
|
|
}
|
|
|
|
class WebSocketException implements IOException {
|
|
final String message;
|
|
|
|
const WebSocketException([this.message = ""]);
|
|
|
|
String toString() => "WebSocketException: $message";
|
|
}
|