blob: 43abce1a246a2d557fc14f13df52a918a9d65051 [file] [log] [blame]
// Copyright (c) 2014, 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.
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_web_socket/shelf_web_socket.dart';
import 'package:test/test.dart';
Map<String, String> get _handshakeHeaders => {
"Upgrade": "websocket",
"Connection": "Upgrade",
"Sec-WebSocket-Key": "x3JJHMbDL1EzLkh9GBhXDw==",
"Sec-WebSocket-Version": "13"
};
void main() {
test("can communicate with a dart:io WebSocket client", () async {
var server = await shelf_io.serve(webSocketHandler((webSocket) {
webSocket.sink.add("hello!");
webSocket.stream.first.then((request) {
expect(request, equals("ping"));
webSocket.sink.add("pong");
webSocket.sink.close();
});
}), "localhost", 0);
try {
var webSocket = await WebSocket.connect('ws://localhost:${server.port}');
var n = 0;
await webSocket.listen((message) {
if (n == 0) {
expect(message, equals("hello!"));
webSocket.add("ping");
} else if (n == 1) {
expect(message, equals("pong"));
webSocket.close();
server.close();
} else {
fail("Only expected two messages.");
}
n++;
}).asFuture();
} finally {
await server.close();
}
});
test("negotiates the sub-protocol", () async {
var server = await shelf_io.serve(
webSocketHandler((webSocket, protocol) {
expect(protocol, equals("two"));
webSocket.sink.close();
}, protocols: ["three", "two", "x"]),
"localhost",
0);
try {
var webSocket = await WebSocket.connect('ws://localhost:${server.port}',
protocols: ["one", "two", "three"]);
expect(webSocket.protocol, equals("two"));
return webSocket.close();
} finally {
await server.close();
}
});
group("with a set of allowed origins", () {
var server;
var url;
setUp(() async {
server = await shelf_io.serve(
webSocketHandler((webSocket) {
webSocket.sink.close();
}, allowedOrigins: ["pub.dartlang.org", "GoOgLe.CoM"]),
"localhost",
0);
url = 'http://localhost:${server.port}/';
});
tearDown(() => server.close());
test("allows access with an allowed origin", () {
var headers = _handshakeHeaders;
headers['Origin'] = 'pub.dartlang.org';
expect(http.get(url, headers: headers), hasStatus(101));
});
test("forbids access with a non-allowed origin", () {
var headers = _handshakeHeaders;
headers['Origin'] = 'dartlang.org';
expect(http.get(url, headers: headers), hasStatus(403));
});
test("allows access with no origin", () {
expect(http.get(url, headers: _handshakeHeaders), hasStatus(101));
});
test("ignores the case of the client origin", () {
var headers = _handshakeHeaders;
headers['Origin'] = 'PuB.DaRtLaNg.OrG';
expect(http.get(url, headers: headers), hasStatus(101));
});
test("ignores the case of the server origin", () {
var headers = _handshakeHeaders;
headers['Origin'] = 'google.com';
expect(http.get(url, headers: headers), hasStatus(101));
});
});
// Regression test for issue 21894.
test("allows a Connection header with multiple values", () async {
var server = await shelf_io.serve(webSocketHandler((webSocket) {
webSocket.sink.close();
}), "localhost", 0);
var url = 'http://localhost:${server.port}/';
var headers = _handshakeHeaders;
headers['Connection'] = 'Other-Token, Upgrade';
expect(http.get(url, headers: headers).whenComplete(server.close),
hasStatus(101));
});
group("HTTP errors", () {
var server;
var url;
setUp(() async {
server = await shelf_io.serve(webSocketHandler((_) {
fail("should not create a WebSocket");
}), "localhost", 0);
url = 'http://localhost:${server.port}/';
});
tearDown(() => server.close());
test("404s for non-GET requests", () {
expect(http.delete(url, headers: _handshakeHeaders), hasStatus(404));
});
test("404s for non-Upgrade requests", () {
var headers = _handshakeHeaders;
headers.remove('Connection');
expect(http.get(url, headers: headers), hasStatus(404));
});
test("404s for non-websocket upgrade requests", () {
var headers = _handshakeHeaders;
headers['Upgrade'] = 'fblthp';
expect(http.get(url, headers: headers), hasStatus(404));
});
test("400s for a missing Sec-WebSocket-Version", () {
var headers = _handshakeHeaders;
headers.remove('Sec-WebSocket-Version');
expect(http.get(url, headers: headers), hasStatus(400));
});
test("404s for an unknown Sec-WebSocket-Version", () {
var headers = _handshakeHeaders;
headers['Sec-WebSocket-Version'] = '15';
expect(http.get(url, headers: headers), hasStatus(404));
});
test("400s for a missing Sec-WebSocket-Key", () {
var headers = _handshakeHeaders;
headers.remove('Sec-WebSocket-Key');
expect(http.get(url, headers: headers), hasStatus(400));
});
});
test("throws an error if a unary function is provided with protocols", () {
expect(() => webSocketHandler((_) => null, protocols: ['foo']),
throwsArgumentError);
});
}
Matcher hasStatus(int status) => completion(predicate((response) {
expect(response, new TypeMatcher<http.Response>());
expect(response.statusCode, equals(status));
return true;
}));