2.0.0-alpha.8

This commit is contained in:
Tobe O 2018-12-09 11:40:09 -05:00
parent b767b3583f
commit 6a5a4c35ff
7 changed files with 250 additions and 14 deletions

View file

@ -1,3 +1,6 @@
# 2.0.0-alpha.8
* Support for WebSockets over HTTP/2 (though in practice this doesn't often happen, if ever).
# 2.0.0-alpha.7
* Replace `WebSocketSynchronizer` with `StreamChannel<WebSocketEvent>`.

29
dev.key Normal file
View file

@ -0,0 +1,29 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE5DAcBgoqhkiG9w0BDAEBMA4ECL7L6rj6uEHGAgIIAASCBMLbucyfqAkgCbhP
xNSHYllPMAv/dsIjtnsBwepCXPGkCBCuOAw/2FaCHjN9hBqL5V7fkrKeaemhm2YE
ycPtlHJYPDf3kEkyMjdZ9rIY6kePGfQizs2uJPcXj4YPyQ4HsfVXpOicKfQrouf5
Mze9bGzeMN065q3iP4dYUMwHAyZYteXCsanQNHlqvsWli0W+H8St8fdsXefZhnv1
qVatKWdNdWQ9t5MuljgNU2Vv56sHKEYXI0yLxk2QUMk8KlJfnmt8foYUsnPUXHmc
gIjLKwwVkpdololnEHSNu0cEOUPowjgJru+uMpn7vdNl7TPEQ9jbEgdNg4JwoYzU
0nao8WzjaSp7kzvZz0VFwKnk5AjstGvvuAWckADdq23QElbn/mF7AG1m/TBpYxzF
gTt37UdndS/AcvVznWVVrRP5iTSIawdIwvqI4s7rqsoE0GCcak+RhchgAz2gWKkS
oODUo0JL6pPVbJ3l4ebbaO6c99nDVc8dViPtc1EkStJEJ2O4kI4xgLSCr4Y9ahKn
oAaoSkX7Xxq3aQm+BzqSpLjdGL8atsqR/YVOIHYIl3gThvP0NfZGx1xHyvO5mCdZ
kHxSA7tKWxauZ3eQ2clbnzeRsl4El0WMHy/5K1ovene4v7sunmoXVtghBC8hK6eh
zMO9orex2PNQ/VQC7HCvtytunOVx1lkSBoNo7hR70igg6rW9H7UyoAoBOwMpT1xa
J6V62nqruTKOqFNfur7aHJGpHGtDb5/ickHeYCyPTvmGp67u4wChzKReeg02oECe
d1E5FKAcIa8s9TVOB6Z+HvTRNQZu2PsI6TJnjQRowvY9DAHiWTlJZBBY/pko3hxX
TsIeybpvRdEHpDWv86/iqtw1hv9CUxS/8ZTWUgBo+osShHW79FeDASr9FC4/Zn76
ZDERTgV4YWlW/klVWcG2lFo7jix+OPXAB+ZQavLhlN1xdWBcIz1AUWjAM4hdPylW
HCX4PB9CQIPl2E7F+Y2p6nMcMWSJVBi5UIH7E9LfaBguXSzMmTk2Fw5p1aOQ6wfN
goVAMVwi8ppAVs741PfHdZ295xMmK/1LCxz5DeAdD/tsA/SYfT753GotioDuC7im
EyJ5JyvTr5I6RFFBuqt3NlUb3Hp16wP3B2x9DZiB6jxr0l341/NHgsyeBXkuIy9j
ON2mvpBPCJhS8kgWo3G0UyyKnx64tcgpGuSvZhGwPz843B6AbYyE6pMRfSWRMkMS
YZYa+VNKhR4ixdj07ocFZEWLVjCH7kxkE8JZXKt8jKYmkWd0lS1QVjgaKlO6lRa3
q6SPJkhW6pvqobvcqVNXwi1XuzpZeEbuh0B7OTekFTTxx5g9XeDl56M8SVQ1KEhT
Q1t7H2Nba18WCB7cf+6PN0F0K0Jz1Kq7ZWaqEI/grX1m4RQuvNF5807sB/QKMO/Z
Gz3NXvHg5xTJRd/567lxPGkor0cE7qD1EZfmJ2HrBYXQ91bhgA7LToBuMZo6ZRXH
QfsanjbP4FPLMiGdQigLjj3A35L/f4sQOOVac/sRaFnm7pzcxsMvyVU/YtvGcjYE
xaOOVnamg661Wo0wksXoDjeSz/JIyyKO3Gwp1FSm2wGLjjy/Ehmqcqy8rvHuf07w
AUukhVtTNn4=
-----END ENCRYPTED PRIVATE KEY-----

57
dev.pem Normal file
View file

@ -0,0 +1,57 @@
-----BEGIN CERTIFICATE-----
MIIDKTCCAhGgAwIBAgIJAOWmjTS+OnTEMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV
BAMMDGludGVybWVkaWF0ZTAeFw0xNTA1MTgwOTAwNDBaFw0yMzA4MDQwOTAwNDBa
MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBALlcwQJuzd+xH8QFgfJSn5tRlvhkldSX98cE7NiA602NBbnAVyUrkRXq
Ni75lgt0kwjYfA9z674m8WSVbgpLPintPCla9CYky1TH0keIs8Rz6cGWHryWEHiu
EDuljQynu2b3sAFuHu9nfWurbJwZnFakBKpdQ9m4EyOZCHC/jHYY7HacKSXg1Cki
we2ca0BWDrcqy8kLy0dZ5oC6IZG8O8drAK8f3f44CRYw59D3sOKBrKXaabpvyEcb
N7Wk2HDBVwHpUJo1reVwtbM8dhqQayYSD8oXnGpP3RQNu/e2rzlXRyq/BfcDY1JI
7TbC4t/7/N4EcPSpGsTcSOC9A7FpzvECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglg
hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O
BBYEFCnwiEMMFZh7NhCr+qA8K0w4Q+AOMB8GA1UdIwQYMBaAFB0h1Evsaw2vfrmS
YuoCTmC4EE6ZMA0GCSqGSIb3DQEBCwUAA4IBAQAcFmHMaXRxyoNaeOowQ6iQWoZd
AUbvG7SHr7I6Pi2aqdqofsKWts7Ytm5WsS0M2nN+sW504houu0iCPeJJX8RQw2q4
CCcNOs9IXk+2uMzlpocHpv+yYoUiD5DxgWh7eghQMLyMpf8FX3Gy4VazeuXznHOM
4gE4L417xkDzYOzqVTp0FTyAPUv6G2euhNCD6TMru9REcRhYul+K9kocjA5tt2KG
MH6y28LXbLyq4YJUxSUU9gY/xlnbbZS48KDqEcdYC9zjW9nQ0qS+XQuQuFIcwjJ5
V4kAUYxDu6FoTpyQjgsrmBbZlKNxH7Nj4NDlcdJhp/zeSKHqWa5hSWjjKIxp
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDAjCCAeqgAwIBAgIJAOWmjTS+OnTDMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
WjAXMRUwEwYDVQQDDAxpbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDSrAO1CoPvUllgLOzDm5nG0skDF7vh1DUgAIDVGz0ecD0JFbQx
EF79pju/6MbtpTW2FYvRp11t/G7rGtX923ybOHY/1MNFQrdIvPlO1VV7IGKjoMwP
DNeb0fIGjHoE9QxaDxR8NX8xQbItpsw+TUtRfc9SLkR+jaYJfVRoM21BOncZbSHE
YKiZlEbpecB/+EtwVpgvl+8mPD5U07Fi4fp/lza3WXInXQPyiTVllIEJCt4PKmlu
MocNaJOW38bysL7i0PzDpVZtOxLHOTaW68yF3FckIHNCaA7k1ABEEEegjFMmIao7
B9w7A0jvr4jZVvNmui5Djjn+oJxwEVVgyf8LAgMBAAGjUDBOMB0GA1UdDgQWBBQd
IdRL7GsNr365kmLqAk5guBBOmTAfBgNVHSMEGDAWgBRk81s9d0ZbiZhh44KckwPb
oTc0XzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBZQTK0plfdB5PC
cC5icut4EmrByJa1RbU7ayuEE70e7hla6KVmVjVdCBGltI4jBYwfhKbRItHiAJ/8
x+XZKBG8DLPFuDb7lAa1ObhAYF7YThUFPQYaBhfzKcWrdmWDBFpvNv6E0Mm364dZ
e7Yxmbe5S4agkYPoxEzgEYmcUk9jbjdR6eTbs8laG169ljrECXfEU9RiAcqz5iSX
NLSewqB47hn3B9qgKcQn+PsgO2j7M+rfklhNgeGJeWmy7j6clSOuCsIjWHU0RLQ4
0W3SB/rpEAJ7fgQbYUPTIUNALSOWi/o1tDX2mXPRjBoxqAv7I+vYk1lZPmSzkyRh
FKvRDxsW
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDAzCCAeugAwIBAgIJAJ0MomS4Ck+8MA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
WjAYMRYwFAYDVQQDDA1yb290YXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAts1ijtBV92S2cOvpUMOSTp9c6A34nIGr0T5Nhz6XiqRVT+gv
dQgmkdKJQjbvR60y6jzltYFsI2MpGVXY8h/oAL81D/k7PDB2aREgyBfTPAhBHyGw
siR+2xYt5b/Zs99q5RdRqQNzNpLPJriIKvUsRyQWy1UiG2s7pRXQeA8qB0XtJdCj
kFIi+G2bDsaffspGeDOCqt7t+yqvRXfSES0c/l7DIHaiMbbp4//ZNML3RNgAjPz2
hCezZ+wOYajOIyoSPK8IgICrhYFYxvgWxwbLDBEfC5B3jOQsySe10GoRAKZz1gBV
DmgReu81tYJmdgkc9zknnQtIFdA0ex+GvZlfWQIDAQABo1AwTjAdBgNVHQ4EFgQU
ZPNbPXdGW4mYYeOCnJMD26E3NF8wHwYDVR0jBBgwFoAUZPNbPXdGW4mYYeOCnJMD
26E3NF8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATzkZ97K777uZ
lQcduNX3ey4IbCiEzFA2zO5Blj+ilfIwNbZXNOgm/lqNvVGDYs6J1apJJe30vL3X
J+t2zsZWzzQzb9uIU37zYemt6m0fHrSrx/iy5lGNqt3HMfqEcOqSCOIK3PCTMz2/
uyGe1iw33PVeWsm1JUybQ9IrU/huJjbgOHU4wab+8SJCM49ipArp68Fr6j4lcEaE
4rfRg1ZsvxiOyUB3qPn6wyL/JB8kOJ+QCBe498376eaem8AEFk0kQRh6hDaWtq/k
t6IIXQLjx+EBDVP/veK0UnVhKRP8YTOoV8ZiG1NcdlJmX/Uk7iAfevP7CkBfSN8W
r6AL284qtw==
-----END CERTIFICATE-----

30
example/index.html Normal file
View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Angel WS</title>
</head>
<body>
<script>
var url = location.protocol === "https:" ? "wss://" : "ws://";
url += location.hostname;
if (location.port) url += ":" + location.port;
url += "/ws";
var ws = new WebSocket(url);
window.ws = ws;
ws.onmessage = function(msg) {
console.info(JSON.parse(JSON.parse(msg.data).data));
};
window.sendWs = function(msg) {
var data = { type: "ping", data: msg };
ws.send(JSON.stringify(data));
};
console.info('Connected! Type sendWs("Hey!") to play around.');
</script>
</body>
</html>

View file

@ -1,19 +1,61 @@
import "package:angel_framework/angel_framework.dart";
import "package:angel_framework/http.dart";
import "package:angel_websocket/server.dart";
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:angel_framework/http2.dart';
import 'package:angel_websocket/server.dart';
import 'package:file/local.dart';
import 'package:logging/logging.dart';
main() async {
main(List<String> args) async {
var app = new Angel();
var http = new AngelHttp(app);
var ws = new AngelWebSocket(app);
var ws = new AngelWebSocket(app, sendErrors: !app.isProduction);
var fs = const LocalFileSystem();
app.logger = new Logger('angel_auth')
..onRecord.listen((rec) {
print(rec);
if (rec.error != null) print(rec.error);
if (rec.stackTrace != null) print(rec.stackTrace);
});
// This is a plug-in. It hooks all your services,
// to automatically broadcast events.
await app.configure(ws.configureServer);
// Listen for requests at `/ws`.
app.all('/ws', ws.handleRequest);
app.get('/', (req, res) => res.streamFile(fs.file('example/index.html')));
var server = await http.startServer('127.0.0.1', 3000);
print('Listening at http://${server.address.address}:${server.port}');
// Listen for requests at `/ws`.
app.get('/ws', ws.handleRequest);
app.fallback((req, res) => throw AngelHttpException.notFound());
ws.onConnection.listen((socket) {
socket.onData.listen((x) {
socket.send('pong', x);
});
});
if (args.contains('http2')) {
var ctx = new SecurityContext()
..useCertificateChain('dev.pem')
..usePrivateKey('dev.key', password: 'dartdart');
try {
ctx.setAlpnProtocols(['h2'], true);
} catch (e, st) {
app.logger.severe(
'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.',
e,
st,
);
}
var http2 = new AngelHttp2(app, ctx);
http2.onHttp1.forEach(http.handleRequest);
await http2.startServer('127.0.0.1', 3000);
print('Listening at ${http2.uri}');
} else {
await http.startServer('127.0.0.1', 3000);
print('Listening at ${http.uri}');
}
}

View file

@ -7,10 +7,12 @@ import 'dart:io';
import 'dart:mirrors';
import 'package:angel_auth/angel_auth.dart';
import 'package:angel_framework/angel_framework.dart';
import "package:angel_framework/http.dart";
import 'package:angel_framework/http.dart';
import 'package:angel_framework/http2.dart';
import 'package:merge_map/merge_map.dart';
import 'package:stream_channel/stream_channel.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'angel_websocket.dart';
export 'angel_websocket.dart';
@ -39,6 +41,12 @@ class AngelWebSocket {
/// discarded, other than `params['query']`.
final bool allowClientParams;
/// An optional whitelist of allowed client origins, or [:null:].
final List<String> allowedOrigins;
/// An optional whitelist of allowed client protocols, or [:null:].
final List<String> allowedProtocols;
/// If `true`, then clients can authenticate their WebSockets by sending a valid JWT.
final bool allowAuth;
@ -79,7 +87,9 @@ class AngelWebSocket {
this.allowAuth: true,
this.synchronizationChannel,
this.serializer,
this.deserializer}) {
this.deserializer,
this.allowedOrigins,
this.allowedProtocols}) {
if (serializer == null) serializer = json.encode;
if (deserializer == null) deserializer = (params) => params;
}
@ -334,7 +344,14 @@ class AngelWebSocket {
}
/// Handles an incoming [WebSocketContext].
Future handleClient(WebSocketContext socket) async {
Future<void> handleClient(WebSocketContext socket) async {
var origin = socket.request.headers.value('origin');
if (allowedOrigins != null && !allowedOrigins.contains(origin)) {
throw new AngelHttpException.forbidden(
message:
'WebSocket connections are not allowed from the origin "$origin".');
}
_clients.add(socket);
await handleConnect(socket);
@ -370,8 +387,66 @@ class AngelWebSocket {
var socket = new WebSocketContext(channel, req, res);
handleClient(socket);
return false;
} else if (req is Http2RequestContext && res is Http2ResponseContext) {
var connection =
req.headers['connection']?.map((s) => s.toLowerCase().trim());
var upgrade = req.headers.value('upgrade')?.toLowerCase();
var version = req.headers.value('sec-websocket-version');
var key = req.headers.value('sec-websocket-key');
var protocol = req.headers.value('sec-websocket-protocol');
if (connection == null) {
throw new AngelHttpException.badRequest(
message: 'Missing `connection` header.');
} else if (!connection.contains('upgrade')) {
throw new AngelHttpException.badRequest(
message: 'Missing "upgrade" in `connection` header.');
} else if (upgrade != 'websocket') {
throw new AngelHttpException.badRequest(
message: 'The `upgrade` header must equal "websocket".');
} else if (version != '13') {
throw new AngelHttpException.badRequest(
message: 'The `sec-websocket-version` header must equal "13".');
} else if (key == null) {
throw new AngelHttpException.badRequest(
message: 'Missing `sec-websocket-key` header.');
} else if (protocol != null &&
allowedProtocols != null &&
!allowedProtocols.contains(protocol)) {
throw new AngelHttpException.badRequest(
message: 'Disallowed `sec-websocket-protocol` header "$protocol".');
} else {
var stream = res.detach();
var ctrl = new StreamChannelController<List<int>>();
ctrl.local.stream.listen((buf) {
stream.sendData(buf);
}, onDone: () {
stream.outgoingMessages.close();
});
if (req.hasParsedBody) {
ctrl.local.sink.close();
} else {
req.body.pipe(ctrl.local.sink);
}
var sink = utf8.encoder.startChunkedConversion(ctrl.foreign.sink);
sink.add("HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: ${WebSocketChannel.signKey(key)}\r\n");
if (protocol != null) sink.add("Sec-WebSocket-Protocol: $protocol\r\n");
sink.add("\r\n");
var ws = new WebSocketChannel(ctrl.foreign);
var socket = new WebSocketContext(ws, req, res);
handleClient(socket);
return false;
}
} else {
throw new ArgumentError('Not an HTTP/1.1 RequestContext: $req');
throw new ArgumentError(
'Not an HTTP/1.1 or HTTP/2 RequestContext+ResponseContext pair: $req, $res');
}
}
}

View file

@ -2,7 +2,7 @@ name: angel_websocket
description: WebSocket plugin for Angel.
environment:
sdk: ">=2.0.0-dev <3.0.0"
version: 2.0.0-alpha.7
version: 2.0.0-alpha.8
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_websocket
dependencies: