This commit is contained in:
Tobe O 2017-11-28 13:14:50 -05:00
parent 2c846ae449
commit 2d87631e8d
19 changed files with 808 additions and 586 deletions

View file

@ -26,14 +26,14 @@
<entry key="angel_route">
<value>
<list>
<option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_route-1.0.8/lib" />
<option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_route-2.0.3+2/lib" />
</list>
</value>
</entry>
<entry key="args">
<value>
<list>
<option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/args-1.0.2/lib" />
<option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/args-1.1.0/lib" />
</list>
</value>
</entry>
@ -86,6 +86,13 @@
</list>
</value>
</entry>
<entry key="combinator">
<value>
<list>
<option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/combinator-1.0.0-beta+7/lib" />
</list>
</value>
</entry>
<entry key="container">
<value>
<list>
@ -114,13 +121,6 @@
</list>
</value>
</entry>
<entry key="flatten">
<value>
<list>
<option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/flatten-1.0.0/lib" />
</list>
</value>
</entry>
<entry key="front_end">
<value>
<list>
@ -271,7 +271,7 @@
<entry key="path">
<value>
<list>
<option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/path-1.5.0/lib" />
<option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/path-1.5.1/lib" />
</list>
</value>
</entry>
@ -442,8 +442,8 @@
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/analyzer-0.30.0+4/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_http_exception-1.0.0/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_model-1.0.0/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_route-1.0.8/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/args-1.0.2/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_route-2.0.3+2/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/args-1.1.0/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/async-1.13.3/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/barback-0.15.2+13/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/body_parser-1.0.3/lib" />
@ -451,11 +451,11 @@
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/charcode-1.1.1/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/cli_util-0.1.2+1/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/collection-1.14.3/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/combinator-1.0.0-beta+7/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/container-0.1.2/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/convert-2.0.1/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/crypto-2.0.2+1/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/csslib-0.14.1/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/flatten-1.0.0/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/front_end-0.1.0-alpha.4.1/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/glob-1.1.5/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/html-0.13.2/lib" />
@ -477,7 +477,7 @@
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/node_preamble-1.4.0/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/package_config-1.0.3/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/package_resolver-1.0.2/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/path-1.5.0/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/path-1.5.1/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/plugin-0.2.0+2/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/pool-1.3.3/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.3.2/lib" />

View file

@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="only match route with matching method in routing_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
<option name="envs">
<entry key="ANGEL_ENV" value="production" />
</option>
<option name="filePath" value="$PROJECT_DIR$/test/routing_test.dart" />
<option name="scope" value="GROUP_OR_TEST_BY_NAME" />
<option name="testName" value="only match route with matching method" />
<method />
</configuration>
</component>

View file

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="tests in framework" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
<option name="filePath" value="$PROJECT_DIR$" />
<option name="scope" value="FOLDER" />
<option name="testRunnerOptions" value="-j 4" />
<method />
</configuration>
</component>

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,15 @@
# 1.1.0-alpha+9
* Fixed a bug that prevented `isProduction` from ever returning `true`.
* This enabled caching, which greatly improved performance.
* Requests no longer have independent zones, which greatly improved performance.
* `FormatException`, when caught, is automatically transformed in a `400` error response.
* Added `extension` to `RequestContext`.
* Added `strict` to `RequestContext#accepts`.
* Added a `toString` override for the `Providers` class.
* Returned to `RegExp` for stripping stray slashes.
* The request path is now only parsed once.
* Optimized the parsing of the `ACCEPT_ENCODING` header.
# 1.1.0-alpha+8
* Added an `autoIdAndDateFields` flag to `MapService`. Finally.

View file

