connectToWebSocket static method
Custom WebSocket Connection logic.
Implementation
static Future<WebSocket> connectToWebSocket(
String url, {
Iterable<String>? protocols,
Map<String, dynamic>? headers,
HttpClient? httpClient,
bool? useStandardWebSocket,
}) async {
Uri? uri = Uri.parse(url);
useStandardWebSocket ??= enableStandardWebSocket;
if (useStandardWebSocket == true && uri.scheme != 'wss') {
return await awaitWithTimeout(
WebSocket.connect(url, protocols: protocols, headers: headers),
60000,
onSuccessAfterTimeout: (WebSocket socket) {
socket.close();
},
);
}
if (uri.scheme != 'ws' && uri.scheme != 'wss') {
throw WebSocketException("Unsupported URL scheme '${uri.scheme}'");
}
var random = Random();
// Generate 16 random bytes.
var nonceData = Uint8List(16);
for (var i = 0; i < 16; i++) {
nonceData[i] = random.nextInt(256);
}
var nonce = base64.encode(nonceData);
var port = uri.port;
if (port == 0) {
port = uri.scheme == 'wss' ? 443 : 80;
}
uri = Uri(
scheme: uri.scheme == 'wss' ? 'https' : 'http',
userInfo: uri.userInfo,
host: uri.host,
port: port,
path: uri.path,
query: uri.query,
);
var _client =
httpClient ??
(HttpClient()..badCertificateCallback = (a, b, c) => true);
return _client
.openUrl('GET', uri)
.then((HttpClientRequest request) async {
if (uri?.userInfo != null && uri!.userInfo.isNotEmpty) {
// If the URL contains user information use that for basic
// authorization.
var auth = base64.encode(utf8.encode(uri.userInfo));
request.headers.set(HttpHeaders.authorizationHeader, 'Basic $auth');
}
if (headers != null) {
headers.forEach(
(field, dynamic value) => request.headers.add(field, value),
);
}
// Setup the initial handshake.
request.headers
..set(HttpHeaders.connectionHeader, 'Upgrade')
..set(HttpHeaders.upgradeHeader, 'websocket')
..set('Sec-WebSocket-Key', nonce)
..set('Cache-Control', 'no-cache')
..set('Sec-WebSocket-Version', '13');
if (protocols != null) {
request.headers.add('Sec-WebSocket-Protocol', protocols.toList());
}
return request.close();
})
.then((response) {
return response;
})
.then((HttpClientResponse response) {
void error(String message) {
// Flush data.
response.detachSocket().then((Socket socket) {
socket.destroy();
});
throw WebSocketException(message);
}
if (response.statusCode != HttpStatus.switchingProtocols ||
response.headers[HttpHeaders.connectionHeader] == null ||
!response.headers[HttpHeaders.connectionHeader]!.any(
(value) => value.toLowerCase() == 'upgrade',
) ||
response.headers
.value(HttpHeaders.upgradeHeader)!
.toLowerCase() !=
'websocket') {
error("Connection to '$uri' was not upgraded to websocket");
}
var accept = response.headers.value('Sec-WebSocket-Accept');
if (accept == null) {
error("Response did not contain a 'Sec-WebSocket-Accept' header");
}
var expectedAccept =
sha1.convert('$nonce$_webSocketGUID'.codeUnits).bytes;
List<int> receivedAccept = base64.decode(accept!);
if (expectedAccept.length != receivedAccept.length) {
error("Response header 'Sec-WebSocket-Accept' is the wrong length");
}
for (var i = 0; i < expectedAccept.length; i++) {
if (expectedAccept[i] != receivedAccept[i]) {
error("Bad response 'Sec-WebSocket-Accept' header");
}
}
var protocol = response.headers.value('Sec-WebSocket-Protocol');
return response.detachSocket().then((socket) {
socket.setOption(SocketOption.tcpNoDelay, _tcpNoDelay);
return WebSocket.fromUpgradedSocket(
socket,
protocol: protocol,
serverSide: false,
);
});
})
.timeout(
Duration(minutes: 1),
onTimeout: () {
_client.close(force: true);
throw WebSocketException('timeout');
},
);
}