[io/http] Add a HTTP Client parameter on WebSocket.connect to allow a custom HTTP Client for web socket connections.

The WebSocket abstract class was changed to allow an optional parameter called customClient that takes in a HTTPClient and passes it to the WebSocket Implementation.
The WebSocket implementation takes the customClient, checks if its null, if its not null, it uses the customClient in place of the static HTTPClient that the WebSocket Implementation offers.
This custom client does not override the static HTTPClient, so all previous functionality remains the same when the customClient is not present.

TEST=testStaticClientUserAgentStaysTheSame() in web_socket_test.dart in standalone_2/standalone
TEST=new SecurityConfiguration(secure: true).runTests(); in web_socket_error_test.dart and web_socket_test.dart in standalone_2/standalone

Bug: https://github.com/dart-lang/sdk/issues/34284

Closes https://github.com/dart-lang/sdk/pull/46040
https://github.com/dart-lang/sdk/pull/46040

GitOrigin-RevId: 58fed38baa606a8a492d3729190afa5009cc2409
Change-Id: I042b1e3fa7a4effed076c0deeec1f86af0dfe26d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/200262
Reviewed-by: Alexander Aprelev <aam@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
rnewquist 2021-09-15 18:06:42 +00:00 committed by commit-bot@chromium.org
parent c612650b8c
commit 2917c1cb8f
5 changed files with 82 additions and 45 deletions

View file

@ -375,8 +375,10 @@ abstract class WebSocket
{Iterable<String>? protocols,
Map<String, dynamic>? headers,
CompressionOptions compression =
CompressionOptions.compressionDefault}) =>
_WebSocketImpl.connect(url, protocols, headers, compression: compression);
CompressionOptions.compressionDefault,
HttpClient? customClient}) =>
_WebSocketImpl.connect(url, protocols, headers,
compression: compression, customClient: customClient);
@Deprecated('This constructor will be removed in Dart 2.0. Use `implements`'
' instead of `extends` if implementing this abstract class.')

View file

