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.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( app.fallback((req, res) => throw new AngelHttpException.notFound(
message: 'No file exists at ${req.uri.path}')); message: 'No file exists at ${req.uri.path}'));
@ -38,6 +38,6 @@ main() async {
// HTTP/1.x requests will fallback to `AngelHttp` // HTTP/1.x requests will fallback to `AngelHttp`
http2.onHttp1.listen(http1.handleRequest); http2.onHttp1.listen(http1.handleRequest);
var server = await http2.startServer('127.0.0.1', 3000); await http2.startServer('127.0.0.1', 3000);
print('Listening at https://${server.address.address}:${server.port}'); print('Listening at ${http2.uri}');
} }

View file

@ -18,7 +18,7 @@ main() async {
}); });
// Index route. Returns JSON. // 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. // Accepts a URL like /greet/foo or /greet/bob.
app.get( app.get(

View file

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

View file

@ -59,7 +59,8 @@ abstract class RequestContext<RawRequest> {
String get originalMethod; String get originalMethod;
/// The content type of an incoming request. /// 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. /// The URL parameters extracted from the request URI.
Map<String, dynamic> params = <String, dynamic>{}; Map<String, dynamic> params = <String, dynamic>{};
@ -67,6 +68,12 @@ abstract class RequestContext<RawRequest> {
/// The requested path. /// The requested path.
String get 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. /// The remote address requesting this resource.
InternetAddress get remoteAddress; InternetAddress get remoteAddress;
@ -76,9 +83,6 @@ abstract class RequestContext<RawRequest> {
/// The [Uri] instance representing the path this request is responding to. /// The [Uri] instance representing the path this request is responding to.
Uri get uri; Uri get uri;
/// Is this an **XMLHttpRequest**?
bool get xhr;
/// Returns the file extension of the requested path, if any. /// Returns the file extension of the requested path, if any.
/// ///
/// Includes the leading `.`, if there is one. /// Includes the leading `.`, if there is one.

View file

@ -20,7 +20,10 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
abstract class ResponseContext<RawResponse> abstract class ResponseContext<RawResponse>
implements StreamSink<List<int>>, StringSink { implements StreamSink<List<int>>, StringSink {
final Map properties = {}; 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; Completer _done;
int _statusCode = 200; int _statusCode = 200;
@ -101,7 +104,18 @@ abstract class ResponseContext<RawResponse>
FutureOr<RawResponse> detach(); FutureOr<RawResponse> detach();
/// Gets or sets the content type to send back to a client. /// 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() => static StateError closed() =>
new StateError('Cannot modify a closed response.'); 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. /// 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() { Future close() {
if (buffer is LockableBytesBuilder) { if (buffer is LockableBytesBuilder) {
(buffer as LockableBytesBuilder).lock(); (buffer as LockableBytesBuilder).lock();
@ -145,21 +157,21 @@ abstract class ResponseContext<RawResponse>
/// You can override the [contentType] sent; by default it is `application/javascript`. /// You can override the [contentType] sent; by default it is `application/javascript`.
void jsonp(value, {String callbackName: "callback", MediaType contentType}) { void jsonp(value, {String callbackName: "callback", MediaType contentType}) {
if (!isOpen) throw closed(); if (!isOpen) throw closed();
write("$callbackName(${serializer(value)})");
this.contentType = this.contentType =
contentType ?? new MediaType('application', 'javascript'); contentType ?? new MediaType('application', 'javascript');
write("$callbackName(${serializer(value)})");
close(); close();
} }
/// Renders a view to the response stream, and closes the response. /// Renders a view to the response stream, and closes the response.
Future render(String view, [Map<String, dynamic> data]) { Future render(String view, [Map<String, dynamic> data]) {
if (!isOpen) throw closed(); if (!isOpen) throw closed();
contentType = new MediaType('text', 'html');
return Future<String>.sync(() => app.viewGenerator( return Future<String>.sync(() => app.viewGenerator(
view, view,
new Map<String, dynamic>.from(renderParams) new Map<String, dynamic>.from(renderParams)
..addAll(data ?? <String, dynamic>{}))).then((content) { ..addAll(data ?? <String, dynamic>{}))).then((content) {
write(content); write(content);
contentType = new MediaType('text', 'html');
close(); close();
}); });
} }
@ -296,8 +308,6 @@ abstract class ResponseContext<RawResponse>
/// Adds a stream directly the underlying response. /// 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 /// If this instance has access to a [correspondingRequest], then it will attempt to transform
/// the content using at most one of the response [encoders]. /// the content using at most one of the response [encoders].
@override @override
@ -305,7 +315,9 @@ abstract class ResponseContext<RawResponse>
@override @override
void addError(Object error, [StackTrace stackTrace]) { 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. /// 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) => void addCookies(HttpResponse response, Iterable<Cookie> cookies) =>
response.cookies.addAll(cookies); response.cookies.addAll(cookies);
@override
Future<HttpServer> close() async {
await server?.close();
return await super.close();
}
@override @override
Future closeResponse(HttpResponse response) => response.close(); Future closeResponse(HttpResponse response) => response.close();
@ -95,11 +101,9 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
} }
@override @override
HttpResponse createResponseFromRawRequest(HttpRequest request) => Stream<HttpResponse> createResponseStreamFromRawRequest(
request.response; HttpRequest request) =>
new Stream.fromIterable([request.response]);
@override
Uri getUriFromRequest(HttpRequest request) => request.uri;
@override @override
void setChunkedEncoding(HttpResponse response, bool value) => void setChunkedEncoding(HttpResponse response, bool value) =>

View file

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

View file

@ -2,6 +2,8 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:http_parser/http_parser.dart';
import '../core/core.dart'; import '../core/core.dart';
import 'http_request_context.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() { bool _openStream() {
if (!_streamInitialized) { if (!_streamInitialized) {
// If this is the first stream added to this response, // If this is the first stream added to this response,

View file

@ -2,34 +2,32 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:angel_framework/angel_framework.dart' hide Header; 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:http2/transport.dart';
import 'package:mock_request/mock_request.dart'; import 'package:mock_request/mock_request.dart';
import 'http2_request_context.dart'; import 'http2_request_context.dart';
import 'http2_response_context.dart'; import 'http2_response_context.dart';
import 'package:pool/pool.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'package:tuple/tuple.dart';
class AngelHttp2 extends Driver<Socket, ServerTransportStream, class AngelHttp2 extends Driver<Socket, ServerTransportStream,
ArtificialServerSocket, Http2RequestContext, Http2ResponseContext> { SecureServerSocket, Http2RequestContext, Http2ResponseContext> {
final ServerSettings settings; final ServerSettings settings;
final StreamController<HttpRequest> _onHttp1 = new StreamController(); final StreamController<HttpRequest> _onHttp1 = new StreamController();
final Map<String, MockHttpSession> _sessions = {}; final Map<String, MockHttpSession> _sessions = {};
final Uuid _uuid = new Uuid(); final Uuid _uuid = new Uuid();
ArtificialServerSocket _artificial; _AngelHttp2ServerSocket _artificial;
HttpServer _httpServer;
StreamController<SecureSocket> _http1; SecureServerSocket get socket => _artificial;
SecureServerSocket _socket;
StreamSubscription _sub;
AngelHttp2._( AngelHttp2._(
Angel app, Angel app,
Future<ArtificialServerSocket> Function(dynamic, int) serverGenerator, Future<SecureServerSocket> Function(dynamic, int) serverGenerator,
bool useZone, bool useZone,
this.settings) this.settings)
: super(app, serverGenerator, useZone: useZone); : super(
app,
serverGenerator,
useZone: useZone,
);
factory AngelHttp2(Angel app, SecurityContext securityContext, factory AngelHttp2(Angel app, SecurityContext securityContext,
{bool useZone: true, ServerSettings settings}) { {bool useZone: true, ServerSettings settings}) {
@ -48,14 +46,25 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
var addr = address is InternetAddress var addr = address is InternetAddress
? address ? address
: new InternetAddress(address.toString()); : new InternetAddress(address.toString());
return SecureServerSocket.bind(addr, port, ctx) return SecureServerSocket.bind(addr, port, ctx);
.then((s) => ArtificialServerSocket(addr, port, s));
}, useZone, settings); }, useZone, settings);
} }
/// Fires when an HTTP/1.x request is received. /// Fires when an HTTP/1.x request is received.
Stream<HttpRequest> get onHttp1 => _onHttp1.stream; 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 @override
void addCookies(ServerTransportStream response, Iterable<Cookie> cookies) { void addCookies(ServerTransportStream response, Iterable<Cookie> cookies) {
var headers = cookies var headers = cookies
@ -84,14 +93,11 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
} }
@override @override
ServerTransportStream createResponseFromRawRequest(Socket request) { Stream<ServerTransportStream> createResponseStreamFromRawRequest(
Socket request) {
var connection = var connection =
new ServerTransportConnection.viaSocket(request, settings: settings); new ServerTransportConnection.viaSocket(request, settings: settings);
} return connection.incomingStreams;
@override
Uri getUriFromRequest(Socket request) {
// TODO: implement getUriFromRequest
} }
@override @override
@ -130,3 +136,85 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
response.sendData(data); 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:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:angel_container/src/container.dart';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:body_parser/body_parser.dart'; import 'package:body_parser/body_parser.dart';
import 'package:http_parser/http_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 _comma = new RegExp(r',\s*');
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
class Http2RequestContext extends RequestContext { class Http2RequestContext extends RequestContext<ServerTransportStream> {
final Container container;
BytesBuilder _buf; BytesBuilder _buf;
ContentType _contentType;
List<Cookie> _cookies; List<Cookie> _cookies;
HttpHeaders _headers; HttpHeaders _headers;
String _method, _override, _path; String _method, _override, _path;
@ -22,13 +23,15 @@ class Http2RequestContext extends RequestContext {
ServerTransportStream _stream; ServerTransportStream _stream;
Uri _uri; Uri _uri;
Http2RequestContext._(this.container);
static Future<Http2RequestContext> from( static Future<Http2RequestContext> from(
ServerTransportStream stream, ServerTransportStream stream,
Socket socket, Socket socket,
Angel app, Angel app,
Map<String, MockHttpSession> sessions, Map<String, MockHttpSession> sessions,
Uuid uuid) async { Uuid uuid) async {
var req = new Http2RequestContext() var req = new Http2RequestContext._(app.container.createChild())
..app = app ..app = app
.._socket = socket .._socket = socket
.._stream = stream; .._stream = stream;
@ -93,7 +96,7 @@ class Http2RequestContext extends RequestContext {
cookies.firstWhere((c) => c.name == 'DARTSESSID', orElse: () => null); cookies.firstWhere((c) => c.name == 'DARTSESSID', orElse: () => null);
if (dartSessId == null) { if (dartSessId == null) {
dartSessId = new Cookie('DARTSESSID', uuid.v4()); dartSessId = new Cookie('DARTSESSID', uuid.v4() as String);
} }
req._session = sessions.putIfAbsent( req._session = sessions.putIfAbsent(
@ -110,12 +113,6 @@ class Http2RequestContext extends RequestContext {
/// The underlying HTTP/2 [ServerTransportStream]. /// The underlying HTTP/2 [ServerTransportStream].
ServerTransportStream get stream => _stream; ServerTransportStream get stream => _stream;
@override
bool get xhr {
return headers.value("X-Requested-With")?.trim()?.toLowerCase() ==
'xmlhttprequest';
}
@override @override
Uri get uri => _uri; Uri get uri => _uri;
@ -132,12 +129,6 @@ class Http2RequestContext extends RequestContext {
return _path; return _path;
} }
@override
ContentType get contentType =>
_contentType ??= (headers['content-type'] == null
? null
: ContentType.parse(headers.value('content-type')));
@override @override
String get originalMethod { String get originalMethod {
return _method; return _method;
@ -148,9 +139,6 @@ class Http2RequestContext extends RequestContext {
return _override ?? _method; return _override ?? _method;
} }
@override
HttpRequest get io => null;
@override @override
String get hostname => _headers.value('host'); String get hostname => _headers.value('host');
@ -168,7 +156,10 @@ class Http2RequestContext extends RequestContext {
new Stream.fromIterable([_buf.takeBytes()]), new Stream.fromIterable([_buf.takeBytes()]),
contentType == null ? null : new MediaType.parse(contentType.toString()), contentType == null ? null : new MediaType.parse(contentType.toString()),
uri, 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 'package:http2/transport.dart';
import 'http2_request_context.dart'; import 'http2_request_context.dart';
class Http2ResponseContext extends ResponseContext { class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
final Angel app; final Angel app;
final ServerTransportStream stream; final ServerTransportStream stream;
ServerTransportStream get rawResponse => stream;
LockableBytesBuilder _buffer;
final Http2RequestContext _req; final Http2RequestContext _req;
bool _useStream = false, _isClosed = false, _isPush = false;
bool _isDetached = false,
_isClosed = false,
_streamInitialized = false,
_isPush = false;
Uri _targetUri; Uri _targetUri;
Http2ResponseContext(this.app, this.stream, this._req) { 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. /// Returns a [List] of all resources that have [push]ed to the client.
List<Http2ResponseContext> get pushes => new List.unmodifiable(_pushes); List<Http2ResponseContext> get pushes => new List.unmodifiable(_pushes);
@override
ServerTransportStream detach() {
_isDetached = true;
return stream;
}
@override @override
RequestContext get correspondingRequest => _req; RequestContext get correspondingRequest => _req;
Uri get targetUri => _targetUri; Uri get targetUri => _targetUri;
@override @override
HttpResponse get io => null; bool get isOpen {
return !_isClosed && !_isDetached;
}
@override @override
bool get streaming => _useStream; bool get isBuffered => _buffer != null;
@override @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]. /// Write headers, status, etc. to the underlying [stream].
void finalize() { bool _openStream() {
if (_isPush) return; if (_isPush || _streamInitialized) return false;
var headers = <Header>[ var headers = <Header>[
new Header.ascii(':status', statusCode.toString()), new Header.ascii(':status', statusCode.toString()),
]; ];
if (encoders.isNotEmpty && correspondingRequest != null) { if (encoders.isNotEmpty && correspondingRequest != null) {
var allowedEncodings = if (_allowedEncodings != null) {
(correspondingRequest.headers['accept-encoding'] ?? []).map((str) { for (var encodingName in _allowedEncodings) {
// Ignore quality specifications in accept-encoding Converter<List<int>, List<int>> encoder;
// ex. gzip;q=0.8
if (!str.contains(';')) return str;
return str.split(';')[0];
});
for (var encodingName in allowedEncodings) {
String key = encodingName; 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; this.headers['content-encoding'] = key;
break; break;
} }
} }
} }
}
// Add all normal headers // Add all normal headers
for (var key in this.headers.keys) { for (var key in this.headers.keys) {
@ -81,54 +110,35 @@ class Http2ResponseContext extends ResponseContext {
} }
stream.sendHeaders(headers); stream.sendHeaders(headers);
return _streamInitialized = true;
} }
@override Iterable<String> __allowedEncodings;
void addError(Object error, [StackTrace stackTrace]) {
Zone.current.handleUncaughtError(error, stackTrace);
super.addError(error, stackTrace);
}
@override Iterable<String> get _allowedEncodings {
bool useStream() { return __allowedEncodings ??= correspondingRequest.headers
if (!_useStream) { .value('accept-encoding')
// If this is the first stream added to this response, ?.split(',')
// then add headers, status code, etc. ?.map((s) => s.trim())
finalize(); ?.where((s) => s.isNotEmpty)
?.map((str) {
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) {
// Ignore quality specifications in accept-encoding // Ignore quality specifications in accept-encoding
// ex. gzip;q=0.8 // ex. gzip;q=0.8
if (!str.contains(';')) return str; if (!str.contains(';')) return str;
return str.split(';')[0]; 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; Converter<List<int>, List<int>> encoder;
String key = encodingName; String key = encodingName;
@ -139,58 +149,62 @@ class Http2ResponseContext extends ResponseContext {
} }
if (encoder != null) { if (encoder != null) {
/*
if (firstStream) {
this.stream.sendHeaders([
new Header.ascii(
'content-encoding', headers['content-encoding'] = key)
]);
}
*/
output = encoders[key].bind(output); output = encoders[key].bind(output);
break; break;
} }
} }
} }
}
return output.forEach(this.stream.sendData); return output.forEach(this.stream.sendData);
} }
@override @override
void add(List<int> data) { void add(List<int> data) {
if (_isClosed && !_useStream) if (!isOpen && isBuffered)
throw ResponseContext.closed(); throw ResponseContext.closed();
else if (_useStream) else if (!isBuffered) {
//stream.sendData(data); _openStream();
addStream(new Stream.fromIterable([data]));
else 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); buffer.add(data);
} }
@override @override
Future close() async { Future close() async {
if (_useStream) { if (!_isDetached && !_isClosed && !isBuffered) {
try { _openStream();
await stream.outgoingMessages.close(); await stream.outgoingMessages.close();
} catch (_) {
// This only seems to occur on `MockHttpRequest`, but
// this try/catch prevents a crash.
}
} }
_isClosed = true; _isClosed = true;
await super.close(); await super.close();
_useStream = false;
} }
/// Pushes a resource to the client. /// Pushes a resource to the client.
Http2ResponseContext push(String path, Http2ResponseContext push(String path,
{Map<String, String> headers: const {}, String method: 'GET'}) { {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 targetUri = _req.uri.replace(path: path);
var h = <Header>[ var h = <Header>[
@ -211,8 +225,4 @@ class Http2ResponseContext extends ResponseContext {
_pushes.add(r); _pushes.add(r);
return r; return r;
} }
void internalReopen() {
_isClosed = false;
}
} }

View file

@ -1,11 +1,6 @@
name: angel_framework name: angel_framework
version: 2.0.0-alpha.10 version: 2.0.0-alpha.11
description: > description: A high-powered HTTP server with dependency injection, routing and much more.
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.
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_framework homepage: https://github.com/angel-dart/angel_framework
environment: environment:

View file

@ -64,5 +64,5 @@ Future<RequestContext> acceptContentTypes(
rq.close(); rq.close();
var app = new Angel(reflector: MirrorsReflector()); var app = new Angel(reflector: MirrorsReflector());
var http = new AngelHttp(app); 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; optional() => 2;
} }
void foo(RequestContext req, ResponseContext res) { bool foo(RequestContext req, ResponseContext res) {
res.write("Hello, "); res.write("Hello, ");
return true;
} }
void bar(RequestContext req, ResponseContext res) { bool bar(RequestContext req, ResponseContext res) {
res.write("world!"); res.write("world!");
return true;
} }
main() { main() {

View file

@ -67,7 +67,7 @@ void encodingTests(Angel getApp()) {
await http.handleRequest(rq); await http.handleRequest(rq);
var body = await getBody(rs); var body = await getBody(rs);
print(rs.headers); //print(rs.headers);
expect(rs.headers.value('content-encoding'), 'deflate'); expect(rs.headers.value('content-encoding'), 'deflate');
expect(body, zlib.encode(utf8.encode('Hello, world!'))); 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 rq = new MockHttpRequest('GET', ENDPOINT.replace(path: path))..close();
var app = new Angel(reflector: MirrorsReflector()); var app = new Angel(reflector: MirrorsReflector());
var http = new AngelHttp(app); 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 ..keepRawRequestBuffers = true
..encoders['gzip'] = gzip.encoder; ..encoders['gzip'] = gzip.encoder;
app.get('/', (req, res) { app.get('/', (req, res) async {
res res.write('Hello world');
..write('Hello world') await res.close();
..close();
}); });
app.all('/method', (req, res) => req.method); app.all('/method', (req, res) => req.method);
@ -41,16 +40,14 @@ void main() {
app.get('/stream', (req, res) => jfkStream().pipe(res)); app.get('/stream', (req, res) => jfkStream().pipe(res));
app.get('/headers', (req, res) { app.get('/headers', (req, res) async {
res res.headers.addAll({'foo': 'bar', 'x-angel': 'http2'});
..headers.addAll({'foo': 'bar', 'x-angel': 'http2'}) await res.close();
..close();
}); });
app.get('/status', (req, res) { app.get('/status', (req, res) async {
res res.statusCode = 1337;
..statusCode = 1337 await res.close();
..close();
}); });
app.post('/body', (req, res) => req.parseBody()); app.post('/body', (req, res) => req.parseBody());
@ -63,9 +60,7 @@ void main() {
}); });
app.get('/push', (req, res) async { app.get('/push', (req, res) async {
res res.write('ok');
..write('ok')
..close();
if (res is Http2ResponseContext && res.canPush) { if (res is Http2ResponseContext && res.canPush) {
res.push('a') res.push('a')
@ -76,6 +71,8 @@ void main() {
..write('b') ..write('b')
..close(); ..close();
} }
await res.close();
}); });
var ctx = new SecurityContext() var ctx = new SecurityContext()
@ -211,7 +208,6 @@ void main() {
expect(getPath(pushA), '/a'); expect(getPath(pushA), '/a');
expect(getPath(pushB), '/b'); expect(getPath(pushB), '/b');
// TODO: Dart http/2 client seems to not be able to get body
// However, Chrome, Firefox, Edge all can // However, Chrome, Firefox, Edge all can
//expect(await getBody(pushA.stream), 'a'); //expect(await getBody(pushA.stream), 'a');
//expect(await getBody(pushB.stream), 'b'); //expect(await getBody(pushB.stream), 'b');

View file

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

View file

@ -29,8 +29,9 @@ void interceptor(RequestContext req, ResponseContext res) {
..close(); ..close();
} }
void interceptService(RequestContext req, ResponseContext res) { bool interceptService(RequestContext req, ResponseContext res) {
res.write("Service with "); res.write("Service with ");
return true;
} }
main() { main() {
@ -96,6 +97,7 @@ main() {
RequestHandler write(String message) { RequestHandler write(String message) {
return (req, res) { return (req, res) {
res.write(message); res.write(message);
return true;
}; };
} }
@ -104,7 +106,7 @@ main() {
app.fallback((req, res) => 'MJ'); app.fallback((req, res) => 'MJ');
app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true); //app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true);
client = new http.Client(); client = new http.Client();
var server = await new AngelHttp(app).startServer('127.0.0.1', 0); 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); var rq = new MockHttpRequest('GET', $foo);
rq.close(); rq.close();
var rs = rq.response; var rs = rq.response;
var req = await http.createRequestContext(rq); var req = await http.createRequestContext(rq, rs);
var res = await http.createResponseContext(rs); var res = await http.createResponseContext(rq, rs);
var e = new AngelHttpException(null, var e = new AngelHttpException(null,
statusCode: 321, message: 'Hello', errors: ['foo', 'bar']); statusCode: 321, message: 'Hello', errors: ['foo', 'bar']);
await app.errorHandler(e, req, res); await app.errorHandler(e, req, res);
await http.sendResponse(rq, req, res); await http.sendResponse(rq, rs, req, res);
expect( expect(
ContentType.parse(rs.headers.value('content-type')).mimeType, ContentType.parse(rs.headers.value('content-type')).mimeType,
'text/html', 'text/html',
@ -120,8 +120,8 @@ main() {
setUp(() async { setUp(() async {
var rq = new MockHttpRequest('GET', $foo)..close(); var rq = new MockHttpRequest('GET', $foo)..close();
req = await http.createRequestContext(rq); req = await http.createRequestContext(rq, rq.response);
res = await http.createResponseContext(rq.response); res = await http.createResponseContext(rq, rq.response);
}); });
group('getHandlerResult', () { group('getHandlerResult', () {

View file

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