2.0.0-alpha.11

This commit is contained in:
Tobe O 2018-11-10 20:07:09 -05:00
parent 2d168dd3aa
commit bf0f01a5b9
23 changed files with 418 additions and 233 deletions

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-----

View file

@ -13,7 +13,7 @@ main() async {
});
app.logger = new Logger('angel')..onRecord.listen(prettyLog);
app.get('/', (_, __) => 'Hello HTTP/2!!!');
app.get('/', (req, res) => 'Hello HTTP/2!!!');
app.fallback((req, res) => throw new AngelHttpException.notFound(
message: 'No file exists at ${req.uri.path}'));
@ -38,6 +38,6 @@ main() async {
// HTTP/1.x requests will fallback to `AngelHttp`
http2.onHttp1.listen(http1.handleRequest);
var server = await http2.startServer('127.0.0.1', 3000);
print('Listening at https://${server.address.address}:${server.port}');
await http2.startServer('127.0.0.1', 3000);
print('Listening at ${http2.uri}');
}

View file

@ -18,7 +18,7 @@ main() async {
});
// Index route. Returns JSON.
app.get('/', (req, res) => res.write('Welcome to Angel!'));
app.get('/', (req, res) => 'Welcome to Angel!');
// Accepts a URL like /greet/foo or /greet/bob.
app.get(

View file

@ -34,15 +34,22 @@ abstract class Driver<
/// The native server running this instance.
Server get server => _server;
Future<Server> generateServer(address, int port) =>
serverGenerator(address, port);
/// Starts, and returns the server.
Future<Server> startServer([address, int port]) {
var host = address ?? '127.0.0.1';
return serverGenerator(host, port ?? 0).then((server) {
return generateServer(host, port ?? 0).then((server) {
_server = server;
return Future.wait(app.startupHooks.map(app.configure)).then((_) {
app.optimizeForProduction();
_sub = server.listen((request) =>
handleRawRequest(request, createResponseFromRawRequest(request)));
_sub = server.listen((request) {
var stream = createResponseStreamFromRawRequest(request);
stream.listen((response) {
return handleRawRequest(request, response);
});
});
return _server;
});
});
@ -78,11 +85,9 @@ abstract class Driver<
void writeToResponse(Response response, List<int> data);
Uri getUriFromRequest(Request request);
Future closeResponse(Response response);
Response createResponseFromRawRequest(Request request);
Stream<Response> createResponseStreamFromRawRequest(Request request);
/// Handles a single request.
Future handleRawRequest(Request request, Response response) {
@ -123,26 +128,17 @@ abstract class Driver<
}
var pipeline = tuple.item1;
var it = pipeline.iterator;
Future Function() runPipeline;
for (var handler in pipeline) {
if (handler == null) break;
if (runPipeline == null)
runPipeline = () =>
Future.sync(() => app.executeHandler(handler, req, res));
else {
var current = runPipeline;
runPipeline = () => current().then((result) => !res.isOpen
? new Future.value(result)
: app.executeHandler(handler, req, res));
}
}
var runPipeline = pipeline.isEmpty
? null
: Future.doWhile(() => !it.moveNext()
? new Future.value(false)
: app.executeHandler(it.current, req, res));
return runPipeline == null
? sendResponse(request, response, req, res)
: runPipeline()
: runPipeline
.then((_) => sendResponse(request, response, req, res));
}
@ -211,7 +207,6 @@ abstract class Driver<
e, trace, req, res, request, response);
}).catchError((e, StackTrace st) {
var trace = new Trace.from(st ?? StackTrace.current).terse;
var uri = getUriFromRequest(request);
closeResponse(response);
// Ideally, we won't be in a position where an absolutely fatal error occurs,
// but if so, we'll need to log it.
@ -221,7 +216,7 @@ abstract class Driver<
} else {
stderr
..writeln('Fatal error occurred when processing '
'$uri:')
'${req.uri}:')
..writeln(e)
..writeln(trace);
}
@ -299,11 +294,10 @@ abstract class Driver<
Future finalizers = ignoreFinalizers == true
? new Future.value()
: app.responseFinalizers.fold<Future>(
new Future.value(), (out, f) => out.then((_) => f(req, res)));
: Future.forEach(app.responseFinalizers, (f) => f(req, res));
return finalizers.then((_) {
if (res.isOpen) res.close();
//if (res.isOpen) res.close();
for (var key in res.headers.keys) {
setHeader(response, key, res.headers[key]);

View file

@ -59,7 +59,8 @@ abstract class RequestContext<RawRequest> {
String get originalMethod;
/// The content type of an incoming request.
MediaType get contentType;
MediaType get contentType =>
new MediaType.parse(headers.contentType.toString());
/// The URL parameters extracted from the request URI.
Map<String, dynamic> params = <String, dynamic>{};
@ -67,6 +68,12 @@ abstract class RequestContext<RawRequest> {
/// The requested path.
String get path;
/// Is this an **XMLHttpRequest**?
bool get isXhr {
return headers.value("X-Requested-With")?.trim()?.toLowerCase() ==
'xmlhttprequest';
}
/// The remote address requesting this resource.
InternetAddress get remoteAddress;
@ -76,9 +83,6 @@ abstract class RequestContext<RawRequest> {
/// The [Uri] instance representing the path this request is responding to.
Uri get uri;
/// Is this an **XMLHttpRequest**?
bool get xhr;
/// Returns the file extension of the requested path, if any.
///
/// Includes the leading `.`, if there is one.

View file

@ -20,7 +20,10 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
abstract class ResponseContext<RawResponse>
implements StreamSink<List<int>>, StringSink {
final Map properties = {};
final Map<String, String> _headers = {'server': 'angel'};
final Map<String, String> _headers = new CaseInsensitiveMap.from({
'content-type': 'text/plain',
'server': 'angel',
});
Completer _done;
int _statusCode = 200;
@ -101,7 +104,18 @@ abstract class ResponseContext<RawResponse>
FutureOr<RawResponse> detach();
/// Gets or sets the content type to send back to a client.
MediaType contentType = new MediaType('text', 'plain');
MediaType get contentType {
try {
return new MediaType.parse(headers['content-type']);
} catch (_) {
return new MediaType('text', 'plain');
}
}
/// Gets or sets the content type to send back to a client.
void set contentType(MediaType value) {
headers['content-type'] = value.toString();
}
static StateError closed() =>
new StateError('Cannot modify a closed response.');
@ -124,8 +138,6 @@ abstract class ResponseContext<RawResponse>
}
/// Prevents more data from being written to the response, and locks it entire from further editing.
///
/// This method should be overwritten, setting [streaming] to `false`, **after** a `super` call.
Future close() {
if (buffer is LockableBytesBuilder) {
(buffer as LockableBytesBuilder).lock();
@ -145,21 +157,21 @@ abstract class ResponseContext<RawResponse>
/// You can override the [contentType] sent; by default it is `application/javascript`.
void jsonp(value, {String callbackName: "callback", MediaType contentType}) {
if (!isOpen) throw closed();
write("$callbackName(${serializer(value)})");
this.contentType =
contentType ?? new MediaType('application', 'javascript');
write("$callbackName(${serializer(value)})");
close();
}
/// Renders a view to the response stream, and closes the response.
Future render(String view, [Map<String, dynamic> data]) {
if (!isOpen) throw closed();
contentType = new MediaType('text', 'html');
return Future<String>.sync(() => app.viewGenerator(
view,
new Map<String, dynamic>.from(renderParams)
..addAll(data ?? <String, dynamic>{}))).then((content) {
write(content);
contentType = new MediaType('text', 'html');
close();
});
}
@ -296,8 +308,6 @@ abstract class ResponseContext<RawResponse>
/// Adds a stream directly the underlying response.
///
/// This will also set [willCloseItself] to `true`, thus canceling out response finalizers.
///
/// If this instance has access to a [correspondingRequest], then it will attempt to transform
/// the content using at most one of the response [encoders].
@override
@ -305,7 +315,9 @@ abstract class ResponseContext<RawResponse>
@override
void addError(Object error, [StackTrace stackTrace]) {
if (_done?.isCompleted == false) _done.completeError(error, stackTrace);
if (_done?.isCompleted == false)
_done.completeError(error, stackTrace);
else if (_done == null) Zone.current.handleUncaughtError(error, stackTrace);
}
/// Writes data to the response.

View file

@ -73,6 +73,12 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
void addCookies(HttpResponse response, Iterable<Cookie> cookies) =>
response.cookies.addAll(cookies);
@override
Future<HttpServer> close() async {
await server?.close();
return await super.close();
}
@override
Future closeResponse(HttpResponse response) => response.close();
@ -95,11 +101,9 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
}
@override
HttpResponse createResponseFromRawRequest(HttpRequest request) =>
request.response;
@override
Uri getUriFromRequest(HttpRequest request) => request.uri;
Stream<HttpResponse> createResponseStreamFromRawRequest(
HttpRequest request) =>
new Stream.fromIterable([request.response]);
@override
void setChunkedEncoding(HttpResponse response, bool value) =>

View file

@ -70,15 +70,6 @@ class HttpRequestContext extends RequestContext<HttpRequest> {
return rawRequest.uri;
}
@override
bool get xhr {
return rawRequest.headers
.value("X-Requested-With")
?.trim()
?.toLowerCase() ==
'xmlhttprequest';
}
/// Magically transforms an [HttpRequest] into a [RequestContext].
static Future<HttpRequestContext> from(
HttpRequest request, Angel app, String path) {

View file

@ -2,6 +2,8 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http_parser/http_parser.dart';
import '../core/core.dart';
import 'http_request_context.dart';
@ -68,6 +70,14 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
});
}
@override
void set contentType(MediaType value) {
super.contentType = value;
if (!_streamInitialized)
rawResponse.headers.contentType =
ContentType(value.type, value.subtype, parameters: value.parameters);
}
bool _openStream() {
if (!_streamInitialized) {
// If this is the first stream added to this response,

View file

@ -2,34 +2,32 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:angel_framework/angel_framework.dart' hide Header;
import 'package:combinator/combinator.dart';
import 'package:http2/src/artificial_server_socket.dart';
import 'package:http2/transport.dart';
import 'package:mock_request/mock_request.dart';
import 'http2_request_context.dart';
import 'http2_response_context.dart';
import 'package:pool/pool.dart';
import 'package:uuid/uuid.dart';
import 'package:tuple/tuple.dart';
class AngelHttp2 extends Driver<Socket, ServerTransportStream,
ArtificialServerSocket, Http2RequestContext, Http2ResponseContext> {
SecureServerSocket, Http2RequestContext, Http2ResponseContext> {
final ServerSettings settings;
final StreamController<HttpRequest> _onHttp1 = new StreamController();
final Map<String, MockHttpSession> _sessions = {};
final Uuid _uuid = new Uuid();
ArtificialServerSocket _artificial;
HttpServer _httpServer;
StreamController<SecureSocket> _http1;
SecureServerSocket _socket;
StreamSubscription _sub;
_AngelHttp2ServerSocket _artificial;
SecureServerSocket get socket => _artificial;
AngelHttp2._(
Angel app,
Future<ArtificialServerSocket> Function(dynamic, int) serverGenerator,
Future<SecureServerSocket> Function(dynamic, int) serverGenerator,
bool useZone,
this.settings)
: super(app, serverGenerator, useZone: useZone);
: super(
app,
serverGenerator,
useZone: useZone,
);
factory AngelHttp2(Angel app, SecurityContext securityContext,
{bool useZone: true, ServerSettings settings}) {
@ -48,14 +46,25 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
var addr = address is InternetAddress
? address
: new InternetAddress(address.toString());
return SecureServerSocket.bind(addr, port, ctx)
.then((s) => ArtificialServerSocket(addr, port, s));
return SecureServerSocket.bind(addr, port, ctx);
}, useZone, settings);
}
/// Fires when an HTTP/1.x request is received.
Stream<HttpRequest> get onHttp1 => _onHttp1.stream;
@override
Future<SecureServerSocket> generateServer([address, int port]) async {
var s = await serverGenerator(address ?? '127.0.0.1', port ?? 0);
return _artificial = new _AngelHttp2ServerSocket(s, this);
}
@override
Future<SecureServerSocket> close() async {
await _artificial.close();
return await super.close();
}
@override
void addCookies(ServerTransportStream response, Iterable<Cookie> cookies) {
var headers = cookies
@ -84,14 +93,11 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
}
@override
ServerTransportStream createResponseFromRawRequest(Socket request) {
Stream<ServerTransportStream> createResponseStreamFromRawRequest(
Socket request) {
var connection =
new ServerTransportConnection.viaSocket(request, settings: settings);
}
@override
Uri getUriFromRequest(Socket request) {
// TODO: implement getUriFromRequest
return connection.incomingStreams;
}
@override
@ -130,3 +136,85 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
response.sendData(data);
}
}
class _FakeServerSocket extends Stream<Socket> implements ServerSocket {
final _AngelHttp2ServerSocket angel;
final _ctrl = new StreamController<Socket>();
_FakeServerSocket(this.angel);
@override
InternetAddress get address => angel.address;
@override
Future<ServerSocket> close() async {
_ctrl.close();
return this;
}
@override
int get port => angel.port;
@override
StreamSubscription<Socket> listen(void Function(Socket event) onData,
{Function onError, void Function() onDone, bool cancelOnError}) {
return _ctrl.stream.listen(onData,
cancelOnError: cancelOnError, onError: onError, onDone: onDone);
}
}
class _AngelHttp2ServerSocket extends Stream<SecureSocket>
implements SecureServerSocket {
final SecureServerSocket socket;
final AngelHttp2 driver;
final _ctrl = new StreamController<SecureSocket>();
_FakeServerSocket _fake;
StreamSubscription _sub;
_AngelHttp2ServerSocket(this.socket, this.driver) {
_fake = new _FakeServerSocket(this);
new HttpServer.listenOn(_fake).pipe(driver._onHttp1);
_sub = socket.listen(
(socket) {
if (socket.selectedProtocol == null ||
socket.selectedProtocol == 'http/1.0' ||
socket.selectedProtocol == 'http/1.1') {
_fake._ctrl.add(socket);
} else if (socket.selectedProtocol == 'h2' ||
socket.selectedProtocol == 'h2-14') {
_ctrl.add(socket);
} else {
socket.destroy();
throw new Exception(
'AngelHttp2 does not support ${socket.selectedProtocol} as an ALPN protocol.');
}
},
onDone: _ctrl.close,
onError: (e, st) {
driver.app.logger.warning(
'HTTP/2 incoming connection failure: ', e, st as StackTrace);
},
);
}
InternetAddress get address => socket.address;
int get port => socket.port;
Future<SecureServerSocket> close() async {
_sub?.cancel();
_fake.close();
_ctrl.close();
return await socket.close();
}
@override
StreamSubscription<SecureSocket> listen(
void Function(SecureSocket event) onData,
{Function onError,
void Function() onDone,
bool cancelOnError}) {
return _ctrl.stream.listen(onData,
cancelOnError: cancelOnError, onError: onError, onDone: onDone);
}
}

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:angel_container/src/container.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:body_parser/body_parser.dart';
import 'package:http_parser/http_parser.dart';
@ -11,9 +12,9 @@ import 'package:uuid/uuid.dart';
final RegExp _comma = new RegExp(r',\s*');
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
class Http2RequestContext extends RequestContext {
class Http2RequestContext extends RequestContext<ServerTransportStream> {
final Container container;
BytesBuilder _buf;
ContentType _contentType;
List<Cookie> _cookies;
HttpHeaders _headers;
String _method, _override, _path;
@ -22,13 +23,15 @@ class Http2RequestContext extends RequestContext {
ServerTransportStream _stream;
Uri _uri;
Http2RequestContext._(this.container);
static Future<Http2RequestContext> from(
ServerTransportStream stream,
Socket socket,
Angel app,
Map<String, MockHttpSession> sessions,
Uuid uuid) async {
var req = new Http2RequestContext()
var req = new Http2RequestContext._(app.container.createChild())
..app = app
.._socket = socket
.._stream = stream;
@ -93,7 +96,7 @@ class Http2RequestContext extends RequestContext {
cookies.firstWhere((c) => c.name == 'DARTSESSID', orElse: () => null);
if (dartSessId == null) {
dartSessId = new Cookie('DARTSESSID', uuid.v4());
dartSessId = new Cookie('DARTSESSID', uuid.v4() as String);
}
req._session = sessions.putIfAbsent(
@ -110,12 +113,6 @@ class Http2RequestContext extends RequestContext {
/// The underlying HTTP/2 [ServerTransportStream].
ServerTransportStream get stream => _stream;
@override
bool get xhr {
return headers.value("X-Requested-With")?.trim()?.toLowerCase() ==
'xmlhttprequest';
}
@override
Uri get uri => _uri;
@ -132,12 +129,6 @@ class Http2RequestContext extends RequestContext {
return _path;
}
@override
ContentType get contentType =>
_contentType ??= (headers['content-type'] == null
? null
: ContentType.parse(headers.value('content-type')));
@override
String get originalMethod {
return _method;
@ -148,9 +139,6 @@ class Http2RequestContext extends RequestContext {
return _override ?? _method;
}
@override
HttpRequest get io => null;
@override
String get hostname => _headers.value('host');
@ -168,7 +156,10 @@ class Http2RequestContext extends RequestContext {
new Stream.fromIterable([_buf.takeBytes()]),
contentType == null ? null : new MediaType.parse(contentType.toString()),
uri,
storeOriginalBuffer: app.storeOriginalBuffer,
storeOriginalBuffer: app.keepRawRequestBuffers == true,
);
}
@override
ServerTransportStream get rawRequest => _stream;
}

View file

@ -5,11 +5,21 @@ import 'package:angel_framework/angel_framework.dart' hide Header;
import 'package:http2/transport.dart';
import 'http2_request_context.dart';
class Http2ResponseContext extends ResponseContext {
class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
final Angel app;
final ServerTransportStream stream;
ServerTransportStream get rawResponse => stream;
LockableBytesBuilder _buffer;
final Http2RequestContext _req;
bool _useStream = false, _isClosed = false, _isPush = false;
bool _isDetached = false,
_isClosed = false,
_streamInitialized = false,
_isPush = false;
Uri _targetUri;
Http2ResponseContext(this.app, this.stream, this._req) {
@ -26,46 +36,65 @@ class Http2ResponseContext extends ResponseContext {
/// Returns a [List] of all resources that have [push]ed to the client.
List<Http2ResponseContext> get pushes => new List.unmodifiable(_pushes);
@override
ServerTransportStream detach() {
_isDetached = true;
return stream;
}
@override
RequestContext get correspondingRequest => _req;
Uri get targetUri => _targetUri;
@override
HttpResponse get io => null;
bool get isOpen {
return !_isClosed && !_isDetached;
}
@override
bool get streaming => _useStream;
bool get isBuffered => _buffer != null;
@override
bool get isOpen => !_isClosed;
BytesBuilder get buffer => _buffer;
@override
void addError(Object error, [StackTrace stackTrace]) {
super.addError(error, stackTrace);
}
@override
void useBuffer() {
_buffer = new LockableBytesBuilder();
}
/// Write headers, status, etc. to the underlying [stream].
void finalize() {
if (_isPush) return;
bool _openStream() {
if (_isPush || _streamInitialized) return false;
var headers = <Header>[
new Header.ascii(':status', statusCode.toString()),
];
if (encoders.isNotEmpty && correspondingRequest != null) {
var allowedEncodings =
(correspondingRequest.headers['accept-encoding'] ?? []).map((str) {
// Ignore quality specifications in accept-encoding
// ex. gzip;q=0.8
if (!str.contains(';')) return str;
return str.split(';')[0];
});
for (var encodingName in allowedEncodings) {
if (_allowedEncodings != null) {
for (var encodingName in _allowedEncodings) {
Converter<List<int>, List<int>> encoder;
String key = encodingName;
if (encoders.containsKey(encodingName)) {
if (encoders.containsKey(encodingName))
encoder = encoders[encodingName];
else if (encodingName == '*') {
encoder = encoders[key = encoders.keys.first];
}
if (encoder != null) {
this.headers['content-encoding'] = key;
break;
}
}
}
}
// Add all normal headers
for (var key in this.headers.keys) {
@ -81,54 +110,35 @@ class Http2ResponseContext extends ResponseContext {
}
stream.sendHeaders(headers);
return _streamInitialized = true;
}
@override
void addError(Object error, [StackTrace stackTrace]) {
Zone.current.handleUncaughtError(error, stackTrace);
super.addError(error, stackTrace);
}
Iterable<String> __allowedEncodings;
@override
bool useStream() {
if (!_useStream) {
// If this is the first stream added to this response,
// then add headers, status code, etc.
finalize();
willCloseItself = _useStream = _isClosed = true;
releaseCorrespondingRequest();
return true;
}
return false;
}
@override
void end() {
_isClosed = true;
super.end();
}
@override
Future addStream(Stream<List<int>> stream) {
if (_isClosed && !_useStream) throw ResponseContext.closed();
var firstStream = useStream();
Stream<List<int>> output = stream;
if ((firstStream || !headers.containsKey('content-encoding')) &&
encoders.isNotEmpty &&
correspondingRequest != null) {
var allowedEncodings =
(correspondingRequest.headers['accept-encoding'] ?? []).map((str) {
Iterable<String> get _allowedEncodings {
return __allowedEncodings ??= correspondingRequest.headers
.value('accept-encoding')
?.split(',')
?.map((s) => s.trim())
?.where((s) => s.isNotEmpty)
?.map((str) {
// Ignore quality specifications in accept-encoding
// ex. gzip;q=0.8
if (!str.contains(';')) return str;
return str.split(';')[0];
});
}
for (var encodingName in allowedEncodings) {
@override
Future addStream(Stream<List<int>> stream) {
if (!isOpen && isBuffered) throw ResponseContext.closed();
_openStream();
Stream<List<int>> output = stream;
if (encoders.isNotEmpty && correspondingRequest != null) {
if (_allowedEncodings != null) {
for (var encodingName in _allowedEncodings) {
Converter<List<int>, List<int>> encoder;
String key = encodingName;
@ -139,58 +149,62 @@ class Http2ResponseContext extends ResponseContext {
}
if (encoder != null) {
/*
if (firstStream) {
this.stream.sendHeaders([
new Header.ascii(
'content-encoding', headers['content-encoding'] = key)
]);
}
*/
output = encoders[key].bind(output);
break;
}
}
}
}
return output.forEach(this.stream.sendData);
}
@override
void add(List<int> data) {
if (_isClosed && !_useStream)
if (!isOpen && isBuffered)
throw ResponseContext.closed();
else if (_useStream)
//stream.sendData(data);
addStream(new Stream.fromIterable([data]));
else
else if (!isBuffered) {
_openStream();
if (encoders.isNotEmpty && correspondingRequest != null) {
if (_allowedEncodings != null) {
for (var encodingName in _allowedEncodings) {
Converter<List<int>, List<int>> encoder;
String key = encodingName;
if (encoders.containsKey(encodingName))
encoder = encoders[encodingName];
else if (encodingName == '*') {
encoder = encoders[key = encoders.keys.first];
}
if (encoder != null) {
data = encoders[key].convert(data);
break;
}
}
}
}
stream.sendData(data);
} else
buffer.add(data);
}
@override
Future close() async {
if (_useStream) {
try {
if (!_isDetached && !_isClosed && !isBuffered) {
_openStream();
await stream.outgoingMessages.close();
} catch (_) {
// This only seems to occur on `MockHttpRequest`, but
// this try/catch prevents a crash.
}
}
_isClosed = true;
await super.close();
_useStream = false;
}
/// Pushes a resource to the client.
Http2ResponseContext push(String path,
{Map<String, String> headers: const {}, String method: 'GET'}) {
if (isOpen)
throw new StateError(
'You can only push resources after the main response context is closed. You will need to use streaming methods, i.e. `addStream`.');
var targetUri = _req.uri.replace(path: path);
var h = <Header>[
@ -211,8 +225,4 @@ class Http2ResponseContext extends ResponseContext {
_pushes.add(r);
return r;
}
void internalReopen() {
_isClosed = false;
}
}

View file

@ -1,11 +1,6 @@
name: angel_framework
version: 2.0.0-alpha.10
description: >
A high-powered HTTP server with DI, routing and more.
When combined with the other packages in the Angel ecosystem, this
package can be used to make robust application servers for API's,
traditional server-side applications, and much more, along with
a rich client API.
version: 2.0.0-alpha.11
description: A high-powered HTTP server with dependency injection, routing and much more.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_framework
environment:

View file

@ -64,5 +64,5 @@ Future<RequestContext> acceptContentTypes(
rq.close();
var app = new Angel(reflector: MirrorsReflector());
var http = new AngelHttp(app);
return http.createRequestContext(rq);
return http.createRequestContext(rq, rq.response);
}

View file

@ -38,12 +38,14 @@ class NamedController extends Controller {
optional() => 2;
}
void foo(RequestContext req, ResponseContext res) {
bool foo(RequestContext req, ResponseContext res) {
res.write("Hello, ");
return true;
}
void bar(RequestContext req, ResponseContext res) {
bool bar(RequestContext req, ResponseContext res) {
res.write("world!");
return true;
}
main() {

View file

@ -67,7 +67,7 @@ void encodingTests(Angel getApp()) {
await http.handleRequest(rq);
var body = await getBody(rs);
print(rs.headers);
//print(rs.headers);
expect(rs.headers.value('content-encoding'), 'deflate');
expect(body, zlib.encode(utf8.encode('Hello, world!')));
});

View file

@ -28,5 +28,5 @@ Future<RequestContext> makeRequest(String path) {
var rq = new MockHttpRequest('GET', ENDPOINT.replace(path: path))..close();
var app = new Angel(reflector: MirrorsReflector());
var http = new AngelHttp(app);
return http.createRequestContext(rq);
return http.createRequestContext(rq, rq.response);
}

View file

@ -29,10 +29,9 @@ void main() {
..keepRawRequestBuffers = true
..encoders['gzip'] = gzip.encoder;
app.get('/', (req, res) {
res
..write('Hello world')
..close();
app.get('/', (req, res) async {
res.write('Hello world');
await res.close();
});
app.all('/method', (req, res) => req.method);
@ -41,16 +40,14 @@ void main() {
app.get('/stream', (req, res) => jfkStream().pipe(res));
app.get('/headers', (req, res) {
res
..headers.addAll({'foo': 'bar', 'x-angel': 'http2'})
..close();
app.get('/headers', (req, res) async {
res.headers.addAll({'foo': 'bar', 'x-angel': 'http2'});
await res.close();
});
app.get('/status', (req, res) {
res
..statusCode = 1337
..close();
app.get('/status', (req, res) async {
res.statusCode = 1337;
await res.close();
});
app.post('/body', (req, res) => req.parseBody());
@ -63,9 +60,7 @@ void main() {
});
app.get('/push', (req, res) async {
res
..write('ok')
..close();
res.write('ok');
if (res is Http2ResponseContext && res.canPush) {
res.push('a')
@ -76,6 +71,8 @@ void main() {
..write('b')
..close();
}
await res.close();
});
var ctx = new SecurityContext()
@ -211,7 +208,6 @@ void main() {
expect(getPath(pushA), '/a');
expect(getPath(pushB), '/b');
// TODO: Dart http/2 client seems to not be able to get body
// However, Chrome, Firefox, Edge all can
//expect(await getBody(pushA.stream), 'a');
//expect(await getBody(pushB.stream), 'b');

View file

@ -62,8 +62,8 @@ main() {
try {
var rq = new MockHttpRequest('GET', Uri.parse('/num/unparsed/32'))
..close();
var req = await http.createRequestContext(rq);
var res = await http.createResponseContext(rq.response, req);
var req = await http.createRequestContext(rq, rq.response);
var res = await http.createResponseContext(rq, rq.response, req);
await app.runContained((num unparsed) => unparsed, req, res);
throw new StateError(
'ArgumentError should be thrown if a parameter cannot be resolved.');

View file

@ -29,8 +29,9 @@ void interceptor(RequestContext req, ResponseContext res) {
..close();
}
void interceptService(RequestContext req, ResponseContext res) {
bool interceptService(RequestContext req, ResponseContext res) {
res.write("Service with ");
return true;
}
main() {
@ -96,6 +97,7 @@ main() {
RequestHandler write(String message) {
return (req, res) {
res.write(message);
return true;
};
}
@ -104,7 +106,7 @@ main() {
app.fallback((req, res) => 'MJ');
app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true);
//app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true);
client = new http.Client();
var server = await new AngelHttp(app).startServer('127.0.0.1', 0);

View file

@ -44,12 +44,12 @@ main() {
var rq = new MockHttpRequest('GET', $foo);
rq.close();
var rs = rq.response;
var req = await http.createRequestContext(rq);
var res = await http.createResponseContext(rs);
var req = await http.createRequestContext(rq, rs);
var res = await http.createResponseContext(rq, rs);
var e = new AngelHttpException(null,
statusCode: 321, message: 'Hello', errors: ['foo', 'bar']);
await app.errorHandler(e, req, res);
await http.sendResponse(rq, req, res);
await http.sendResponse(rq, rs, req, res);
expect(
ContentType.parse(rs.headers.value('content-type')).mimeType,
'text/html',
@ -120,8 +120,8 @@ main() {
setUp(() async {
var rq = new MockHttpRequest('GET', $foo)..close();
req = await http.createRequestContext(rq);
res = await http.createResponseContext(rq.response);
req = await http.createRequestContext(rq, rq.response);
res = await http.createResponseContext(rq, rq.response);
});
group('getHandlerResult', () {

View file

@ -1,4 +1,4 @@
#!/usr/bin/env bash
set -e
pub run test
ANGEL_ENV=production pub run test
pub run test --timeout 5s
ANGEL_ENV=production pub run test --timeout 5s