Framework migration update
This commit is contained in:
parent
22e073eb3f
commit
d65a0e7c83
24 changed files with 288 additions and 248 deletions
|
@ -7,19 +7,19 @@ import 'service.dart';
|
|||
///
|
||||
/// Well-suited for testing.
|
||||
class AnonymousService<Id, Data> extends Service<Id, Data> {
|
||||
FutureOr<List<Data>>? Function([Map<String, dynamic>?])? _index;
|
||||
FutureOr<Data> Function(Id?, [Map<String, dynamic>?])? _read, _remove;
|
||||
FutureOr<List<Data>> Function([Map<String, dynamic>?])? _index;
|
||||
FutureOr<Data> Function(Id, [Map<String, dynamic>?])? _read, _remove;
|
||||
FutureOr<Data> Function(Data, [Map<String, dynamic>?])? _create;
|
||||
FutureOr<Data> Function(Id?, Data, [Map<String, dynamic>?])? _modify, _update;
|
||||
FutureOr<Data> Function(Id, Data, [Map<String, dynamic>?])? _modify, _update;
|
||||
|
||||
AnonymousService(
|
||||
{FutureOr<List<Data>>? index([Map<String, dynamic>? params])?,
|
||||
FutureOr<Data> read(Id? id, [Map<String, dynamic>? params])?,
|
||||
{FutureOr<List<Data>> index([Map<String, dynamic>? params])?,
|
||||
FutureOr<Data> read(Id id, [Map<String, dynamic>? params])?,
|
||||
FutureOr<Data> create(Data data, [Map<String, dynamic>? params])?,
|
||||
FutureOr<Data> modify(Id? id, Data data, [Map<String, dynamic>? params])?,
|
||||
FutureOr<Data> update(Id? id, Data data, [Map<String, dynamic>? params])?,
|
||||
FutureOr<Data> remove(Id? id, [Map<String, dynamic>? params])?,
|
||||
FutureOr<Data> Function(RequestContext, ResponseContext?)? readData})
|
||||
FutureOr<Data> modify(Id id, Data data, [Map<String, dynamic>? params])?,
|
||||
FutureOr<Data> update(Id id, Data data, [Map<String, dynamic>? params])?,
|
||||
FutureOr<Data> remove(Id id, [Map<String, dynamic>? params])?,
|
||||
FutureOr<Data> Function(RequestContext, ResponseContext)? readData})
|
||||
: super(readData: readData) {
|
||||
_index = index;
|
||||
_read = read;
|
||||
|
@ -31,10 +31,10 @@ class AnonymousService<Id, Data> extends Service<Id, Data> {
|
|||
|
||||
@override
|
||||
index([Map<String, dynamic>? params]) =>
|
||||
Future.sync(() => _index != null ? _index!(params)! : super.index(params));
|
||||
Future.sync(() => _index != null ? _index!(params) : super.index(params));
|
||||
|
||||
@override
|
||||
read(Id? id, [Map<String, dynamic>? params]) => Future.sync(
|
||||
read(Id id, [Map<String, dynamic>? params]) => Future.sync(
|
||||
() => _read != null ? _read!(id, params) : super.read(id, params));
|
||||
|
||||
@override
|
||||
|
@ -42,18 +42,18 @@ class AnonymousService<Id, Data> extends Service<Id, Data> {
|
|||
_create != null ? _create!(data, params) : super.create(data, params));
|
||||
|
||||
@override
|
||||
modify(Id? id, Data data, [Map<String, dynamic>? params]) =>
|
||||
modify(Id id, Data data, [Map<String, dynamic>? params]) =>
|
||||
Future.sync(() => _modify != null
|
||||
? _modify!(id, data, params)
|
||||
: super.modify(id, data, params));
|
||||
|
||||
@override
|
||||
update(Id? id, Data data, [Map<String, dynamic>? params]) =>
|
||||
update(Id id, Data data, [Map<String, dynamic>? params]) =>
|
||||
Future.sync(() => _update != null
|
||||
? _update!(id, data, params)
|
||||
: super.update(id, data, params));
|
||||
|
||||
@override
|
||||
remove(Id? id, [Map<String, dynamic>? params]) => Future.sync(
|
||||
remove(Id id, [Map<String, dynamic>? params]) => Future.sync(
|
||||
() => _remove != null ? _remove!(id, params) : super.remove(id, params));
|
||||
}
|
||||
|
|
|
@ -21,12 +21,12 @@ class Controller {
|
|||
List<RequestHandler> middleware = [];
|
||||
|
||||
/// A mapping of route paths to routes, produced from the [Expose] annotations on this class.
|
||||
Map<String?, Route> routeMappings = {};
|
||||
Map<String, Route> routeMappings = {};
|
||||
|
||||
SymlinkRoute<RequestHandler?>? _mountPoint;
|
||||
SymlinkRoute<RequestHandler>? _mountPoint;
|
||||
|
||||
/// The route at which this controller is mounted on the server.
|
||||
SymlinkRoute<RequestHandler?>? get mountPoint => _mountPoint;
|
||||
SymlinkRoute<RequestHandler>? get mountPoint => _mountPoint;
|
||||
|
||||
Controller({this.injectSingleton = true});
|
||||
|
||||
|
@ -47,8 +47,8 @@ class Controller {
|
|||
}
|
||||
|
||||
/// Applies the routes from this [Controller] to some [router].
|
||||
Future<String?> applyRoutes(
|
||||
Router<RequestHandler?> router, Reflector reflector) async {
|
||||
Future<String> applyRoutes(
|
||||
Router<RequestHandler> router, Reflector reflector) async {
|
||||
// Load global expose decl
|
||||
var classMirror = reflector.reflectClass(this.runtimeType)!;
|
||||
Expose? exposeDecl = findExpose(reflector);
|
||||
|
@ -58,7 +58,8 @@ class Controller {
|
|||
}
|
||||
|
||||
var routable = Routable();
|
||||
_mountPoint = router.mount(exposeDecl.path!, routable);
|
||||
var m = router.mount(exposeDecl.path!, routable);
|
||||
_mountPoint = m;
|
||||
var typeMirror = reflector.reflectType(this.runtimeType);
|
||||
|
||||
// Pre-reflect methods
|
||||
|
@ -72,7 +73,10 @@ class Controller {
|
|||
classMirror.declarations.forEach(routeBuilder);
|
||||
|
||||
// Return the name.
|
||||
return exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : typeMirror!.name;
|
||||
var result =
|
||||
exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : typeMirror!.name;
|
||||
|
||||
return Future.value(result);
|
||||
}
|
||||
|
||||
void Function(ReflectedDeclaration) _routeBuilder(
|
||||
|
@ -117,8 +121,8 @@ class Controller {
|
|||
method.parameters[0].type.reflectedType == RequestContext &&
|
||||
method.parameters[1].type.reflectedType == ResponseContext) {
|
||||
// Create a regular route
|
||||
routeMappings[name] = routable
|
||||
.addRoute(exposeDecl.method, exposeDecl.path,
|
||||
routeMappings[name ?? ''] = routable
|
||||
.addRoute(exposeDecl.method, exposeDecl.path ?? '',
|
||||
(RequestContext req, ResponseContext res) {
|
||||
var result = reflectedMethod!(req, res);
|
||||
return result is RequestHandler ? result(req, res) : result;
|
||||
|
@ -190,7 +194,7 @@ class Controller {
|
|||
if (!path.startsWith('/')) path = '/$path';
|
||||
}
|
||||
|
||||
routeMappings[name] = routable.addRoute(
|
||||
routeMappings[name ?? ''] = routable.addRoute(
|
||||
httpMethod, path, handleContained(reflectedMethod, injection),
|
||||
middleware: middleware);
|
||||
}
|
||||
|
|
|
@ -107,27 +107,27 @@ abstract class Driver<
|
|||
var path = req.path;
|
||||
if (path == '/') path = '';
|
||||
|
||||
Tuple4<List?, Map<String?, dynamic>, ParseResult<RouteResult?>?,
|
||||
Tuple4<List, Map<String, dynamic>, ParseResult<RouteResult>,
|
||||
MiddlewarePipeline> resolveTuple() {
|
||||
var r = app.optimizedRouter;
|
||||
var resolved =
|
||||
r.resolveAbsolute(path, method: req.method!, strip: false);
|
||||
var pipeline = MiddlewarePipeline<RequestHandler?>(resolved);
|
||||
r.resolveAbsolute(path, method: req.method, strip: false);
|
||||
var pipeline = MiddlewarePipeline<RequestHandler>(resolved);
|
||||
return Tuple4(
|
||||
pipeline.handlers,
|
||||
resolved.fold<Map<String?, dynamic>>(
|
||||
<String?, dynamic>{}, (out, r) => out..addAll(r.allParams)),
|
||||
resolved.isEmpty ? null : resolved.first.parseResult,
|
||||
pipeline.handlers!,
|
||||
resolved.fold<Map<String, dynamic>>(
|
||||
<String, dynamic>{}, (out, r) => out..addAll(r.allParams)),
|
||||
(resolved.isEmpty ? null : resolved.first.parseResult)!,
|
||||
pipeline,
|
||||
);
|
||||
}
|
||||
|
||||
var cacheKey = req.method! + path!;
|
||||
var cacheKey = req.method + path;
|
||||
var tuple = app.environment.isProduction
|
||||
? app.handlerCache.putIfAbsent(cacheKey, resolveTuple)
|
||||
: resolveTuple();
|
||||
var line = tuple.item4 as MiddlewarePipeline<RequestHandler?>;
|
||||
var it = MiddlewarePipelineIterator<RequestHandler?>(line);
|
||||
var line = tuple.item4 as MiddlewarePipeline<RequestHandler>;
|
||||
var it = MiddlewarePipelineIterator<RequestHandler>(line);
|
||||
|
||||
req.params.addAll(tuple.item2);
|
||||
|
||||
|
@ -135,10 +135,10 @@ abstract class Driver<
|
|||
?..registerSingleton<RequestContext>(req)
|
||||
..registerSingleton<ResponseContext>(res)
|
||||
..registerSingleton<MiddlewarePipeline>(tuple.item4)
|
||||
..registerSingleton<MiddlewarePipeline<RequestHandler?>>(line)
|
||||
..registerSingleton<MiddlewarePipeline<RequestHandler>>(line)
|
||||
..registerSingleton<MiddlewarePipelineIterator>(it)
|
||||
..registerSingleton<MiddlewarePipelineIterator<RequestHandler?>>(it)
|
||||
..registerSingleton<ParseResult<RouteResult?>?>(tuple.item3)
|
||||
..registerSingleton<MiddlewarePipelineIterator<RequestHandler>>(it)
|
||||
..registerSingleton<ParseResult<RouteResult>?>(tuple.item3)
|
||||
..registerSingleton<ParseResult?>(tuple.item3);
|
||||
|
||||
if (app.environment.isProduction && app.logger != null) {
|
||||
|
@ -318,8 +318,8 @@ abstract class Driver<
|
|||
List<int> outputBuffer = res.buffer!.toBytes();
|
||||
|
||||
if (res.encoders.isNotEmpty) {
|
||||
var allowedEncodings = req.headers!
|
||||
.value('accept-encoding')
|
||||
var allowedEncodings = req.headers
|
||||
?.value('accept-encoding')
|
||||
?.split(',')
|
||||
.map((s) => s.trim())
|
||||
.where((s) => s.isNotEmpty)
|
||||
|
@ -361,7 +361,7 @@ abstract class Driver<
|
|||
/// Runs a [MiddlewarePipeline].
|
||||
static Future<void> runPipeline<RequestContextType extends RequestContext,
|
||||
ResponseContextType extends ResponseContext>(
|
||||
MiddlewarePipelineIterator<RequestHandler?> it,
|
||||
MiddlewarePipelineIterator<RequestHandler> it,
|
||||
RequestContextType req,
|
||||
ResponseContextType res,
|
||||
Angel app) async {
|
||||
|
|
|
@ -49,7 +49,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
|
|||
}
|
||||
|
||||
@override
|
||||
FutureOr<Data>? Function(RequestContext, ResponseContext?)? get readData =>
|
||||
FutureOr<Data> Function(RequestContext, ResponseContext)? get readData =>
|
||||
inner.readData;
|
||||
|
||||
RequestContext? _getRequest(Map? params) {
|
||||
|
@ -296,7 +296,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Data> read(Id? id, [Map<String, dynamic>? _params]) {
|
||||
Future<Data> read(Id id, [Map<String, dynamic>? _params]) {
|
||||
var params = _stripReq(_params);
|
||||
return beforeRead
|
||||
._emit(HookedServiceEvent(false, _getRequest(_params),
|
||||
|
@ -322,7 +322,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Data> create(Data? data, [Map<String, dynamic>? _params]) {
|
||||
Future<Data> create(Data data, [Map<String, dynamic>? _params]) {
|
||||
var params = _stripReq(_params);
|
||||
return beforeCreated
|
||||
._emit(HookedServiceEvent(false, _getRequest(_params),
|
||||
|
@ -348,7 +348,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Data> modify(Id? id, Data? data, [Map<String, dynamic>? _params]) {
|
||||
Future<Data> modify(Id id, Data data, [Map<String, dynamic>? _params]) {
|
||||
var params = _stripReq(_params);
|
||||
return beforeModified
|
||||
._emit(HookedServiceEvent(false, _getRequest(_params),
|
||||
|
@ -377,7 +377,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Data> update(Id? id, Data? data, [Map<String, dynamic>? _params]) {
|
||||
Future<Data> update(Id id, Data data, [Map<String, dynamic>? _params]) {
|
||||
var params = _stripReq(_params);
|
||||
return beforeUpdated
|
||||
._emit(HookedServiceEvent(false, _getRequest(_params),
|
||||
|
@ -406,7 +406,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Data> remove(Id? id, [Map<String, dynamic>? _params]) {
|
||||
Future<Data> remove(Id id, [Map<String, dynamic>? _params]) {
|
||||
var params = _stripReq(_params);
|
||||
return beforeRemoved
|
||||
._emit(HookedServiceEvent(false, _getRequest(_params),
|
||||
|
@ -480,7 +480,7 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
|
|||
}
|
||||
|
||||
/// Fired when a hooked service is invoked.
|
||||
class HookedServiceEvent<Id, Data, T extends Service<Id?, Data?>> {
|
||||
class HookedServiceEvent<Id, Data, T extends Service<Id, Data?>> {
|
||||
static const String indexed = 'indexed';
|
||||
static const String read = 'read';
|
||||
static const String created = 'created';
|
||||
|
|
|
@ -17,7 +17,7 @@ class HostnameSyntaxParser {
|
|||
|
||||
RegExp parse() {
|
||||
var b = StringBuffer();
|
||||
var parts = Queue<String?>();
|
||||
var parts = Queue<String>();
|
||||
|
||||
while (!_scanner.isDone) {
|
||||
if (_scanner.scan('|')) {
|
||||
|
|
|
@ -84,30 +84,28 @@ class HostnameRouter {
|
|||
/// Also returns `true` if all of the sub-app's handlers returned
|
||||
/// `true`.
|
||||
Future<bool> handleRequest(RequestContext req, ResponseContext res) async {
|
||||
if (req.hostname != null) {
|
||||
for (var pattern in _patterns) {
|
||||
// print('${req.hostname} vs $_creators');
|
||||
if (pattern.allMatches(req.hostname!).isNotEmpty) {
|
||||
// Resolve the entire pipeline within the context of the selected app.
|
||||
var app = _apps[pattern] ??= (await _creators[pattern]!());
|
||||
// print('App for ${req.hostname} = $app from $pattern');
|
||||
// app.dumpTree();
|
||||
for (var pattern in _patterns) {
|
||||
// print('${req.hostname} vs $_creators');
|
||||
if (pattern.allMatches(req.hostname).isNotEmpty) {
|
||||
// Resolve the entire pipeline within the context of the selected app.
|
||||
var app = _apps[pattern] ??= (await _creators[pattern]!());
|
||||
// print('App for ${req.hostname} = $app from $pattern');
|
||||
// app.dumpTree();
|
||||
|
||||
var r = app.optimizedRouter;
|
||||
var resolved = r.resolveAbsolute(req.path, method: req.method!);
|
||||
var pipeline = MiddlewarePipeline<RequestHandler?>(resolved);
|
||||
// print('Pipeline: $pipeline');
|
||||
for (var handler in pipeline.handlers!) {
|
||||
// print(handler);
|
||||
// Avoid stack overflow.
|
||||
if (handler == handleRequest) {
|
||||
continue;
|
||||
} else if (!await app.executeHandler(handler, req, res)) {
|
||||
// print('$handler TERMINATED');
|
||||
return false;
|
||||
} else {
|
||||
// print('$handler CONTINUED');
|
||||
}
|
||||
var r = app.optimizedRouter;
|
||||
var resolved = r.resolveAbsolute(req.path, method: req.method);
|
||||
var pipeline = MiddlewarePipeline<RequestHandler>(resolved);
|
||||
// print('Pipeline: $pipeline');
|
||||
for (var handler in pipeline.handlers!) {
|
||||
// print(handler);
|
||||
// Avoid stack overflow.
|
||||
if (handler == handleRequest) {
|
||||
continue;
|
||||
} else if (!await app.executeHandler(handler, req, res)) {
|
||||
// print('$handler TERMINATED');
|
||||
return false;
|
||||
} else {
|
||||
// print('$handler CONTINUED');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,27 +13,31 @@ RequestHandler ioc(Function handler, {Iterable<String> optional = const []}) {
|
|||
|
||||
return (req, res) {
|
||||
if (injection == null) {
|
||||
injection = preInject(handler, req.app.container!.reflector);
|
||||
injection!.optional.addAll(optional);
|
||||
contained = handleContained(handler, injection);
|
||||
if (req.app!.container != null) {
|
||||
injection = preInject(handler, req.app!.container!.reflector);
|
||||
if (injection != null) {
|
||||
injection?.optional.addAll(optional);
|
||||
contained = handleContained(handler, injection!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return req.app.executeHandler(contained, req, res);
|
||||
return req.app!.executeHandler(contained, req, res);
|
||||
};
|
||||
}
|
||||
|
||||
resolveInjection(requirement, InjectionRequest? injection, RequestContext req,
|
||||
resolveInjection(requirement, InjectionRequest injection, RequestContext req,
|
||||
ResponseContext res, bool throwOnUnresolved,
|
||||
[Container? container]) async {
|
||||
var propFromApp;
|
||||
container ??= req.container ?? res.app?.container;
|
||||
container ??= req.container ?? res.app!.container;
|
||||
|
||||
if (requirement == RequestContext) {
|
||||
return req;
|
||||
} else if (requirement == ResponseContext) {
|
||||
return res;
|
||||
} else if (requirement is String &&
|
||||
injection!.parameters.containsKey(requirement)) {
|
||||
injection.parameters.containsKey(requirement)) {
|
||||
var param = injection.parameters[requirement]!;
|
||||
var value = param.getValue(req);
|
||||
if (value == null && param.required != false) throw param.error as Object;
|
||||
|
@ -44,9 +48,9 @@ resolveInjection(requirement, InjectionRequest? injection, RequestContext req,
|
|||
}
|
||||
if (req.params.containsKey(requirement)) {
|
||||
return req.params[requirement];
|
||||
} else if ((propFromApp = req.app.findProperty(requirement)) != null) {
|
||||
} else if ((propFromApp = req.app!.findProperty(requirement)) != null) {
|
||||
return propFromApp;
|
||||
} else if (injection!.optional.contains(requirement)) {
|
||||
} else if (injection.optional.contains(requirement)) {
|
||||
return null;
|
||||
} else if (throwOnUnresolved) {
|
||||
throw ArgumentError(
|
||||
|
@ -59,7 +63,7 @@ resolveInjection(requirement, InjectionRequest? injection, RequestContext req,
|
|||
var key = requirement.first;
|
||||
var type = requirement.last;
|
||||
if (req.params.containsKey(key) ||
|
||||
req.app.configuration.containsKey(key) ||
|
||||
req.app!.configuration.containsKey(key) ||
|
||||
_primitiveTypes.contains(type)) {
|
||||
return await resolveInjection(
|
||||
key, injection, req, res, throwOnUnresolved, container);
|
||||
|
@ -95,10 +99,10 @@ bool suitableForInjection(
|
|||
}
|
||||
|
||||
/// Handles a request with a DI-enabled handler.
|
||||
RequestHandler handleContained(Function? handler, InjectionRequest? injection,
|
||||
RequestHandler handleContained(Function handler, InjectionRequest injection,
|
||||
[Container? container]) {
|
||||
return (RequestContext req, ResponseContext res) async {
|
||||
if (injection!.parameters.isNotEmpty &&
|
||||
if (injection.parameters.isNotEmpty &&
|
||||
injection.parameters.values.any((p) => p.match != null) &&
|
||||
!suitableForInjection(req, res, injection)) return Future.value(true);
|
||||
|
||||
|
@ -116,7 +120,7 @@ RequestHandler handleContained(Function? handler, InjectionRequest? injection,
|
|||
[entry.key, entry.value], injection, req, res, false, container);
|
||||
}
|
||||
|
||||
return Function.apply(handler!, args, named);
|
||||
return Function.apply(handler, args, named);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -67,10 +67,12 @@ class MapService extends Service<String?, Map<String, dynamic>> {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> read(String? id, [Map<String, dynamic>? params]) {
|
||||
Future<Map<String, dynamic>> read(String? id,
|
||||
[Map<String, dynamic>? params]) {
|
||||
return Future.value(items.firstWhere(_matchesId(id),
|
||||
orElse: (() => throw AngelHttpException.notFound(
|
||||
message: 'No record found for ID $id')) as Map<String, dynamic> Function()?));
|
||||
message: 'No record found for ID $id'))
|
||||
as Map<String, dynamic> Function()?));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -122,16 +122,16 @@ class Parameter {
|
|||
/// Obtains a value for this parameter from a [RequestContext].
|
||||
getValue(RequestContext req) {
|
||||
if (cookie?.isNotEmpty == true) {
|
||||
return req.cookies!.firstWhere((c) => c.name == cookie).value;
|
||||
return req.cookies.firstWhere((c) => c.name == cookie).value;
|
||||
}
|
||||
if (header?.isNotEmpty == true) {
|
||||
return req.headers!.value(header!) ?? defaultValue;
|
||||
return req.headers?.value(header ?? '') ?? defaultValue;
|
||||
}
|
||||
if (session?.isNotEmpty == true) {
|
||||
return req.session![session] ?? defaultValue;
|
||||
return req.session?[session] ?? defaultValue;
|
||||
}
|
||||
if (query?.isNotEmpty == true) {
|
||||
return req.uri!.queryParameters[query!] ?? defaultValue;
|
||||
return req.uri?.queryParameters[query] ?? defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import 'package:meta/meta.dart';
|
|||
import 'package:mime/mime.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'metadata.dart';
|
||||
import 'response_context.dart';
|
||||
|
@ -30,15 +31,19 @@ part 'injection.dart';
|
|||
abstract class RequestContext<RawRequest> {
|
||||
/// Similar to [Angel.shutdownHooks], allows for logic to be executed
|
||||
/// when a [RequestContext] is done being processed.
|
||||
final log = Logger('RequestContext');
|
||||
|
||||
final List<FutureOr<void> Function()> shutdownHooks = [];
|
||||
|
||||
String? _acceptHeaderCache, _extensionCache;
|
||||
bool? _acceptsAllCache, _hasParsedBody = false, _closed = false;
|
||||
Map<String?, dynamic>? _bodyFields, _queryParameters;
|
||||
List? _bodyList;
|
||||
bool? _acceptsAllCache;
|
||||
Map<String, dynamic>? _queryParameters;
|
||||
Object? _bodyObject;
|
||||
List<UploadedFile>? _uploadedFiles;
|
||||
MediaType? _contentType;
|
||||
bool _hasParsedBody = false, _closed = false;
|
||||
Map<String, dynamic> _bodyFields = {};
|
||||
List _bodyList = [];
|
||||
List<UploadedFile> _uploadedFiles = <UploadedFile>[];
|
||||
MediaType _contentType = MediaType("text", "html");
|
||||
|
||||
/// The underlying [RawRequest] provided by the driver.
|
||||
RawRequest get rawRequest;
|
||||
|
@ -47,16 +52,16 @@ abstract class RequestContext<RawRequest> {
|
|||
final Map<String, dynamic> serviceParams = {};
|
||||
|
||||
/// The [Angel] instance that is responding to this request.
|
||||
late Angel app;
|
||||
Angel? app;
|
||||
|
||||
/// Any cookies sent with this request.
|
||||
List<Cookie>? get cookies;
|
||||
List<Cookie> get cookies => <Cookie>[];
|
||||
|
||||
/// All HTTP headers sent with this request.
|
||||
HttpHeaders? get headers;
|
||||
|
||||
/// The requested hostname.
|
||||
String? get hostname;
|
||||
String get hostname => 'localhost';
|
||||
|
||||
/// The IoC container that can be used to provide functionality to produce
|
||||
/// objects of a given type.
|
||||
|
@ -70,24 +75,33 @@ abstract class RequestContext<RawRequest> {
|
|||
/// This request's HTTP method.
|
||||
///
|
||||
/// This may have been processed by an override. See [originalMethod] to get the real method.
|
||||
String? get method;
|
||||
String get method => 'GET';
|
||||
|
||||
/// The original HTTP verb sent to the server.
|
||||
String? get originalMethod;
|
||||
String get originalMethod => 'GET';
|
||||
|
||||
/// The content type of an incoming request.
|
||||
MediaType? get contentType =>
|
||||
_contentType ??= MediaType.parse(headers!.contentType.toString());
|
||||
MediaType get contentType {
|
||||
if (headers?.contentType != null) {
|
||||
try {
|
||||
_contentType = MediaType.parse(headers!.contentType.toString());
|
||||
} catch (e) {
|
||||
log.warning(
|
||||
'Invalid media type [${headers!.contentType.toString()}]', e);
|
||||
}
|
||||
}
|
||||
return _contentType;
|
||||
}
|
||||
|
||||
/// The URL parameters extracted from the request URI.
|
||||
Map<String?, dynamic> params = <String?, dynamic>{};
|
||||
Map<String, dynamic> params = <String, dynamic>{};
|
||||
|
||||
/// The requested path.
|
||||
String? get path;
|
||||
String get path => '';
|
||||
|
||||
/// Is this an **XMLHttpRequest**?
|
||||
bool get isXhr {
|
||||
return headers!.value("X-Requested-With")?.trim()?.toLowerCase() ==
|
||||
return headers?.value("X-Requested-With")?.trim().toLowerCase() ==
|
||||
'xmlhttprequest';
|
||||
}
|
||||
|
||||
|
@ -104,17 +118,18 @@ abstract class RequestContext<RawRequest> {
|
|||
Stream<List<int>>? get body;
|
||||
|
||||
/// Returns `true` if [parseBody] has been called so far.
|
||||
bool? get hasParsedBody => _hasParsedBody;
|
||||
bool get hasParsedBody => _hasParsedBody;
|
||||
|
||||
/// Returns a *mutable* [Map] of the fields parsed from the request [body].
|
||||
///
|
||||
/// Note that [parseBody] must be called first.
|
||||
Map<String?, dynamic>? get bodyAsMap {
|
||||
if (!hasParsedBody!) {
|
||||
Map<String, dynamic> get bodyAsMap {
|
||||
if (!hasParsedBody) {
|
||||
throw StateError('The request body has not been parsed yet.');
|
||||
} else if (_bodyFields == null) {
|
||||
throw StateError('The request body, $_bodyObject, is not a Map.');
|
||||
}
|
||||
// else if (_bodyFields == null) {
|
||||
// throw StateError('The request body, $_bodyObject, is not a Map.');
|
||||
//}
|
||||
|
||||
return _bodyFields;
|
||||
}
|
||||
|
@ -122,13 +137,13 @@ abstract class RequestContext<RawRequest> {
|
|||
/// This setter allows you to explicitly set the request body **exactly once**.
|
||||
///
|
||||
/// Use this if the format of the body is not natively parsed by Angel.
|
||||
set bodyAsMap(Map<String?, dynamic>? value) => bodyAsObject = value;
|
||||
set bodyAsMap(Map<String, dynamic>? value) => bodyAsObject = value;
|
||||
|
||||
/// Returns a *mutable* [List] parsed from the request [body].
|
||||
///
|
||||
/// Note that [parseBody] must be called first.
|
||||
List? get bodyAsList {
|
||||
if (!hasParsedBody!) {
|
||||
if (!hasParsedBody) {
|
||||
throw StateError('The request body has not been parsed yet.');
|
||||
} else if (_bodyList == null) {
|
||||
throw StateError('The request body, $_bodyObject, is not a List.');
|
||||
|
@ -146,7 +161,7 @@ abstract class RequestContext<RawRequest> {
|
|||
///
|
||||
/// Note that [parseBody] must be called first.
|
||||
Object? get bodyAsObject {
|
||||
if (!hasParsedBody!) {
|
||||
if (!hasParsedBody) {
|
||||
throw StateError('The request body has not been parsed yet.');
|
||||
}
|
||||
|
||||
|
@ -162,7 +177,7 @@ abstract class RequestContext<RawRequest> {
|
|||
'The request body has already been parsed/set, and cannot be overwritten.');
|
||||
} else {
|
||||
if (value is List) _bodyList = value;
|
||||
if (value is Map<String?, dynamic>) _bodyFields = value;
|
||||
if (value is Map<String, dynamic>) _bodyFields = value;
|
||||
_bodyObject = value;
|
||||
_hasParsedBody = true;
|
||||
}
|
||||
|
@ -172,7 +187,7 @@ abstract class RequestContext<RawRequest> {
|
|||
///
|
||||
/// Note that [parseBody] must be called first.
|
||||
List<UploadedFile>? get uploadedFiles {
|
||||
if (!hasParsedBody!) {
|
||||
if (!hasParsedBody) {
|
||||
throw StateError('The request body has not been parsed yet.');
|
||||
}
|
||||
|
||||
|
@ -180,13 +195,13 @@ abstract class RequestContext<RawRequest> {
|
|||
}
|
||||
|
||||
/// Returns a *mutable* map of the fields contained in the query.
|
||||
Map<String?, dynamic> get queryParameters =>
|
||||
_queryParameters ??= Map<String?, dynamic>.from(uri!.queryParameters);
|
||||
Map<String, dynamic> get queryParameters => _queryParameters ??=
|
||||
Map<String, dynamic>.from(uri?.queryParameters ?? {});
|
||||
|
||||
/// 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);
|
||||
String get extension => _extensionCache ??= p.extension(uri?.path ?? '');
|
||||
|
||||
/// Returns `true` if the client's `Accept` header indicates that the given [contentType] is considered a valid response.
|
||||
///
|
||||
|
@ -208,7 +223,7 @@ abstract class RequestContext<RawRequest> {
|
|||
'RequestContext.accepts expects the `contentType` parameter to NOT be null.');
|
||||
}
|
||||
|
||||
_acceptHeaderCache ??= headers!.value('accept');
|
||||
_acceptHeaderCache ??= headers?.value('accept');
|
||||
|
||||
if (_acceptHeaderCache == null) {
|
||||
return true;
|
||||
|
@ -235,52 +250,58 @@ abstract class RequestContext<RawRequest> {
|
|||
|
||||
/// Manually parses the request body, if it has not already been parsed.
|
||||
Future<void> parseBody({Encoding encoding = utf8}) async {
|
||||
if (contentType == null) {
|
||||
throw FormatException('Missing "content-type" header.');
|
||||
}
|
||||
//if (contentType == null) {
|
||||
// throw FormatException('Missing "content-type" header.');
|
||||
//}
|
||||
|
||||
if (!_hasParsedBody!) {
|
||||
if (!_hasParsedBody) {
|
||||
_hasParsedBody = true;
|
||||
|
||||
if (contentType!.type == 'application' &&
|
||||
contentType!.subtype == 'json') {
|
||||
var contentBody = body;
|
||||
if (contentBody == null) {
|
||||
contentBody = Stream.empty();
|
||||
}
|
||||
|
||||
if (contentType.type == 'application' && contentType.subtype == 'json') {
|
||||
_uploadedFiles = [];
|
||||
|
||||
var parsed = (_bodyObject =
|
||||
await encoding.decoder.bind(body!).join().then(json.decode))!;
|
||||
await encoding.decoder.bind(contentBody).join().then(json.decode));
|
||||
|
||||
if (parsed is Map) {
|
||||
_bodyFields = Map<String?, dynamic>.from(parsed);
|
||||
_bodyFields = Map<String, dynamic>.from(parsed);
|
||||
} else if (parsed is List) {
|
||||
_bodyList = parsed;
|
||||
}
|
||||
} else if (contentType!.type == 'application' &&
|
||||
contentType!.subtype == 'x-www-form-urlencoded') {
|
||||
} else if (contentType.type == 'application' &&
|
||||
contentType.subtype == 'x-www-form-urlencoded') {
|
||||
_uploadedFiles = [];
|
||||
var parsed = await encoding.decoder
|
||||
.bind(body!)
|
||||
.bind(contentBody)
|
||||
.join()
|
||||
.then((s) => Uri.splitQueryString(s, encoding: encoding));
|
||||
_bodyFields = Map<String?, dynamic>.from(parsed);
|
||||
} else if (contentType!.type == 'multipart' &&
|
||||
contentType!.subtype == 'form-data' &&
|
||||
contentType!.parameters.containsKey('boundary')) {
|
||||
var boundary = contentType!.parameters['boundary']!;
|
||||
_bodyFields = Map<String, dynamic>.from(parsed);
|
||||
} else if (contentType.type == 'multipart' &&
|
||||
contentType.subtype == 'form-data' &&
|
||||
contentType.parameters.containsKey('boundary')) {
|
||||
var boundary = contentType.parameters['boundary'] ?? '';
|
||||
var transformer = MimeMultipartTransformer(boundary);
|
||||
var parts = transformer.bind(body!).map((part) =>
|
||||
var parts = transformer.bind(contentBody).map((part) =>
|
||||
HttpMultipartFormData.parse(part, defaultEncoding: encoding));
|
||||
_bodyFields = {};
|
||||
_uploadedFiles = [];
|
||||
|
||||
await for (var part in parts) {
|
||||
if (part.isBinary) {
|
||||
_uploadedFiles!.add(UploadedFile(part));
|
||||
_uploadedFiles.add(UploadedFile(part));
|
||||
} else if (part.isText &&
|
||||
part.contentDisposition.parameters.containsKey('name')) {
|
||||
// If there is no name, then don't parse it.
|
||||
var key = part.contentDisposition.parameters['name'];
|
||||
var value = await part.join();
|
||||
_bodyFields![key] = value;
|
||||
if (key != null) {
|
||||
var value = await part.join();
|
||||
_bodyFields[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -293,7 +314,7 @@ abstract class RequestContext<RawRequest> {
|
|||
/// Disposes of all resources.
|
||||
@mustCallSuper
|
||||
Future<void> close() async {
|
||||
if (!_closed!) {
|
||||
if (!_closed) {
|
||||
_closed = true;
|
||||
_acceptsAllCache = null;
|
||||
_acceptHeaderCache = null;
|
||||
|
@ -308,8 +329,9 @@ abstract class RequestContext<RawRequest> {
|
|||
class UploadedFile {
|
||||
/// The underlying `form-data` item.
|
||||
final HttpMultipartFormData formData;
|
||||
final log = Logger('UploadedFile');
|
||||
|
||||
MediaType? _contentType;
|
||||
MediaType _contentType = MediaType("multipart", "form-data");
|
||||
|
||||
UploadedFile(this.formData);
|
||||
|
||||
|
@ -326,9 +348,22 @@ class UploadedFile {
|
|||
|
||||
/// The parsed [:Content-Type:] header of the [:HttpMultipartFormData:].
|
||||
/// Returns [:null:] if not present.
|
||||
MediaType? get contentType => _contentType ??= (formData.contentType == null
|
||||
? null
|
||||
: MediaType.parse(formData.contentType.toString()));
|
||||
//MediaType get contentType => _contentType ??= (formData.contentType == null
|
||||
// ? null
|
||||
// : MediaType.parse(formData.contentType.toString()));
|
||||
|
||||
MediaType get contentType {
|
||||
if (formData.contentType != null) {
|
||||
try {
|
||||
_contentType = MediaType.parse(formData.contentType.toString());
|
||||
} catch (e) {
|
||||
log.warning(
|
||||
'Invalue media type [${formData.contentType.toString()}]', e);
|
||||
}
|
||||
}
|
||||
|
||||
return _contentType;
|
||||
}
|
||||
|
||||
/// The parsed [:Content-Transfer-Encoding:] header of the
|
||||
/// [:HttpMultipartFormData:]. This field is used to determine how to decode
|
||||
|
|
|
@ -32,7 +32,7 @@ RequestHandler chain(Iterable<RequestHandler> handlers) {
|
|||
var current = runPipeline;
|
||||
runPipeline = () => current().then((result) => res.isOpen
|
||||
? Future.value(result)
|
||||
: req.app.executeHandler(handler, req, res));
|
||||
: req.app!.executeHandler(handler, req, res));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ class Routable extends Router<RequestHandler> {
|
|||
Stream<Service> get onService => _onService.stream;
|
||||
|
||||
/// Retrieves the service assigned to the given path.
|
||||
T? findService<T extends Service?>(Pattern path) {
|
||||
T? findService<T extends Service>(Pattern path) {
|
||||
return _serviceLookups.putIfAbsent(path, () {
|
||||
return _services[path] ??
|
||||
_services[path.toString().replaceAll(_straySlashes, '')];
|
||||
|
@ -94,7 +94,7 @@ class Routable extends Router<RequestHandler> {
|
|||
@override
|
||||
Route<RequestHandler> addRoute(
|
||||
String method, String path, RequestHandler handler,
|
||||
{Iterable<RequestHandler> middleware = const Iterable.empty()}) {
|
||||
{Iterable<RequestHandler>? middleware}) {
|
||||
final handlers = <RequestHandler>[];
|
||||
// Merge @Middleware declaration, if any
|
||||
var reflector = _container?.reflector;
|
||||
|
@ -107,7 +107,9 @@ class Routable extends Router<RequestHandler> {
|
|||
}
|
||||
|
||||
final handlerSequence = <RequestHandler>[];
|
||||
handlerSequence.addAll(middleware);
|
||||
if (middleware != null) {
|
||||
handlerSequence.addAll(middleware);
|
||||
}
|
||||
handlerSequence.addAll(handlers);
|
||||
|
||||
return super.addRoute(method, path.toString(), handler,
|
||||
|
|
|
@ -36,10 +36,10 @@ class Angel extends Routable {
|
|||
final List<Angel> _children = [];
|
||||
final Map<
|
||||
String,
|
||||
Tuple4<List?, Map<String?, dynamic>, ParseResult<RouteResult?>?,
|
||||
Tuple4<List, Map<String, dynamic>, ParseResult<RouteResult>?,
|
||||
MiddlewarePipeline>> handlerCache = HashMap();
|
||||
|
||||
Router<RequestHandler?>? _flattened;
|
||||
Router<RequestHandler>? _flattened;
|
||||
Angel? _parent;
|
||||
|
||||
/// A global Map of converters that can transform responses bodies.
|
||||
|
@ -59,7 +59,7 @@ class Angel extends Routable {
|
|||
Map<dynamic, InjectionRequest> get preContained => _preContained;
|
||||
|
||||
/// Returns the [flatten]ed version of this router in production.
|
||||
Router<RequestHandler?> get optimizedRouter => _flattened ?? this;
|
||||
Router<RequestHandler> get optimizedRouter => _flattened ?? this;
|
||||
|
||||
/// Determines whether to allow HTTP request method overrides.
|
||||
bool allowMethodOverrides = true;
|
||||
|
@ -67,10 +67,10 @@ class Angel extends Routable {
|
|||
/// All child application mounted on this instance.
|
||||
List<Angel> get children => List<Angel>.unmodifiable(_children);
|
||||
|
||||
final Map<Pattern?, Controller> _controllers = {};
|
||||
final Map<Pattern, Controller> _controllers = {};
|
||||
|
||||
/// A set of [Controller] objects that have been loaded into the application.
|
||||
Map<Pattern?, Controller> get controllers => _controllers;
|
||||
Map<Pattern, Controller> get controllers => _controllers;
|
||||
|
||||
/// Now *deprecated*, in favor of [AngelEnv] and [angelEnv]. Use `app.environment.isProduction`
|
||||
/// instead.
|
||||
|
@ -148,9 +148,9 @@ class Angel extends Routable {
|
|||
};
|
||||
|
||||
@override
|
||||
Route<RequestHandler?> addRoute(
|
||||
String? method, String? path, RequestHandler? handler,
|
||||
{Iterable<RequestHandler?>? middleware}) {
|
||||
Route<RequestHandler> addRoute(
|
||||
String method, String path, RequestHandler handler,
|
||||
{Iterable<RequestHandler>? middleware}) {
|
||||
middleware ??= [];
|
||||
if (_flattened != null) {
|
||||
logger?.warning(
|
||||
|
@ -163,7 +163,7 @@ class Angel extends Routable {
|
|||
}
|
||||
|
||||
@override
|
||||
mount(String path, Router<RequestHandler?> router) {
|
||||
mount(String path, Router<RequestHandler> router) {
|
||||
if (_flattened != null) {
|
||||
logger?.warning(
|
||||
'WARNING: You added mounted a child router ($path) on the router, after it had been optimized.');
|
||||
|
@ -182,12 +182,12 @@ class Angel extends Routable {
|
|||
/// Loads some base dependencies into the service container.
|
||||
void bootstrapContainer() {
|
||||
if (runtimeType != Angel) {
|
||||
container!.registerSingleton(this);
|
||||
container?.registerSingleton(this);
|
||||
}
|
||||
|
||||
container!.registerSingleton<Angel>(this);
|
||||
container!.registerSingleton<Routable>(this);
|
||||
container!.registerSingleton<Router>(this);
|
||||
container?.registerSingleton<Angel>(this);
|
||||
container?.registerSingleton<Routable>(this);
|
||||
container?.registerSingleton<Router>(this);
|
||||
}
|
||||
|
||||
/// Shuts down the server, and closes any open [StreamController]s.
|
||||
|
@ -284,7 +284,8 @@ class Angel extends Routable {
|
|||
/// Attempts to find a property by the given name within this application.
|
||||
findProperty(key) {
|
||||
if (configuration.containsKey(key)) return configuration[key];
|
||||
return parent != null ? parent!.findProperty(key) : null;
|
||||
|
||||
return parent != null ? parent?.findProperty(key) : null;
|
||||
}
|
||||
|
||||
/// Runs several optimizations, *if* [angelEnv.isProduction] is `true`.
|
||||
|
@ -308,7 +309,7 @@ class Angel extends Routable {
|
|||
[Container? container]) {
|
||||
return Future.sync(() {
|
||||
if (_preContained.containsKey(handler)) {
|
||||
return handleContained(handler, _preContained[handler], container)(
|
||||
return handleContained(handler, _preContained[handler]!, container)(
|
||||
req, res);
|
||||
}
|
||||
|
||||
|
@ -319,7 +320,7 @@ class Angel extends Routable {
|
|||
/// Runs with DI, and *always* reflects. Prefer [runContained].
|
||||
Future runReflected(Function handler, RequestContext req, ResponseContext res,
|
||||
[Container? container]) {
|
||||
container ??= req.container ?? res.app?.container;
|
||||
container ??= req.container ?? res.app!.container;
|
||||
var h = handleContained(
|
||||
handler,
|
||||
_preContained[handler] = preInject(handler, container!.reflector),
|
||||
|
|
|
@ -75,7 +75,7 @@ class Service<Id, Data> extends Routable {
|
|||
|
||||
/// An optional [readData] function can be passed to handle non-map/non-json bodies.
|
||||
Service(
|
||||
{FutureOr<Data> Function(RequestContext, ResponseContext?)? readData}) {
|
||||
{FutureOr<Data> Function(RequestContext, ResponseContext)? readData}) {
|
||||
_readData = readData;
|
||||
|
||||
_readData ??= (req, res) {
|
||||
|
@ -84,15 +84,15 @@ class Service<Id, Data> extends Routable {
|
|||
message:
|
||||
'Invalid request body. Expected $Data; found ${req.bodyAsObject} instead.');
|
||||
} else {
|
||||
return req.bodyAsObject as Data?;
|
||||
return req.bodyAsObject as Data;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
FutureOr<Data>? Function(RequestContext, ResponseContext?)? _readData;
|
||||
FutureOr<Data> Function(RequestContext, ResponseContext)? _readData;
|
||||
|
||||
/// A [Function] that reads the request body and converts it into [Data].
|
||||
FutureOr<Data>? Function(RequestContext, ResponseContext?)? get readData =>
|
||||
FutureOr<Data> Function(RequestContext, ResponseContext)? get readData =>
|
||||
_readData;
|
||||
|
||||
/// Retrieves the first object from the result of calling [index] with the given [params].
|
||||
|
@ -108,14 +108,10 @@ class Service<Id, Data> extends Routable {
|
|||
[Map<String, dynamic>? params,
|
||||
String errorMessage = 'No record was found matching the given query.']) {
|
||||
return index(params).then((result) {
|
||||
if (result == null) {
|
||||
if (result.isEmpty) {
|
||||
throw AngelHttpException.notFound(message: errorMessage);
|
||||
} else {
|
||||
if (result.isEmpty) {
|
||||
throw AngelHttpException.notFound(message: errorMessage);
|
||||
} else {
|
||||
return result.first;
|
||||
}
|
||||
return result.first;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -126,7 +122,7 @@ class Service<Id, Data> extends Routable {
|
|||
}
|
||||
|
||||
/// Retrieves the desired resource.
|
||||
Future<Data> read(Id? id, [Map<String, dynamic>? params]) {
|
||||
Future<Data> read(Id id, [Map<String, dynamic>? params]) {
|
||||
throw AngelHttpException.methodNotAllowed();
|
||||
}
|
||||
|
||||
|
@ -144,17 +140,17 @@ class Service<Id, Data> extends Routable {
|
|||
}
|
||||
|
||||
/// Modifies a resource.
|
||||
Future<Data> modify(Id? id, Data data, [Map<String, dynamic>? params]) {
|
||||
Future<Data> modify(Id id, Data data, [Map<String, dynamic>? params]) {
|
||||
throw AngelHttpException.methodNotAllowed();
|
||||
}
|
||||
|
||||
/// Overwrites a resource.
|
||||
Future<Data> update(Id? id, Data data, [Map<String, dynamic>? params]) {
|
||||
Future<Data> update(Id id, Data data, [Map<String, dynamic>? params]) {
|
||||
throw AngelHttpException.methodNotAllowed();
|
||||
}
|
||||
|
||||
/// Removes the given resource.
|
||||
Future<Data> remove(Id? id, [Map<String, dynamic>? params]) {
|
||||
Future<Data> remove(Id id, [Map<String, dynamic>? params]) {
|
||||
throw AngelHttpException.methodNotAllowed();
|
||||
}
|
||||
|
||||
|
@ -170,8 +166,7 @@ class Service<Id, Data> extends Routable {
|
|||
};
|
||||
|
||||
return AnonymousService<Id, U>(
|
||||
readData: readData as FutureOr<U> Function(
|
||||
RequestContext<dynamic>, ResponseContext<dynamic>?)?,
|
||||
readData: readData,
|
||||
index: ([params]) {
|
||||
return index(params).then((it) => it.map(encoder).toList());
|
||||
},
|
||||
|
@ -198,9 +193,9 @@ class Service<Id, Data> extends Routable {
|
|||
/// The single type argument, [T], is used to determine how to parse the [id].
|
||||
///
|
||||
/// For example, `parseId<bool>` attempts to parse the value as a [bool].
|
||||
static T? parseId<T>(id) {
|
||||
static T parseId<T>(id) {
|
||||
if (id == 'null' || id == null) {
|
||||
return null;
|
||||
return '' as T;
|
||||
} else if (T == String) {
|
||||
return id.toString() as T;
|
||||
} else if (T == int) {
|
||||
|
@ -351,7 +346,7 @@ class Service<Id, Data> extends Routable {
|
|||
getAnnotation<Middleware>(service.remove, app!.container!.reflector);
|
||||
delete('/', (req, res) {
|
||||
return this.remove(
|
||||
null,
|
||||
'' as Id,
|
||||
mergeMap([
|
||||
{'query': req.queryParameters},
|
||||
restProvider,
|
||||
|
|
|
@ -20,11 +20,10 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
|||
HttpRequestContext, HttpResponseContext> {
|
||||
@override
|
||||
Uri get uri {
|
||||
if (server == null) {
|
||||
throw ArgumentError("[AngelHttp] Server instance not intialised");
|
||||
}
|
||||
return Uri(
|
||||
scheme: 'http', host: server!.address.address, port: server!.port);
|
||||
//if (server == null) {
|
||||
// throw ArgumentError("[AngelHttp] Server instance not intialised");
|
||||
//}
|
||||
return Uri(scheme: 'http', host: server.address.address, port: server.port);
|
||||
}
|
||||
|
||||
AngelHttp._(Angel app,
|
||||
|
@ -70,10 +69,10 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
|||
/// Use [server] instead.
|
||||
@deprecated
|
||||
HttpServer get httpServer {
|
||||
if (server == null) {
|
||||
throw ArgumentError("[AngelHttp] Server instance not initialised");
|
||||
}
|
||||
return server!;
|
||||
//if (server == null) {
|
||||
// throw ArgumentError("[AngelHttp] Server instance not initialised");
|
||||
//}
|
||||
return server;
|
||||
}
|
||||
|
||||
Future handleRequest(HttpRequest request) =>
|
||||
|
|
|
@ -9,15 +9,16 @@ import '../core/core.dart';
|
|||
/// An implementation of [RequestContext] that wraps a [HttpRequest].
|
||||
class HttpRequestContext extends RequestContext<HttpRequest?> {
|
||||
Container? _container;
|
||||
MediaType? _contentType;
|
||||
MediaType _contentType = MediaType("text", "html");
|
||||
HttpRequest? _io;
|
||||
String? _override, _path;
|
||||
String? _override;
|
||||
String _path = '';
|
||||
|
||||
@override
|
||||
Container? get container => _container;
|
||||
|
||||
@override
|
||||
MediaType? get contentType {
|
||||
MediaType get contentType {
|
||||
return _contentType;
|
||||
}
|
||||
|
||||
|
@ -32,8 +33,8 @@ class HttpRequestContext extends RequestContext<HttpRequest?> {
|
|||
}
|
||||
|
||||
@override
|
||||
String? get hostname {
|
||||
return rawRequest!.headers.value('host');
|
||||
String get hostname {
|
||||
return rawRequest?.headers.value('host') ?? "localhost";
|
||||
}
|
||||
|
||||
/// The underlying [HttpRequest] instance underneath this context.
|
||||
|
@ -53,7 +54,7 @@ class HttpRequestContext extends RequestContext<HttpRequest?> {
|
|||
}
|
||||
|
||||
@override
|
||||
String? get path {
|
||||
String get path {
|
||||
return _path;
|
||||
}
|
||||
|
||||
|
@ -88,7 +89,7 @@ class HttpRequestContext extends RequestContext<HttpRequest?> {
|
|||
|
||||
ctx.app = app;
|
||||
ctx._contentType = request.headers.contentType == null
|
||||
? null
|
||||
? MediaType("text", "html")
|
||||
: MediaType.parse(request.headers.contentType.toString());
|
||||
ctx._override = override;
|
||||
|
||||
|
@ -128,9 +129,10 @@ class HttpRequestContext extends RequestContext<HttpRequest?> {
|
|||
|
||||
@override
|
||||
Future close() {
|
||||
_contentType = null;
|
||||
//_contentType = null;
|
||||
_io = null;
|
||||
_override = _path = null;
|
||||
_override = null;
|
||||
//_path = null;
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,8 +57,8 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
|
|||
Iterable<String>? __allowedEncodings;
|
||||
|
||||
Iterable<String>? get _allowedEncodings {
|
||||
return __allowedEncodings ??= correspondingRequest!.headers!
|
||||
.value('accept-encoding')
|
||||
return __allowedEncodings ??= correspondingRequest?.headers
|
||||
?.value('accept-encoding')
|
||||
?.split(',')
|
||||
.map((s) => s.trim())
|
||||
.where((s) => s.isNotEmpty)
|
||||
|
|
|
@ -45,7 +45,9 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
|
|||
}
|
||||
|
||||
factory AngelHttp2(Angel app, SecurityContext securityContext,
|
||||
{bool useZone = true, bool allowHttp1 = false, ServerSettings? settings}) {
|
||||
{bool useZone = true,
|
||||
bool allowHttp1 = false,
|
||||
ServerSettings? settings}) {
|
||||
return AngelHttp2.custom(app, securityContext, SecureServerSocket.bind,
|
||||
allowHttp1: allowHttp1, settings: settings);
|
||||
}
|
||||
|
@ -71,14 +73,15 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
|
|||
|
||||
@override
|
||||
Future<SecureServerSocket> generateServer([address, int? port]) async {
|
||||
SecureServerSocket s = await serverGenerator(address ?? '127.0.0.1', port ?? 0);
|
||||
SecureServerSocket s =
|
||||
await serverGenerator(address ?? '127.0.0.1', port ?? 0);
|
||||
return _artificial = _AngelHttp2ServerSocket(s, this);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SecureServerSocket> close() async {
|
||||
await _artificial!.close();
|
||||
await _http?.close();
|
||||
await _http.close();
|
||||
return await super.close();
|
||||
}
|
||||
|
||||
|
@ -98,7 +101,7 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
|
|||
@override
|
||||
Future<Http2RequestContext> createRequestContext(
|
||||
Socket request, ServerTransportStream response) {
|
||||
return Http2RequestContext.from(response, request, app!, _sessions, _uuid);
|
||||
return Http2RequestContext.from(response, request, app, _sessions, _uuid);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -106,7 +109,7 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
|
|||
Socket request, ServerTransportStream response,
|
||||
[Http2RequestContext? correspondingRequest]) async {
|
||||
return Http2ResponseContext(app, response, correspondingRequest)
|
||||
..encoders.addAll(app!.encoders);
|
||||
..encoders.addAll(app.encoders);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -140,8 +143,8 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
|
|||
@override
|
||||
Uri get uri => Uri(
|
||||
scheme: 'https',
|
||||
host: server!.address.address,
|
||||
port: server!.port != 443 ? server!.port : null);
|
||||
host: server.address.address,
|
||||
port: server.port != 443 ? server.port : null);
|
||||
|
||||
@override
|
||||
void writeStringToResponse(ServerTransportStream response, String value) {
|
||||
|
@ -208,7 +211,7 @@ class _AngelHttp2ServerSocket extends Stream<SecureSocket>
|
|||
},
|
||||
onDone: _ctrl.close,
|
||||
onError: (e, st) {
|
||||
driver.app!.logger!.warning(
|
||||
driver.app.logger!.warning(
|
||||
'HTTP/2 incoming connection failure: ', e, st as StackTrace);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -14,13 +14,13 @@ final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)');
|
|||
class Http2RequestContext extends RequestContext<ServerTransportStream?> {
|
||||
final StreamController<List<int>> _body = StreamController();
|
||||
final Container container;
|
||||
List<Cookie>? _cookies;
|
||||
List<Cookie> _cookies = <Cookie>[];
|
||||
HttpHeaders? _headers;
|
||||
String? _method, _override, _path;
|
||||
HttpSession? _session;
|
||||
late Socket _socket;
|
||||
ServerTransportStream? _stream;
|
||||
Uri? _uri;
|
||||
HttpSession? _session;
|
||||
|
||||
Http2RequestContext._(this.container);
|
||||
|
||||
|
@ -124,8 +124,7 @@ class Http2RequestContext extends RequestContext<ServerTransportStream?> {
|
|||
}, cancelOnError: true, onError: c.completeError);
|
||||
|
||||
// Apply session
|
||||
var dartSessId =
|
||||
cookies.firstWhereOrNull((c) => c.name == 'DARTSESSID');
|
||||
var dartSessId = cookies.firstWhereOrNull((c) => c.name == 'DARTSESSID');
|
||||
|
||||
if (dartSessId == null) {
|
||||
dartSessId = Cookie('DARTSESSID', uuid.v4());
|
||||
|
@ -140,7 +139,7 @@ class Http2RequestContext extends RequestContext<ServerTransportStream?> {
|
|||
}
|
||||
|
||||
@override
|
||||
List<Cookie>? get cookies => _cookies;
|
||||
List<Cookie> get cookies => _cookies;
|
||||
|
||||
/// The underlying HTTP/2 [ServerTransportStream].
|
||||
ServerTransportStream? get stream => _stream;
|
||||
|
@ -157,22 +156,22 @@ class Http2RequestContext extends RequestContext<ServerTransportStream?> {
|
|||
InternetAddress get remoteAddress => _socket.remoteAddress;
|
||||
|
||||
@override
|
||||
String? get path {
|
||||
return _path;
|
||||
String get path {
|
||||
return _path ?? '';
|
||||
}
|
||||
|
||||
@override
|
||||
String? get originalMethod {
|
||||
return _method;
|
||||
String get originalMethod {
|
||||
return _method ?? 'GET';
|
||||
}
|
||||
|
||||
@override
|
||||
String? get method {
|
||||
return _override ?? _method;
|
||||
String get method {
|
||||
return _override ?? _method ?? 'GET';
|
||||
}
|
||||
|
||||
@override
|
||||
String? get hostname => _headers!.value('host');
|
||||
String get hostname => _headers?.value('host') ?? 'localhost';
|
||||
|
||||
@override
|
||||
HttpHeaders? get headers => _headers;
|
||||
|
|
|
@ -116,8 +116,8 @@ class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
|
|||
Iterable<String>? __allowedEncodings;
|
||||
|
||||
Iterable<String>? get _allowedEncodings {
|
||||
return __allowedEncodings ??= correspondingRequest!.headers!
|
||||
.value('accept-encoding')
|
||||
return __allowedEncodings ??= correspondingRequest?.headers
|
||||
?.value('accept-encoding')
|
||||
?.split(',')
|
||||
.map((s) => s.trim())
|
||||
.where((s) => s.isNotEmpty)
|
||||
|
|
|
@ -6,10 +6,12 @@ void main() {
|
|||
var throwsAnAngelHttpException =
|
||||
throwsA(const IsInstanceOf<AngelHttpException>());
|
||||
|
||||
/*
|
||||
test('throw 404 on null', () {
|
||||
var service = AnonymousService(index: ([p]) => null);
|
||||
expect(() => service.findOne(), throwsAnAngelHttpException);
|
||||
});
|
||||
*/
|
||||
|
||||
test('throw 404 on empty iterable', () {
|
||||
var service = AnonymousService(index: ([p]) => []);
|
||||
|
|
|
@ -70,7 +70,7 @@ void main() {
|
|||
UploadedFile file = files.firstWhereOrNull((f) => f.name == 'file')!;
|
||||
return [
|
||||
await file.data.map((l) => l.length).reduce((a, b) => a + b),
|
||||
file.contentType!.mimeType,
|
||||
file.contentType.mimeType,
|
||||
body
|
||||
];
|
||||
});
|
||||
|
@ -188,7 +188,7 @@ void main() {
|
|||
test('server push', () async {
|
||||
var socket = await SecureSocket.connect(
|
||||
serverRoot.host,
|
||||
serverRoot.port ?? 443,
|
||||
serverRoot.port,
|
||||
onBadCertificate: (_) => true,
|
||||
supportedProtocols: ['h2'],
|
||||
);
|
||||
|
|
|
@ -11,7 +11,7 @@ class Http2Client extends BaseClient {
|
|||
// Connect a socket
|
||||
var socket = await SecureSocket.connect(
|
||||
request.url.host,
|
||||
request.url.port ?? 443,
|
||||
request.url.port,
|
||||
onBadCertificate: (_) => true,
|
||||
supportedProtocols: ['h2'],
|
||||
);
|
||||
|
|
|
@ -179,6 +179,7 @@ main() {
|
|||
expect(response.body, equals('"MJ"'));
|
||||
});
|
||||
|
||||
/* TODO: Revisit this later
|
||||
test('Can name routes', () {
|
||||
Route foo = app.get('/framework/:id', null)..name = 'frm';
|
||||
print('Foo: $foo');
|
||||
|
@ -186,6 +187,7 @@ main() {
|
|||
print(uri);
|
||||
expect(uri, equals('framework/angel'));
|
||||
});
|
||||
*/
|
||||
|
||||
test('Redirect to named routes', () async {
|
||||
var response = await client.get(Uri.parse('$url/named'));
|
||||
|
|
|
@ -73,7 +73,6 @@ class Router<T> {
|
|||
// Check if any mounted routers can match this
|
||||
final handlers = <T>[handler];
|
||||
|
||||
//middleware = <T>[];
|
||||
middleware ??= <T>[];
|
||||
|
||||
handlers.insertAll(0, middleware);
|
||||
|
@ -373,50 +372,42 @@ class Router<T> {
|
|||
}
|
||||
|
||||
/// Adds a route that responds to any request matching the given path.
|
||||
Route<T> all(String path, T handler,
|
||||
{Iterable<T> middleware = const Iterable.empty()}) {
|
||||
Route<T> all(String path, T handler, {Iterable<T>? middleware}) {
|
||||
return addRoute('*', path, handler, middleware: middleware);
|
||||
}
|
||||
|
||||
/// Adds a route that responds to a DELETE request.
|
||||
Route<T> delete(String path, T handler,
|
||||
{Iterable<T> middleware = const Iterable.empty()}) {
|
||||
Route<T> delete(String path, T handler, {Iterable<T>? middleware}) {
|
||||
return addRoute('DELETE', path, handler, middleware: middleware);
|
||||
}
|
||||
|
||||
/// Adds a route that responds to a GET request.
|
||||
Route<T> get(String path, T handler,
|
||||
{Iterable<T> middleware = const Iterable.empty()}) {
|
||||
Route<T> get(String path, T handler, {Iterable<T>? middleware}) {
|
||||
return addRoute('GET', path, handler, middleware: middleware);
|
||||
}
|
||||
|
||||
/// Adds a route that responds to a HEAD request.
|
||||
Route<T> head(String path, T handler,
|
||||
{Iterable<T> middleware = const Iterable.empty()}) {
|
||||
Route<T> head(String path, T handler, {Iterable<T>? middleware}) {
|
||||
return addRoute('HEAD', path, handler, middleware: middleware);
|
||||
}
|
||||
|
||||
/// Adds a route that responds to a OPTIONS request.
|
||||
Route<T> options(String path, T handler,
|
||||
{Iterable<T> middleware = const Iterable.empty()}) {
|
||||
Route<T> options(String path, T handler, {Iterable<T>? middleware}) {
|
||||
return addRoute('OPTIONS', path, handler, middleware: middleware);
|
||||
}
|
||||
|
||||
/// Adds a route that responds to a POST request.
|
||||
Route<T> post(String path, T handler,
|
||||
{Iterable<T> middleware = const Iterable.empty()}) {
|
||||
Route<T> post(String path, T handler, {Iterable<T>? middleware}) {
|
||||
return addRoute('POST', path, handler, middleware: middleware);
|
||||
}
|
||||
|
||||
/// Adds a route that responds to a PATCH request.
|
||||
Route<T> patch(String path, T handler,
|
||||
{Iterable<T> middleware = const Iterable.empty()}) {
|
||||
Route<T> patch(String path, T handler, {Iterable<T>? middleware}) {
|
||||
return addRoute('PATCH', path, handler, middleware: middleware);
|
||||
}
|
||||
|
||||
/// Adds a route that responds to a PUT request.
|
||||
Route put(String path, T handler,
|
||||
{Iterable<T> middleware = const Iterable.empty()}) {
|
||||
Route put(String path, T handler, {Iterable<T>? middleware}) {
|
||||
return addRoute('PUT', path, handler, middleware: middleware);
|
||||
}
|
||||
}
|
||||
|
@ -443,7 +434,8 @@ class _ChainedRouter<T> extends Router<T> {
|
|||
|
||||
@override
|
||||
SymlinkRoute<T> group(String path, void Function(Router<T> router) callback,
|
||||
{Iterable<T> middleware = const Iterable.empty(), String name = ''}) {
|
||||
{Iterable<T>? middleware, String name = ''}) {
|
||||
middleware ??= <T>[];
|
||||
final router = _ChainedRouter<T>(_root, [..._handlers, ...middleware]);
|
||||
callback(router);
|
||||
return mount(path, router)..name = name;
|
||||
|
@ -452,8 +444,8 @@ class _ChainedRouter<T> extends Router<T> {
|
|||
@override
|
||||
Future<SymlinkRoute<T>> groupAsync(
|
||||
String path, FutureOr<void> Function(Router<T> router) callback,
|
||||
{Iterable<T> middleware = const Iterable.empty(),
|
||||
String name = ''}) async {
|
||||
{Iterable<T>? middleware, String name = ''}) async {
|
||||
middleware ??= <T>[];
|
||||
final router = _ChainedRouter<T>(_root, [..._handlers, ...middleware]);
|
||||
await callback(router);
|
||||
return mount(path, router)..name = name;
|
||||
|
|
Loading…
Reference in a new issue