2.0.0-alpha.8
This commit is contained in:
parent
b767b3583f
commit
6a5a4c35ff
7 changed files with 250 additions and 14 deletions
|
@ -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
29
dev.key
Normal 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
57
dev.pem
Normal 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
30
example/index.html
Normal 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>
|
|
@ -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}');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue