+9
This commit is contained in:
parent
2c846ae449
commit
2d87631e8d
19 changed files with 808 additions and 586 deletions
|
@ -26,14 +26,14 @@
|
||||||
<entry key="angel_route">
|
<entry key="angel_route">
|
||||||
<value>
|
<value>
|
||||||
<list>
|
<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>
|
</list>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</entry>
|
||||||
<entry key="args">
|
<entry key="args">
|
||||||
<value>
|
<value>
|
||||||
<list>
|
<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>
|
</list>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</entry>
|
||||||
|
@ -86,6 +86,13 @@
|
||||||
</list>
|
</list>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</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">
|
<entry key="container">
|
||||||
<value>
|
<value>
|
||||||
<list>
|
<list>
|
||||||
|
@ -114,13 +121,6 @@
|
||||||
</list>
|
</list>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</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">
|
<entry key="front_end">
|
||||||
<value>
|
<value>
|
||||||
<list>
|
<list>
|
||||||
|
@ -271,7 +271,7 @@
|
||||||
<entry key="path">
|
<entry key="path">
|
||||||
<value>
|
<value>
|
||||||
<list>
|
<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>
|
</list>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</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/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_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_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/angel_route-2.0.3+2/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/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/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/barback-0.15.2+13/lib" />
|
||||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/body_parser-1.0.3/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/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/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/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/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/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/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/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/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/glob-1.1.5/lib" />
|
||||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/html-0.13.2/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/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_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/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/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/pool-1.3.3/lib" />
|
||||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.3.2/lib" />
|
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.3.2/lib" />
|
||||||
|
|
|
@ -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>
|
8
.idea/runConfigurations/tests_in_framework.xml
Normal file
8
.idea/runConfigurations/tests_in_framework.xml
Normal 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
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -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
|
# 1.1.0-alpha+8
|
||||||
* Added an `autoIdAndDateFields` flag to `MapService`. Finally.
|
* Added an `autoIdAndDateFields` flag to `MapService`. Finally.
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:mirrors';
|
import 'dart:mirrors';
|
||||||
import 'package:body_parser/body_parser.dart';
|
import 'package:body_parser/body_parser.dart';
|
||||||
import 'package:charcode/charcode.dart';
|
import 'package:path/path.dart' as p;
|
||||||
import 'metadata.dart';
|
import 'metadata.dart';
|
||||||
import 'response_context.dart';
|
import 'response_context.dart';
|
||||||
import 'routable.dart';
|
import 'routable.dart';
|
||||||
|
@ -13,7 +13,7 @@ part 'injection.dart';
|
||||||
|
|
||||||
/// A convenience wrapper around an incoming HTTP request.
|
/// A convenience wrapper around an incoming HTTP request.
|
||||||
class RequestContext {
|
class RequestContext {
|
||||||
String _acceptHeaderCache;
|
String _acceptHeaderCache, _extensionCache;
|
||||||
bool _acceptsAllCache;
|
bool _acceptsAllCache;
|
||||||
BodyParseResult _body;
|
BodyParseResult _body;
|
||||||
ContentType _contentType;
|
ContentType _contentType;
|
||||||
|
@ -39,9 +39,10 @@ class RequestContext {
|
||||||
String get hostname => io.headers.value(HttpHeaders.HOST);
|
String get hostname => io.headers.value(HttpHeaders.HOST);
|
||||||
|
|
||||||
final Map _injections = {};
|
final Map _injections = {};
|
||||||
|
Map _injectionsCache;
|
||||||
|
|
||||||
/// A [Map] of singletons injected via [inject]. *Read-only*.
|
/// 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.
|
/// The underlying [HttpRequest] instance underneath this context.
|
||||||
HttpRequest get io => _io;
|
HttpRequest get io => _io;
|
||||||
|
@ -135,8 +136,13 @@ class RequestContext {
|
||||||
io.headers.value("X-Requested-With")?.trim()?.toLowerCase() ==
|
io.headers.value("X-Requested-With")?.trim()?.toLowerCase() ==
|
||||||
'xmlhttprequest';
|
'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].
|
/// 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();
|
RequestContext ctx = new RequestContext();
|
||||||
|
|
||||||
String override = request.method;
|
String override = request.method;
|
||||||
|
@ -150,6 +156,7 @@ class RequestContext {
|
||||||
ctx._contentType = request.headers.contentType;
|
ctx._contentType = request.headers.contentType;
|
||||||
ctx._override = override;
|
ctx._override = override;
|
||||||
|
|
||||||
|
/*
|
||||||
// Faster way to get path
|
// Faster way to get path
|
||||||
List<int> _path = [];
|
List<int> _path = [];
|
||||||
|
|
||||||
|
@ -175,6 +182,9 @@ class RequestContext {
|
||||||
ctx._path = new String.fromCharCodes(_path.take(lastSlash));
|
ctx._path = new String.fromCharCodes(_path.take(lastSlash));
|
||||||
else
|
else
|
||||||
ctx._path = new String.fromCharCodes(_path);
|
ctx._path = new String.fromCharCodes(_path);
|
||||||
|
*/
|
||||||
|
|
||||||
|
ctx._path = path;
|
||||||
ctx._io = request;
|
ctx._io = request;
|
||||||
|
|
||||||
if (app.lazyParseBodies != true) {
|
if (app.lazyParseBodies != true) {
|
||||||
|
@ -219,17 +229,19 @@ class RequestContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
_injections[type] = value;
|
_injections[type] = value;
|
||||||
|
_injectionsCache = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the client's `Accept` header indicates that the given [contentType] is considered a valid response.
|
/// Returns `true` if the client's `Accept` header indicates that the given [contentType] is considered a valid response.
|
||||||
///
|
///
|
||||||
/// You cannot provide a `null` [contentType].
|
/// You cannot provide a `null` [contentType].
|
||||||
/// If the `Accept` header's value is `*/*`, this method will always return `true`.
|
/// 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:
|
/// [contentType] can be either of the following:
|
||||||
/// * A [ContentType], in which case the `Accept` header will be compared against its `mimeType` property.
|
/// * 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.
|
/// * 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
|
var contentTypeString = contentType is ContentType
|
||||||
? contentType.mimeType
|
? contentType.mimeType
|
||||||
: contentType?.toString();
|
: contentType?.toString();
|
||||||
|
@ -242,7 +254,7 @@ class RequestContext {
|
||||||
|
|
||||||
if (_acceptHeaderCache == null)
|
if (_acceptHeaderCache == null)
|
||||||
return false;
|
return false;
|
||||||
else if (_acceptHeaderCache.contains('*/*'))
|
else if (strict != true && _acceptHeaderCache.contains('*/*'))
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
return _acceptHeaderCache.contains(contentTypeString);
|
return _acceptHeaderCache.contains(contentTypeString);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'dart:io';
|
||||||
import 'package:angel_route/angel_route.dart';
|
import 'package:angel_route/angel_route.dart';
|
||||||
import 'package:json_god/json_god.dart' as god;
|
import 'package:json_god/json_god.dart' as god;
|
||||||
import 'package:mime/mime.dart';
|
import 'package:mime/mime.dart';
|
||||||
|
import 'package:pool/pool.dart';
|
||||||
import 'server.dart' show Angel;
|
import 'server.dart' show Angel;
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
import 'request_context.dart';
|
import 'request_context.dart';
|
||||||
|
@ -337,6 +338,31 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
|
||||||
buffer.add(data);
|
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.
|
/// Adds a stream directly the underlying dart:[io] response.
|
||||||
///
|
///
|
||||||
/// This will also set [willCloseItself] to `true`, thus canceling out response finalizers.
|
/// This will also set [willCloseItself] to `true`, thus canceling out response finalizers.
|
||||||
|
@ -346,24 +372,10 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
|
||||||
@override
|
@override
|
||||||
Future addStream(Stream<List<int>> stream) {
|
Future addStream(Stream<List<int>> stream) {
|
||||||
if (_isClosed && !_useStream) throw _closed();
|
if (_isClosed && !_useStream) throw _closed();
|
||||||
bool firstStream = _useStream == false;
|
var firstStream = useStream();
|
||||||
willCloseItself = _useStream = _isClosed = true;
|
|
||||||
|
|
||||||
if (_correspondingRequest?.injections?.containsKey(Stopwatch) == true) {
|
|
||||||
(_correspondingRequest.injections[Stopwatch] as Stopwatch).stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<List<int>> output = stream;
|
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) {
|
if (encoders.isNotEmpty && correspondingRequest != null) {
|
||||||
var allowedEncodings =
|
var allowedEncodings =
|
||||||
(correspondingRequest.headers[HttpHeaders.ACCEPT_ENCODING] ?? [])
|
(correspondingRequest.headers[HttpHeaders.ACCEPT_ENCODING] ?? [])
|
||||||
|
|
|
@ -64,6 +64,7 @@ class Routable extends Router {
|
||||||
/// Assigns a middleware to a name for convenience.
|
/// Assigns a middleware to a name for convenience.
|
||||||
@override
|
@override
|
||||||
registerMiddleware(String name, @checked RequestHandler middleware) =>
|
registerMiddleware(String name, @checked RequestHandler middleware) =>
|
||||||
|
// ignore: deprecated_member_use
|
||||||
super.registerMiddleware(name, middleware);
|
super.registerMiddleware(name, middleware);
|
||||||
|
|
||||||
/// Retrieves the service assigned to the given path.
|
/// Retrieves the service assigned to the given path.
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
library angel_framework.http.server;
|
library angel_framework.http.server;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:collection' show HashMap;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:angel_http_exception/angel_http_exception.dart';
|
import 'package:angel_http_exception/angel_http_exception.dart';
|
||||||
import 'package:angel_route/angel_route.dart' hide Extensible;
|
import 'package:angel_route/angel_route.dart';
|
||||||
import 'package:charcode/charcode.dart';
|
import 'package:combinator/combinator.dart';
|
||||||
export 'package:container/container.dart';
|
export 'package:container/container.dart';
|
||||||
import 'package:flatten/flatten.dart';
|
|
||||||
import 'package:json_god/json_god.dart' as god;
|
import 'package:json_god/json_god.dart' as god;
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:pool/pool.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
import 'angel_base.dart';
|
import 'angel_base.dart';
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
@ -30,11 +31,13 @@ typedef Future AngelConfigurer(Angel app);
|
||||||
/// A powerful real-time/REST/MVC server class.
|
/// A powerful real-time/REST/MVC server class.
|
||||||
class Angel extends AngelBase {
|
class Angel extends AngelBase {
|
||||||
final List<Angel> _children = [];
|
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;
|
Router _flattened;
|
||||||
bool _isProduction = false;
|
bool _isProduction;
|
||||||
Angel _parent;
|
Angel _parent;
|
||||||
|
Pool _pool;
|
||||||
StreamSubscription<HttpRequest> _sub;
|
StreamSubscription<HttpRequest> _sub;
|
||||||
ServerGenerator _serverGenerator = HttpServer.bind;
|
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
|
/// This value is memoized the first time you call it, so do not change environment
|
||||||
/// configuration at runtime!
|
/// configuration at runtime!
|
||||||
bool get isProduction {
|
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.
|
/// The function used to bind this instance to an HTTP server.
|
||||||
|
@ -237,7 +241,7 @@ class Angel extends AngelBase {
|
||||||
String tab: ' ',
|
String tab: ' ',
|
||||||
bool showMatchers: false}) {
|
bool showMatchers: false}) {
|
||||||
if (isProduction) {
|
if (isProduction) {
|
||||||
if (_flattened == null) _flattened = flatten(this);
|
_flattened ??= flatten(this);
|
||||||
|
|
||||||
_flattened.dumpTree(
|
_flattened.dumpTree(
|
||||||
callback: callback,
|
callback: callback,
|
||||||
|
@ -246,8 +250,7 @@ class Angel extends AngelBase {
|
||||||
: (isProduction
|
: (isProduction
|
||||||
? 'Dumping flattened route tree:'
|
? 'Dumping flattened route tree:'
|
||||||
: 'Dumping route tree:'),
|
: 'Dumping route tree:'),
|
||||||
tab: tab ?? ' ',
|
tab: tab ?? ' ');
|
||||||
showMatchers: showMatchers == true);
|
|
||||||
} else {
|
} else {
|
||||||
super.dumpTree(
|
super.dumpTree(
|
||||||
callback: callback,
|
callback: callback,
|
||||||
|
@ -256,8 +259,7 @@ class Angel extends AngelBase {
|
||||||
: (isProduction
|
: (isProduction
|
||||||
? 'Dumping flattened route tree:'
|
? 'Dumping flattened route tree:'
|
||||||
: 'Dumping route tree:'),
|
: 'Dumping route tree:'),
|
||||||
tab: tab ?? ' ',
|
tab: tab ?? ' ');
|
||||||
showMatchers: showMatchers == true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +310,6 @@ class Angel extends AngelBase {
|
||||||
/// Runs some [handler]. Returns `true` if request execution should continue.
|
/// Runs some [handler]. Returns `true` if request execution should continue.
|
||||||
Future<bool> executeHandler(
|
Future<bool> executeHandler(
|
||||||
handler, RequestContext req, ResponseContext res) async {
|
handler, RequestContext req, ResponseContext res) async {
|
||||||
if (handler == null) return false;
|
|
||||||
var result = await getHandlerResult(handler, req, res);
|
var result = await getHandlerResult(handler, req, res);
|
||||||
|
|
||||||
if (result == null)
|
if (result == null)
|
||||||
|
@ -325,8 +326,11 @@ class Angel extends AngelBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RequestContext> createRequestContext(HttpRequest request) {
|
Future<RequestContext> createRequestContext(HttpRequest request) {
|
||||||
return RequestContext.from(request, this).then((req) {
|
var path = request.uri.path.replaceAll(_straySlashes, '');
|
||||||
_injections.forEach(req.inject);
|
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;
|
return req;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -378,53 +382,32 @@ class Angel extends AngelBase {
|
||||||
Future handleRequest(HttpRequest request) async {
|
Future handleRequest(HttpRequest request) async {
|
||||||
var req = await createRequestContext(request);
|
var req = await createRequestContext(request);
|
||||||
var res = await createResponseContext(request.response, req);
|
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 {
|
try {
|
||||||
String requestedUrl;
|
var path = req.path;
|
||||||
|
if (path == '/') path = '';
|
||||||
|
|
||||||
// Faster way to get path
|
Tuple3<List, Map, ParseResult<Map<String, String>>> resolveTuple() {
|
||||||
List<int> _path = request.uri.path.codeUnits;
|
Router r = _flattened ?? this;
|
||||||
|
|
||||||
// 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;
|
|
||||||
var resolved =
|
var resolved =
|
||||||
r.resolveAll(requestedUrl, requestedUrl, method: req.method);
|
r.resolveAbsolute(path, method: req.method, strip: false);
|
||||||
|
|
||||||
return new Tuple3(
|
return new Tuple3(
|
||||||
new MiddlewarePipeline(resolved).handlers,
|
new MiddlewarePipeline(resolved).handlers,
|
||||||
resolved.fold<Map>({}, (out, r) => out..addAll(r.allParams)),
|
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
|
var tuple = isProduction
|
||||||
? handlerCache.putIfAbsent(
|
? handlerCache.putIfAbsent(cacheKey, resolveTuple)
|
||||||
'${req.method}:$requestedUrl', resolveTuple)
|
|
||||||
: resolveTuple();
|
: resolveTuple();
|
||||||
|
|
||||||
req.inject(Zone, zone);
|
//req.inject(Zone, zone);
|
||||||
req.inject(ZoneSpecification, zoneSpec);
|
//req.inject(ZoneSpecification, zoneSpec);
|
||||||
req.params.addAll(tuple.item2);
|
req.params.addAll(tuple.item2);
|
||||||
req.inject(Match, tuple.item3);
|
req.inject(ParseResult, tuple.item3);
|
||||||
|
|
||||||
if (logger != null) req.inject(Stopwatch, new Stopwatch()..start());
|
if (logger != null) req.inject(Stopwatch, new Stopwatch()..start());
|
||||||
|
|
||||||
|
@ -453,7 +436,15 @@ class Angel extends AngelBase {
|
||||||
ignoreFinalizers: true,
|
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,
|
var e = new AngelHttpException(error,
|
||||||
stackTrace: stackTrace, message: error?.toString());
|
stackTrace: stackTrace, message: error?.toString());
|
||||||
|
|
||||||
|
@ -461,13 +452,10 @@ class Angel extends AngelBase {
|
||||||
logger.severe(e.message ?? e.toString(), error, stackTrace);
|
logger.severe(e.message ?? e.toString(), error, stackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleAngelHttpException(e, stackTrace, req, res, request);
|
return await handleAngelHttpException(e, stackTrace, req, res, request);
|
||||||
}).whenComplete(() {
|
} finally {
|
||||||
scheduleMicrotask(() {
|
|
||||||
req.close();
|
|
||||||
res.dispose();
|
res.dispose();
|
||||||
});
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs several optimizations, *if* [isProduction] is `true`.
|
/// 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);
|
_walk(_flattened);
|
||||||
|
|
||||||
|
@ -528,9 +516,8 @@ class Angel extends AngelBase {
|
||||||
Future sendResponse(
|
Future sendResponse(
|
||||||
HttpRequest request, RequestContext req, ResponseContext res,
|
HttpRequest request, RequestContext req, ResponseContext res,
|
||||||
{bool ignoreFinalizers: false}) {
|
{bool ignoreFinalizers: false}) {
|
||||||
if (res.willCloseItself) {
|
if (res.willCloseItself) return new Future.value();
|
||||||
return new Future.value();
|
|
||||||
} else {
|
|
||||||
Future finalizers = ignoreFinalizers == true
|
Future finalizers = ignoreFinalizers == true
|
||||||
? new Future.value()
|
? new Future.value()
|
||||||
: responseFinalizers.fold<Future>(
|
: responseFinalizers.fold<Future>(
|
||||||
|
@ -549,13 +536,14 @@ class Angel extends AngelBase {
|
||||||
|
|
||||||
if (res.encoders.isNotEmpty) {
|
if (res.encoders.isNotEmpty) {
|
||||||
var allowedEncodings =
|
var allowedEncodings =
|
||||||
(req.headers[HttpHeaders.ACCEPT_ENCODING] ?? []).map((str) {
|
req.headers[HttpHeaders.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];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (allowedEncodings != null) {
|
||||||
for (var encodingName in allowedEncodings) {
|
for (var encodingName in allowedEncodings) {
|
||||||
Converter<List<int>, List<int>> encoder;
|
Converter<List<int>, List<int>> encoder;
|
||||||
String key = encodingName;
|
String key = encodingName;
|
||||||
|
@ -574,13 +562,20 @@ class Angel extends AngelBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
request.response
|
request.response
|
||||||
..statusCode = res.statusCode
|
..statusCode = res.statusCode
|
||||||
..cookies.addAll(res.cookies)
|
..cookies.addAll(res.cookies)
|
||||||
..add(outputBuffer);
|
..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) {
|
if (logger != null) {
|
||||||
var sw = req.grab<Stopwatch>(Stopwatch);
|
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.
|
/// Applies an [AngelConfigurer] to this instance.
|
||||||
|
@ -651,8 +653,10 @@ class Angel extends AngelBase {
|
||||||
/// Default constructor. ;)
|
/// Default constructor. ;)
|
||||||
Angel() : super() {
|
Angel() : super() {
|
||||||
bootstrapContainer();
|
bootstrapContainer();
|
||||||
|
createZoneForRequest = defaultZoneCreator;
|
||||||
|
}
|
||||||
|
|
||||||
createZoneForRequest = (request, req, res) async {
|
Future<ZoneSpecification> defaultZoneCreator(request, req, res) async {
|
||||||
return new ZoneSpecification(
|
return new ZoneSpecification(
|
||||||
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
|
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
|
||||||
if (logger != null) {
|
if (logger != null) {
|
||||||
|
@ -662,7 +666,6 @@ class Angel extends AngelBase {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An instance mounted on a server started by the [serverGenerator].
|
/// An instance mounted on a server started by the [serverGenerator].
|
||||||
|
|
|
@ -35,6 +35,11 @@ class Providers {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) => other is Providers && other.via == via;
|
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.
|
/// A front-facing interface that can present data to and operate on data on behalf of the user.
|
||||||
|
|
|
@ -22,7 +22,8 @@ class AngelMetrics extends Angel {
|
||||||
res.contentType = ContentType.HTML;
|
res.contentType = ContentType.HTML;
|
||||||
|
|
||||||
var rows = stats.all.map((stat) {
|
var rows = stats.all.map((stat) {
|
||||||
return '''<tr>
|
return '''
|
||||||
|
<tr>
|
||||||
<td>${stat.name}</td>
|
<td>${stat.name}</td>
|
||||||
<td>${stat.iterations}</td>
|
<td>${stat.iterations}</td>
|
||||||
<td>${stat.sum}ms</td>
|
<td>${stat.sum}ms</td>
|
||||||
|
@ -107,6 +108,13 @@ class AngelMetrics extends Angel {
|
||||||
() => super.createResponseContext(response, correspondingRequest));
|
() => 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
|
@override
|
||||||
Future handleRequest(HttpRequest request) {
|
Future handleRequest(HttpRequest request) {
|
||||||
return stats.handleRequest.run(() async {
|
return stats.handleRequest.run(() async {
|
||||||
|
@ -146,6 +154,7 @@ class AngelMetricsStats {
|
||||||
all = [
|
all = [
|
||||||
createRequestContext,
|
createRequestContext,
|
||||||
createResponseContext,
|
createResponseContext,
|
||||||
|
resolveAll,
|
||||||
executeHandler,
|
executeHandler,
|
||||||
getHandlerResult,
|
getHandlerResult,
|
||||||
runContained,
|
runContained,
|
||||||
|
@ -156,6 +165,7 @@ class AngelMetricsStats {
|
||||||
|
|
||||||
final Stats createRequestContext = new Stats('createRequestContext');
|
final Stats createRequestContext = new Stats('createRequestContext');
|
||||||
final Stats createResponseContext = new Stats('createResponseContext');
|
final Stats createResponseContext = new Stats('createResponseContext');
|
||||||
|
final Stats resolveAll = new Stats('resolveAll');
|
||||||
final Stats handleRequest = new Stats('handleRequest');
|
final Stats handleRequest = new Stats('handleRequest');
|
||||||
final Stats executeHandler = new Stats('executeHandler');
|
final Stats executeHandler = new Stats('executeHandler');
|
||||||
final Stats getHandlerResult = new Stats('getHandlerResult');
|
final Stats getHandlerResult = new Stats('getHandlerResult');
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:mirrors';
|
import 'dart:mirrors';
|
||||||
|
|
||||||
|
final RegExp straySlashes = new RegExp(r'(^/+)|(/+$)');
|
||||||
|
|
||||||
matchingAnnotation(List<InstanceMirror> metadata, Type T) {
|
matchingAnnotation(List<InstanceMirror> metadata, Type T) {
|
||||||
for (InstanceMirror metaDatum in metadata) {
|
for (InstanceMirror metaDatum in metadata) {
|
||||||
if (metaDatum.hasReflectee) {
|
if (metaDatum.hasReflectee) {
|
||||||
|
|
|
@ -5,20 +5,39 @@ import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
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++) {
|
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) {
|
void start(int id) {
|
||||||
var app = new AngelMetrics.custom(startShared)
|
var app = new Angel.custom(startShared)..lazyParseBodies = true;
|
||||||
..lazyParseBodies = true
|
|
||||||
..get('/', (req, res) => res.write('Hello, world!'));
|
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;
|
var oldHandler = app.errorHandler;
|
||||||
app.errorHandler = (e, req, res) {
|
app.errorHandler = (e, req, res) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: angel_framework
|
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.
|
description: A high-powered HTTP server with DI, routing and more.
|
||||||
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
|
||||||
|
@ -8,17 +8,17 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
angel_http_exception: ^1.0.0
|
angel_http_exception: ^1.0.0
|
||||||
angel_model: ^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
|
body_parser: ^1.0.0-dev
|
||||||
charcode: ^1.0.0
|
charcode: ^1.0.0
|
||||||
container: ^0.1.2
|
container: ^0.1.2
|
||||||
flatten: ^1.0.0
|
|
||||||
json_god: ^2.0.0-beta
|
json_god: ^2.0.0-beta
|
||||||
logging: ">=0.11.3 <1.0.0"
|
logging: ">=0.11.3 <1.0.0"
|
||||||
matcher: ^0.12.0
|
matcher: ^0.12.0
|
||||||
merge_map: ^1.0.0
|
merge_map: ^1.0.0
|
||||||
meta: ^1.0.0
|
meta: ^1.0.0
|
||||||
mime: ^0.9.3
|
mime: ^0.9.3
|
||||||
|
pool: ^1.0.0
|
||||||
random_string: ^0.0.1
|
random_string: ^0.0.1
|
||||||
tuple: ^1.0.0
|
tuple: ^1.0.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
|
@ -34,6 +34,12 @@ main() {
|
||||||
expect(req.accepts('text/html'), isTrue);
|
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', () {
|
group('disallow null', () {
|
||||||
RequestContext req;
|
RequestContext req;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'controller_test.dart' as controller;
|
||||||
import 'di_test.dart' as di;
|
import 'di_test.dart' as di;
|
||||||
import 'encoders_buffer_test.dart' as encoders_buffer;
|
import 'encoders_buffer_test.dart' as encoders_buffer;
|
||||||
import 'exception_test.dart' as exception;
|
import 'exception_test.dart' as exception;
|
||||||
|
import 'extension_test.dart' as extension;
|
||||||
import 'general_test.dart' as general;
|
import 'general_test.dart' as general;
|
||||||
import 'hooked_test.dart' as hooked;
|
import 'hooked_test.dart' as hooked;
|
||||||
import 'parameter_meta_test.dart' as parameter_meta;
|
import 'parameter_meta_test.dart' as parameter_meta;
|
||||||
|
@ -27,6 +28,7 @@ main() {
|
||||||
group('di', di.main);
|
group('di', di.main);
|
||||||
group('encoders_buffer', encoders_buffer.main);
|
group('encoders_buffer', encoders_buffer.main);
|
||||||
group('exception', exception.main);
|
group('exception', exception.main);
|
||||||
|
group('extension', extension.main);
|
||||||
group('general', general.main);
|
group('general', general.main);
|
||||||
group('hooked', hooked.main);
|
group('hooked', hooked.main);
|
||||||
group('parameter_meta', parameter_meta.main);
|
group('parameter_meta', parameter_meta.main);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
import 'package:angel_route/angel_route.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
|
29
test/extension_test.dart
Normal file
29
test/extension_test.dart
Normal 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);
|
||||||
|
}
|
|
@ -49,8 +49,8 @@ main() {
|
||||||
|
|
||||||
ted = nested.post('/ted/:route', (RequestContext req, res) {
|
ted = nested.post('/ted/:route', (RequestContext req, res) {
|
||||||
print('Params: ${req.params}');
|
print('Params: ${req.params}');
|
||||||
print(
|
print('Path: ${ted.path}, uri: ${req.path}');
|
||||||
'Path: ${ted.path}, matcher: ${ted.matcher.pattern}, uri: ${req.path}');
|
print('matcher: ${ted.parser}');
|
||||||
return req.params;
|
return req.params;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ main() {
|
||||||
app
|
app
|
||||||
.get('/greet/:name',
|
.get('/greet/:name',
|
||||||
(RequestContext req, res) async => "Hello ${req.params['name']}")
|
(RequestContext req, res) async => "Hello ${req.params['name']}")
|
||||||
.as('Named routes');
|
.name = 'Named routes';
|
||||||
app.get('/named', (req, ResponseContext res) async {
|
app.get('/named', (req, ResponseContext res) async {
|
||||||
res.redirectTo('Named routes', {'name': 'tests'});
|
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');
|
app.use('MJ');
|
||||||
|
|
||||||
|
@ -165,7 +167,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can name routes', () {
|
test('Can name routes', () {
|
||||||
Route foo = new Route('/framework/:id', name: 'frm');
|
Route foo = app.get('/framework/:id', [])..name = 'frm';
|
||||||
print('Foo: $foo');
|
print('Foo: $foo');
|
||||||
String uri = foo.makeUri({'id': 'angel'});
|
String uri = foo.makeUri({'id': 'angel'});
|
||||||
print(uri);
|
print(uri);
|
||||||
|
|
Loading…
Reference in a new issue