@ -4,7 +4,7 @@ import 'dart:async';
import 'dart:io';
import 'dart:mirrors';
import 'package:body_parser/body_parser.dart';
import 'package:charcode/charcode.dart';
import 'package:path/path.dart' as p;
import 'metadata.dart';
import 'response_context.dart';
import 'routable.dart';
@ -13,7 +13,7 @@ part 'injection.dart';
/// A convenience wrapper around an incoming HTTP request.
class RequestContext {
String _acceptHeaderCache;
String _acceptHeaderCache, _extensionCache;
bool _acceptsAllCache;
BodyParseResult _body;
ContentType _contentType;
@ -39,9 +39,10 @@ class RequestContext {
String get hostname => io.headers.value(HttpHeaders.HOST);
final Map _injections = {};
Map _injectionsCache;
/// A [Map] of singletons injected via [inject]. *Read-only*.
Map get injections => new Map.unmodifiable(_injections);
Map get injections => _injectionsCache ??= new Map.unmodifiable(_injections);
/// The underlying [HttpRequest] instance underneath this context.
HttpRequest get io => _io;
@ -135,8 +136,13 @@ class RequestContext {
io.headers.value("X-Requested-With")?.trim()?.toLowerCase() ==
'xmlhttprequest';
/// Returns the file extension of the requested path, if any.
///
/// Includes the leading `.`, if there is one.
String get extension => _extensionCache ??= p.extension(uri.path);
/// Magically transforms an [HttpRequest] into a [RequestContext].
static Future<RequestContext> from(HttpRequest request, Angel app) async {
static Future<RequestContext> from(HttpRequest request, Angel app, String path) async {
RequestContext ctx = new RequestContext();
String override = request.method;
@ -150,6 +156,7 @@ class RequestContext {
ctx._contentType = request.headers.contentType;
ctx._override = override;
/*
// Faster way to get path
List<int> _path = [];
@ -175,6 +182,9 @@ class RequestContext {
ctx._path = new String.fromCharCodes(_path.take(lastSlash));
else
ctx._path = new String.fromCharCodes(_path);
*/
ctx._path = path;
ctx._io = request;
if (app.lazyParseBodies != true) {
@ -219,17 +229,19 @@ class RequestContext {
}
_injections[type] = value;
_injectionsCache = null;
}
/// Returns `true` if the client's `Accept` header indicates that the given [contentType] is considered a valid response.
///
/// You cannot provide a `null` [contentType].
/// If the `Accept` header's value is `*/*`, this method will always return `true`.
/// To ignore the wildcard (`*/*`), pass [strict] as `true`.
///
/// [contentType] can be either of the following:
/// * A [ContentType], in which case the `Accept` header will be compared against its `mimeType` property.
/// * Any other Dart value, in which case the `Accept` header will be compared against the result of a `toString()` call.
bool accepts(contentType) {
bool accepts(contentType, {bool strict: false}) {
var contentTypeString = contentType is ContentType
? contentType.mimeType
: contentType?.toString();
@ -242,7 +254,7 @@ class RequestContext {
if (_acceptHeaderCache == null)
return false;
else if (_acceptHeaderCache.contains('*/*'))
else if (strict != true && _acceptHeaderCache.contains('*/*'))
return true;
else
return _acceptHeaderCache.contains(contentTypeString);

View file

@ -6,6 +6,7 @@ import 'dart:io';
import 'package:angel_route/angel_route.dart';
import 'package:json_god/json_god.dart' as god;
import 'package:mime/mime.dart';
import 'package:pool/pool.dart';
import 'server.dart' show Angel;
import 'controller.dart';
import 'request_context.dart';
@ -337,6 +338,31 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
buffer.add(data);
}
/// Configure the response to write directly to the output stream, instead of buffering.
bool useStream() {
if (!_useStream) {
// If this is the first stream added to this response,
// then add headers, status code, etc.
io
..statusCode = statusCode
..cookies.addAll(cookies);
headers.forEach(io.headers.set);
willCloseItself = _useStream = _isClosed = true;
if (_correspondingRequest?.injections?.containsKey(Stopwatch) == true) {
(_correspondingRequest.injections[Stopwatch] as Stopwatch).stop();
}
if (_correspondingRequest?.injections?.containsKey(PoolResource) == true) {
(_correspondingRequest.injections[PoolResource] as PoolResource).release();
}
return true;
}
return false;
}
/// Adds a stream directly the underlying dart:[io] response.
///
/// This will also set [willCloseItself] to `true`, thus canceling out response finalizers.
@ -346,24 +372,10 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
@override
Future addStream(Stream<List<int>> stream) {
if (_isClosed && !_useStream) throw _closed();
bool firstStream = _useStream == false;
willCloseItself = _useStream = _isClosed = true;
if (_correspondingRequest?.injections?.containsKey(Stopwatch) == true) {
(_correspondingRequest.injections[Stopwatch] as Stopwatch).stop();
}
var firstStream = useStream();
Stream<List<int>> output = stream;
if (firstStream) {
// If this is the first stream added to this response,
// then add headers, status code, etc.
io
..statusCode = statusCode
..cookies.addAll(cookies);
headers.forEach(io.headers.set);
}
if (encoders.isNotEmpty && correspondingRequest != null) {
var allowedEncodings =
(correspondingRequest.headers[HttpHeaders.ACCEPT_ENCODING] ?? [])

View file

@ -64,6 +64,7 @@ class Routable extends Router {
/// Assigns a middleware to a name for convenience.
@override
registerMiddleware(String name, @checked RequestHandler middleware) =>
// ignore: deprecated_member_use
super.registerMiddleware(name, middleware);
/// Retrieves the service assigned to the given path.

View file

@ -1,16 +1,17 @@
library angel_framework.http.server;
import 'dart:async';
import 'dart:collection' show HashMap;
import 'dart:convert';
import 'dart:io';
import 'package:angel_http_exception/angel_http_exception.dart';
import 'package:angel_route/angel_route.dart' hide Extensible;
import 'package:charcode/charcode.dart';
import 'package:angel_route/angel_route.dart';
import 'package:combinator/combinator.dart';
export 'package:container/container.dart';
import 'package:flatten/flatten.dart';
import 'package:json_god/json_god.dart' as god;
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:pool/pool.dart';
import 'package:tuple/tuple.dart';
import 'angel_base.dart';
import 'controller.dart';
@ -30,11 +31,13 @@ typedef Future AngelConfigurer(Angel app);
/// A powerful real-time/REST/MVC server class.
class Angel extends AngelBase {
final List<Angel> _children = [];
final Map<String, Tuple3<List, Map, Match>> handlerCache = {};
final Map<String, Tuple3<List, Map, ParseResult<Map<String, String>>>>
handlerCache = new HashMap();
Router _flattened;
bool _isProduction = false;
bool _isProduction;
Angel _parent;
Pool _pool;
StreamSubscription<HttpRequest> _sub;
ServerGenerator _serverGenerator = HttpServer.bind;
@ -75,7 +78,8 @@ class Angel extends AngelBase {
/// This value is memoized the first time you call it, so do not change environment
/// configuration at runtime!
bool get isProduction {
return _isProduction ??= (Platform.environment['ANGEL_ENV'] == 'production');
return _isProduction ??=
(Platform.environment['ANGEL_ENV'] == 'production');
}
/// The function used to bind this instance to an HTTP server.
@ -237,7 +241,7 @@ class Angel extends AngelBase {
String tab: ' ',
bool showMatchers: false}) {
if (isProduction) {
if (_flattened == null) _flattened = flatten(this);
_flattened ??= flatten(this);
_flattened.dumpTree(
callback: callback,
@ -246,8 +250,7 @@ class Angel extends AngelBase {
: (isProduction
? 'Dumping flattened route tree:'
: 'Dumping route tree:'),
tab: tab ?? ' ',
showMatchers: showMatchers == true);
tab: tab ?? ' ');
} else {
super.dumpTree(
callback: callback,
@ -256,8 +259,7 @@ class Angel extends AngelBase {
: (isProduction
? 'Dumping flattened route tree:'
: 'Dumping route tree:'),
tab: tab ?? ' ',
showMatchers: showMatchers == true);
tab: tab ?? ' ');
}
}
@ -308,7 +310,6 @@ class Angel extends AngelBase {
/// Runs some [handler]. Returns `true` if request execution should continue.
Future<bool> executeHandler(
handler, RequestContext req, ResponseContext res) async {
if (handler == null) return false;
var result = await getHandlerResult(handler, req, res);
if (result == null)
@ -325,8 +326,11 @@ class Angel extends AngelBase {
}
Future<RequestContext> createRequestContext(HttpRequest request) {
return RequestContext.from(request, this).then((req) {
_injections.forEach(req.inject);
var path = request.uri.path.replaceAll(_straySlashes, '');
if (path.length == 0) path = '/';
return RequestContext.from(request, this, path).then((req) async {
if (_pool != null) req.inject(PoolResource, await _pool.request());
if (_injections.isNotEmpty) _injections.forEach(req.inject);
return req;
});
}
@ -378,53 +382,32 @@ class Angel extends AngelBase {
Future handleRequest(HttpRequest request) async {
var req = await createRequestContext(request);
var res = await createResponseContext(request.response, req);
var zoneSpec = await createZoneForRequest(request, req, res);
var zone = Zone.current.fork(specification: zoneSpec);
return zone.runGuarded(() async {
String requestedUrl;
try {
var path = req.path;
if (path == '/') path = '';
// Faster way to get path
List<int> _path = request.uri.path.codeUnits;
// Remove trailing slashes
int lastSlash = -1;
for (int i = _path.length - 1; i >= 0; i--) {
if (_path[i] == $slash)
lastSlash = i;
else
break;
}
if (lastSlash > -1)
requestedUrl = new String.fromCharCodes(_path.take(lastSlash));
else
requestedUrl = new String.fromCharCodes(_path);
if (requestedUrl.isEmpty) requestedUrl = '/';
Tuple3<List, Map, Match> resolveTuple() {
Router r = isProduction ? (_flattened ??= flatten(this)) : this;
Tuple3<List, Map, ParseResult<Map<String, String>>> resolveTuple() {
Router r = _flattened ?? this;
var resolved =
r.resolveAll(requestedUrl, requestedUrl, method: req.method);
r.resolveAbsolute(path, method: req.method, strip: false);
return new Tuple3(
new MiddlewarePipeline(resolved).handlers,
resolved.fold<Map>({}, (out, r) => out..addAll(r.allParams)),
resolved.isEmpty ? null : resolved.first.route.match(requestedUrl),
resolved.isEmpty ? null : resolved.first.parseResult,
);
}
var cacheKey = req.method + path;
var tuple = isProduction
? handlerCache.putIfAbsent(
'${req.method}:$requestedUrl', resolveTuple)
? handlerCache.putIfAbsent(cacheKey, resolveTuple)
: resolveTuple();
req.inject(Zone, zone);
req.inject(ZoneSpecification, zoneSpec);
//req.inject(Zone, zone);
//req.inject(ZoneSpecification, zoneSpec);
req.params.addAll(tuple.item2);
req.inject(Match, tuple.item3);
req.inject(ParseResult, tuple.item3);
if (logger != null) req.inject(Stopwatch, new Stopwatch()..start());
@ -453,7 +436,15 @@ class Angel extends AngelBase {
ignoreFinalizers: true,
);
}
}).catchError((error, stackTrace) {
} on FormatException catch (error, stackTrace) {
var e = new AngelHttpException.badRequest(message: error.message);
if (logger != null) {
logger.severe(e.message ?? e.toString(), error, stackTrace);
}
return await handleAngelHttpException(e, stackTrace, req, res, request);
} catch (error, stackTrace) {
var e = new AngelHttpException(error,
stackTrace: stackTrace, message: error?.toString());
@ -461,13 +452,10 @@ class Angel extends AngelBase {
logger.severe(e.message ?? e.toString(), error, stackTrace);
}
return handleAngelHttpException(e, stackTrace, req, res, request);
}).whenComplete(() {
scheduleMicrotask(() {
req.close();
return await handleAngelHttpException(e, stackTrace, req, res, request);
} finally {
res.dispose();
});
});
}
}
/// Runs several optimizations, *if* [isProduction] is `true`.
@ -495,7 +483,7 @@ class Angel extends AngelBase {
});
}
if (_flattened == null) _flattened = flatten(this);
_flattened ??= flatten(this);
_walk(_flattened);
@ -528,9 +516,8 @@ class Angel extends AngelBase {
Future sendResponse(
HttpRequest request, RequestContext req, ResponseContext res,
{bool ignoreFinalizers: false}) {
if (res.willCloseItself) {
return new Future.value();
} else {
if (res.willCloseItself) return new Future.value();
Future finalizers = ignoreFinalizers == true
? new Future.value()
: responseFinalizers.fold<Future>(
@ -549,13 +536,14 @@ class Angel extends AngelBase {
if (res.encoders.isNotEmpty) {
var allowedEncodings =
(req.headers[HttpHeaders.ACCEPT_ENCODING] ?? []).map((str) {
req.headers[HttpHeaders.ACCEPT_ENCODING]?.map((str) {
// Ignore quality specifications in accept-encoding
// ex. gzip;q=0.8
if (!str.contains(';')) return str;
return str.split(';')[0];
});
if (allowedEncodings != null) {
for (var encodingName in allowedEncodings) {
Converter<List<int>, List<int>> encoder;
String key = encodingName;
@ -574,13 +562,20 @@ class Angel extends AngelBase {
}
}
}
}
request.response
..statusCode = res.statusCode
..cookies.addAll(res.cookies)
..add(outputBuffer);
return finalizers.then((_) => request.response.close()).then((_) {
return finalizers.then((_) async {
request.response.close();
if (req.injections.containsKey(PoolResource)) {
req.injections[PoolResource].release();
}
if (logger != null) {
var sw = req.grab<Stopwatch>(Stopwatch);
@ -592,6 +587,13 @@ class Angel extends AngelBase {
}
});
}
/// Limits the maximum number of requests to be handled concurrently by this instance.
///
/// You can optionally provide a [timeout] to limit the amount of time a request can be
/// handled before.
void throttle(int maxConcurrentRequests, {Duration timeout}) {
_pool = new Pool(maxConcurrentRequests, timeout: timeout);
}
/// Applies an [AngelConfigurer] to this instance.
@ -651,8 +653,10 @@ class Angel extends AngelBase {
/// Default constructor. ;)
Angel() : super() {
bootstrapContainer();
createZoneForRequest = defaultZoneCreator;
}
createZoneForRequest = (request, req, res) async {
Future<ZoneSpecification> defaultZoneCreator(request, req, res) async {
return new ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
if (logger != null) {
@ -662,7 +666,6 @@ class Angel extends AngelBase {
}
},
);
};
}
/// An instance mounted on a server started by the [serverGenerator].

View file

@ -35,6 +35,11 @@ class Providers {
@override
bool operator ==(other) => other is Providers && other.via == via;
@override
String toString() {
return 'via:$via';
}
}
/// A front-facing interface that can present data to and operate on data on behalf of the user.

View file

@ -22,7 +22,8 @@ class AngelMetrics extends Angel {
res.contentType = ContentType.HTML;
var rows = stats.all.map((stat) {
return '''<tr>
return '''
<tr>
<td>${stat.name}</td>
<td>${stat.iterations}</td>
<td>${stat.sum}ms</td>
@ -107,6 +108,13 @@ class AngelMetrics extends Angel {
() => super.createResponseContext(response, correspondingRequest));
}
@override
Iterable<RoutingResult> resolveAll(String absolute, String relative,
{String method: 'GET', bool strip: true}) {
return stats.resolveAll
.run(() => super.resolveAll(absolute, relative, method: method, strip: strip));
}
@override
Future handleRequest(HttpRequest request) {
return stats.handleRequest.run(() async {
@ -146,6 +154,7 @@ class AngelMetricsStats {
all = [
createRequestContext,
createResponseContext,
resolveAll,
executeHandler,
getHandlerResult,
runContained,
@ -156,6 +165,7 @@ class AngelMetricsStats {
final Stats createRequestContext = new Stats('createRequestContext');
final Stats createResponseContext = new Stats('createResponseContext');
final Stats resolveAll = new Stats('resolveAll');
final Stats handleRequest = new Stats('handleRequest');
final Stats executeHandler = new Stats('executeHandler');
final Stats getHandlerResult = new Stats('getHandlerResult');

View file

@ -1,6 +1,8 @@
import 'dart:async';
import 'dart:mirrors';
final RegExp straySlashes = new RegExp(r'(^/+)|(/+$)');
matchingAnnotation(List<InstanceMirror> metadata, Type T) {
for (InstanceMirror metaDatum in metadata) {
if (metaDatum.hasReflectee) {

View file

@ -5,20 +5,39 @@ import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/metrics.dart';
main() {
main() async {
var isolates = <Isolate>[];
for (int i = 0; i < Platform.numberOfProcessors; i++) {
Isolate.spawn(start, i + 1);
isolates.add(await Isolate.spawn(start, i + 1));
}
start(0);
await Future.wait(isolates.map((i) {
var rcv = new ReceivePort();
i.addOnExitListener(rcv.sendPort);
return rcv.first;
}));
//start(0);
}
void start(int id) {
var app = new AngelMetrics.custom(startShared)
..lazyParseBodies = true
..get('/', (req, res) => res.write('Hello, world!'));
var app = new Angel.custom(startShared)..lazyParseBodies = true;
if (true) {
app.get('/', (req, ResponseContext res) {
res.willCloseItself = true;
res.io
..write('Hello, world!')
..close();
return false;
});
} else {
app.get('/', (req, ResponseContext res) {
res.useStream();
res.write('Hello, world!');
});
}
var oldHandler = app.errorHandler;
app.errorHandler = (e, req, res) {

View file

@ -1,5 +1,5 @@
name: angel_framework
version: 1.1.0-alpha+8
version: 1.1.0-alpha+9
description: A high-powered HTTP server with DI, routing and more.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_framework
@ -8,17 +8,17 @@ environment:
dependencies:
angel_http_exception: ^1.0.0
angel_model: ^1.0.0
angel_route: ">=1.0.5 <2.0.0"
angel_route: ^2.0.0
body_parser: ^1.0.0-dev
charcode: ^1.0.0
container: ^0.1.2
flatten: ^1.0.0
json_god: ^2.0.0-beta
logging: ">=0.11.3 <1.0.0"
matcher: ^0.12.0
merge_map: ^1.0.0
meta: ^1.0.0
mime: ^0.9.3
pool: ^1.0.0
random_string: ^0.0.1
tuple: ^1.0.0
dev_dependencies:

View file

@ -34,6 +34,12 @@ main() {
expect(req.accepts('text/html'), isTrue);
});
test('strict', () async {
var req = await acceptContentTypes(['text/html', "*/*"]);
expect(req.accepts(ContentType.HTML), isTrue);
expect(req.accepts(ContentType.JSON, strict: true), isFalse);
});
group('disallow null', () {
RequestContext req;

View file

@ -4,6 +4,7 @@ import 'controller_test.dart' as controller;
import 'di_test.dart' as di;
import 'encoders_buffer_test.dart' as encoders_buffer;
import 'exception_test.dart' as exception;
import 'extension_test.dart' as extension;
import 'general_test.dart' as general;
import 'hooked_test.dart' as hooked;
import 'parameter_meta_test.dart' as parameter_meta;
@ -27,6 +28,7 @@ main() {
group('di', di.main);
group('encoders_buffer', encoders_buffer.main);
group('exception', exception.main);
group('extension', extension.main);
group('general', general.main);
group('hooked', hooked.main);
group('parameter_meta', parameter_meta.main);

View file

@ -1,7 +1,6 @@
import 'dart:convert';
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_route/angel_route.dart';
import 'package:http/http.dart' as http;
import 'package:test/test.dart';
import 'common.dart';

29
test/extension_test.dart Normal file
View file

@ -0,0 +1,29 @@
import 'dart:async';
import 'package:angel_framework/angel_framework.dart';
import 'package:mock_request/mock_request.dart';
import 'package:test/test.dart';
final Uri ENDPOINT = Uri.parse('http://example.com');
main() {
test('single extension', () async {
var req = await makeRequest('foo.js');
expect(req.extension, '.js');
});
test('multiple extensions', () async {
var req = await makeRequest('foo.min.js');
expect(req.extension, '.js');
});
test('no extension', () async {
var req = await makeRequest('foo');
expect(req.extension, '');
});
}
Future<RequestContext> makeRequest(String path) {
var rq = new MockHttpRequest('GET', ENDPOINT.replace(path: path))..close();
var app = new Angel();
return app.createRequestContext(rq);
}

View file

@ -49,8 +49,8 @@ main() {
ted = nested.post('/ted/:route', (RequestContext req, res) {
print('Params: ${req.params}');
print(
'Path: ${ted.path}, matcher: ${ted.matcher.pattern}, uri: ${req.path}');
print('Path: ${ted.path}, uri: ${req.path}');
print('matcher: ${ted.parser}');
return req.params;
});
@ -65,7 +65,7 @@ main() {
app
.get('/greet/:name',
(RequestContext req, res) async => "Hello ${req.params['name']}")
.as('Named routes');
.name = 'Named routes';
app.get('/named', (req, ResponseContext res) async {
res.redirectTo('Named routes', {'name': 'tests'});
});
@ -86,7 +86,9 @@ main() {
};
}
app.chain(write('a')).chain([write('b'), write('c')]).get('/chained', () => false);
app
.chain(write('a'))
.chain([write('b'), write('c')]).get('/chained', () => false);
app.use('MJ');
@ -165,7 +167,7 @@ main() {
});
test('Can name routes', () {
Route foo = new Route('/framework/:id', name: 'frm');
Route foo = app.get('/framework/:id', [])..name = 'frm';
print('Foo: $foo');
String uri = foo.makeUri({'id': 'angel'});
print(uri);