@ -999,8 +999,8 @@ class _WebSocketImpl extends Stream with _ServiceObject implements WebSocket {
static Future<WebSocket> connect(
String url, Iterable<String>? protocols, Map<String, dynamic>? headers,
{CompressionOptions compression =
CompressionOptions.compressionDefault}) {
{CompressionOptions compression = CompressionOptions.compressionDefault,
HttpClient? customClient}) {
Uri uri = Uri.parse(url);
if (uri.scheme != "ws" && uri.scheme != "wss") {
throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'");
@ -1024,7 +1024,7 @@ class _WebSocketImpl extends Stream with _ServiceObject implements WebSocket {
path: uri.path,
query: uri.query,
fragment: uri.fragment);
return _httpClient.openUrl("GET", uri).then((request) {
return (customClient ?? _httpClient).openUrl("GET", uri).then((request) {
if (uri.userInfo != null && !uri.userInfo.isEmpty) {
// If the URL contains user information use that for basic
// authorization.

View file

@ -48,8 +48,8 @@ class SecurityConfiguration {
: HttpServer.bind(HOST_NAME, 0, backlog: backlog);
Future<WebSocket> createClient(int port) =>
// TODO(whesse): Add a client context argument to WebSocket.connect.
WebSocket.connect('${secure ? "wss" : "ws"}://$HOST_NAME:$port/');
WebSocket.connect('${secure ? "wss" : "ws"}://$HOST_NAME:$port/',
customClient: secure ? HttpClient(context: clientContext) : null);
void testForceCloseServerEnd(int totalConnections) {
createServer().then((server) {
@ -94,7 +94,6 @@ class SecurityConfiguration {
main() {
asyncStart();
new SecurityConfiguration(secure: false).runTests();
// TODO(whesse): WebSocket.connect needs an optional context: parameter
// new SecurityConfiguration(secure: true).runTests();
new SecurityConfiguration(secure: true).runTests();
asyncEnd();
}

View file

@ -13,10 +13,8 @@ import "dart:io";
import "dart:typed_data";
import "package:async_helper/async_helper.dart";
import "package:convert/convert.dart";
import "package:crypto/crypto.dart";
import "package:expect/expect.dart";
import "package:path/path.dart";
const WEB_SOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@ -44,9 +42,17 @@ class SecurityConfiguration {
? HttpServer.bindSecure(HOST_NAME, 0, serverContext, backlog: backlog)
: HttpServer.bind(HOST_NAME, 0, backlog: backlog);
Future<WebSocket> createClient(int port) =>
// TODO(whesse): Add client context argument to WebSocket.connect
WebSocket.connect('${secure ? "wss" : "ws"}://$HOST_NAME:$port/');
Future<WebSocket> createClient(int port,
{String? user,
Map<String, Object>? headers,
String? customUserAgent}) =>
WebSocket.connect(
'${secure ? "wss" : "ws"}://${user is Null ? "" : "$user@"}$HOST_NAME:$port/',
headers: headers,
customClient: secure
? (HttpClient(context: clientContext)
..userAgent = customUserAgent)
: null);
checkCloseStatus(webSocket, closeStatus, closeReason) {
Expect.equals(
@ -304,7 +310,7 @@ class SecurityConfiguration {
asyncEnd();
});
HttpClient client = new HttpClient();
final client = HttpClient(context: secure ? clientContext : null);
client
.postUrl(Uri.parse(
"${secure ? 'https:' : 'http:'}//$HOST_NAME:${server.port}/"))
@ -401,12 +407,12 @@ class SecurityConfiguration {
var baseWsUrl = '$wsProtocol://$HOST_NAME:${server.port}/';
var httpProtocol = '${secure ? "https" : "http"}';
var baseHttpUrl = '$httpProtocol://$HOST_NAME:${server.port}/';
HttpClient client = new HttpClient();
final client = HttpClient(context: secure ? clientContext : null);
for (int i = 0; i < connections; i++) {
var completer = new Completer();
futures.add(completer.future);
WebSocket.connect('${baseWsUrl}').then((websocket) {
createClient(server.port).then((websocket) {
websocket.listen((_) {
websocket.close();
}, onDone: completer.complete);
@ -456,9 +462,7 @@ class SecurityConfiguration {
});
});
var url = '${secure ? "wss" : "ws"}://$HOST_NAME:${server.port}/';
WebSocket.connect(url).then((websocket) {
createClient(server.port).then((websocket) {
return websocket.listen((message) {
Expect.equals("Hello", message);
websocket.close();
@ -484,12 +488,11 @@ class SecurityConfiguration {
});
});
var url = '${secure ? "wss" : "ws"}://$HOST_NAME:${server.port}/';
var headers = {
'My-Header': 'my-value',
'My-Header-Multiple': ['my-value-1', 'my-value-2']
};
WebSocket.connect(url, headers: headers).then((websocket) {
createClient(server.port, headers: headers).then((websocket) {
return websocket.listen((message) {
Expect.equals("Hello", message);
websocket.close();
@ -522,9 +525,7 @@ class SecurityConfiguration {
});
});
var url =
'${secure ? "wss" : "ws"}://$userInfo@$HOST_NAME:${server.port}/';
WebSocket.connect(url).then((websocket) {
createClient(server.port, user: userInfo).then((websocket) {
return websocket.listen((message) {
Expect.equals("Hello", message);
websocket.close();
@ -554,6 +555,24 @@ class SecurityConfiguration {
});
}
void testStaticClientUserAgentStaysTheSame() {
asyncStart();
createServer().then((server) {
server.transform(new WebSocketTransformer()).listen((webSocket) {
Expect.equals('Custom User Agent', WebSocket.userAgent);
server.close();
webSocket.close();
asyncEnd();
});
// Next line should take no effect on custom user agent value provided
WebSocket.userAgent = 'Custom User Agent';
createClient(server.port, customUserAgent: 'New User Agent')
.then((webSocket) {
webSocket.close();
});
});
}
void runTests() {
testRequestResponseClientCloses(2, null, null, 1);
testRequestResponseClientCloses(2, 3001, null, 2);
@ -582,11 +601,11 @@ class SecurityConfiguration {
testAdditionalHeaders();
testBasicAuthentication();
testShouldSetUserAgent();
testStaticClientUserAgentStaysTheSame();
}
}
main() {
new SecurityConfiguration(secure: false).runTests();
// TODO(whesse): Make WebSocket.connect() take an optional context: parameter.
// new SecurityConfiguration(secure: true).runTests();
new SecurityConfiguration(secure: true).runTests();
}

View file

@ -15,10 +15,8 @@ import "dart:io";
import "dart:typed_data";
import "package:async_helper/async_helper.dart";
import "package:convert/convert.dart";
import "package:crypto/crypto.dart";
import "package:expect/expect.dart";
import "package:path/path.dart";
const WEB_SOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@ -46,9 +44,15 @@ class SecurityConfiguration {
? HttpServer.bindSecure(HOST_NAME, 0, serverContext, backlog: backlog)
: HttpServer.bind(HOST_NAME, 0, backlog: backlog);
Future<WebSocket> createClient(int port) =>
// TODO(whesse): Add client context argument to WebSocket.connect
WebSocket.connect('${secure ? "wss" : "ws"}://$HOST_NAME:$port/');
Future<WebSocket> createClient(int port,
{String user, Map<String, Object> headers, String customUserAgent}) =>
WebSocket.connect(
'${secure ? "wss" : "ws"}://${user is Null ? "" : "$user@"}$HOST_NAME:$port/',
headers: headers,
customClient: secure
? (HttpClient(context: clientContext)
..userAgent = customUserAgent)
: null);
checkCloseStatus(webSocket, closeStatus, closeReason) {
Expect.equals(
@ -306,7 +310,7 @@ class SecurityConfiguration {
asyncEnd();
});
HttpClient client = new HttpClient();
final client = HttpClient(context: secure ? clientContext : null);
client
.postUrl(Uri.parse(
"${secure ? 'https:' : 'http:'}//$HOST_NAME:${server.port}/"))
@ -403,12 +407,12 @@ class SecurityConfiguration {
var baseWsUrl = '$wsProtocol://$HOST_NAME:${server.port}/';
var httpProtocol = '${secure ? "https" : "http"}';
var baseHttpUrl = '$httpProtocol://$HOST_NAME:${server.port}/';
HttpClient client = new HttpClient();
final client = HttpClient(context: secure ? clientContext : null);
for (int i = 0; i < connections; i++) {
var completer = new Completer();
futures.add(completer.future);
WebSocket.connect('${baseWsUrl}').then((websocket) {
createClient(server.port).then((websocket) {
websocket.listen((_) {
websocket.close();
}, onDone: completer.complete);
@ -458,9 +462,7 @@ class SecurityConfiguration {
});
});
var url = '${secure ? "wss" : "ws"}://$HOST_NAME:${server.port}/';
WebSocket.connect(url).then((websocket) {
createClient(server.port).then((websocket) {
return websocket.listen((message) {
Expect.equals("Hello", message);
websocket.close();
@ -486,12 +488,11 @@ class SecurityConfiguration {
});
});
var url = '${secure ? "wss" : "ws"}://$HOST_NAME:${server.port}/';
var headers = {
'My-Header': 'my-value',
'My-Header-Multiple': ['my-value-1', 'my-value-2']
};
WebSocket.connect(url, headers: headers).then((websocket) {
createClient(server.port, headers: headers).then((websocket) {
return websocket.listen((message) {
Expect.equals("Hello", message);
websocket.close();
@ -524,9 +525,7 @@ class SecurityConfiguration {
});
});
var url =
'${secure ? "wss" : "ws"}://$userInfo@$HOST_NAME:${server.port}/';
WebSocket.connect(url).then((websocket) {
createClient(server.port, user: userInfo).then((websocket) {
return websocket.listen((message) {
Expect.equals("Hello", message);
websocket.close();
@ -556,6 +555,24 @@ class SecurityConfiguration {
});
}
void testStaticClientUserAgentStaysTheSame() {
asyncStart();
createServer().then((server) {
server.transform(new WebSocketTransformer()).listen((webSocket) {
Expect.equals('Custom User Agent', WebSocket.userAgent);
server.close();
webSocket.close();
asyncEnd();
});
// Next line should take no effect on custom user agent value provided
WebSocket.userAgent = 'Custom User Agent';
createClient(server.port, customUserAgent: 'New User Agent')
.then((webSocket) {
webSocket.close();
});
});
}
void runTests() {
testRequestResponseClientCloses(2, null, null, 1);
testRequestResponseClientCloses(2, 3001, null, 2);
@ -584,11 +601,11 @@ class SecurityConfiguration {
testAdditionalHeaders();
testBasicAuthentication();
testShouldSetUserAgent();
testStaticClientUserAgentStaysTheSame();
}
}
main() {
new SecurityConfiguration(secure: false).runTests();
// TODO(whesse): Make WebSocket.connect() take an optional context: parameter.
// new SecurityConfiguration(secure: true).runTests();
new SecurityConfiguration(secure: true).runTests();
}