platform/lib/src/core/request_context.dart

257 lines
8.1 KiB
Dart
Raw Normal View History

library angel_framework.http.request_context;
2016-10-22 20:41:36 +00:00
import 'dart:async';
import 'dart:io';
2017-09-24 19:43:14 +00:00
import 'dart:mirrors';
import 'package:body_parser/body_parser.dart';
2018-02-07 04:59:05 +00:00
import 'package:meta/meta.dart';
2017-11-28 18:14:50 +00:00
import 'package:path/path.dart' as p;
2017-10-10 16:55:42 +00:00
import 'metadata.dart';
2017-09-24 19:43:14 +00:00
import 'response_context.dart';
import 'routable.dart';
2017-03-02 04:04:37 +00:00
import 'server.dart' show Angel;
2017-09-24 19:43:14 +00:00
part 'injection.dart';
2016-04-18 03:27:23 +00:00
/// A convenience wrapper around an incoming HTTP request.
2018-02-07 04:59:05 +00:00
abstract class RequestContext {
2017-11-28 18:14:50 +00:00
String _acceptHeaderCache, _extensionCache;
2017-07-10 23:08:05 +00:00
bool _acceptsAllCache;
2016-10-22 20:41:36 +00:00
BodyParseResult _body;
Map _provisionalQuery;
final Map properties = {};
2016-10-22 20:41:36 +00:00
2017-02-23 00:37:15 +00:00
/// Additional params to be passed to services.
final Map serviceParams = {};
2016-04-18 03:27:23 +00:00
/// The [Angel] instance that is responding to this request.
2017-03-02 04:04:37 +00:00
Angel app;
2016-04-18 03:27:23 +00:00
/// Any cookies sent with this request.
2018-02-07 04:59:05 +00:00
List<Cookie> get cookies;
2016-04-18 03:27:23 +00:00
/// All HTTP headers sent with this request.
2018-02-07 04:59:05 +00:00
HttpHeaders get headers;
2016-04-18 03:27:23 +00:00
/// The requested hostname.
2018-02-07 04:59:05 +00:00
String get hostname;
2016-11-23 19:50:17 +00:00
2017-09-24 19:43:14 +00:00
final Map _injections = {};
2017-11-28 18:14:50 +00:00
Map _injectionsCache;
2017-09-24 19:43:14 +00:00
/// A [Map] of singletons injected via [inject]. *Read-only*.
2017-11-28 18:14:50 +00:00
Map get injections => _injectionsCache ??= new Map.unmodifiable(_injections);
2016-11-23 19:50:17 +00:00
2018-02-07 04:59:05 +00:00
/// This feature does not map to other adapters (i.e. HTTP/2), so it will be removed in a future version.
@deprecated
HttpRequest get io;
2016-04-18 03:27:23 +00:00
/// The user's IP.
String get ip => remoteAddress.address;
/// This request's HTTP method.
2017-03-02 22:06:02 +00:00
///
/// This may have been processed by an override. See [originalMethod] to get the real method.
2018-02-07 04:59:05 +00:00
String get method;
2017-03-02 22:06:02 +00:00
/// The original HTTP verb sent to the server.
2018-02-07 04:59:05 +00:00
String get originalMethod;
2016-04-18 03:27:23 +00:00
2017-03-28 23:29:22 +00:00
StateError _unparsed(String type, String caps) => new StateError(
'Cannot get the $type of an unparsed request. Use lazy${caps}() instead.');
2016-04-18 03:27:23 +00:00
/// All post data submitted to the server.
2017-03-28 23:29:22 +00:00
///
/// If you are lazy-parsing request bodies, but have not manually [parse]d this one,
/// then an error will be thrown.
2017-07-09 16:50:46 +00:00
///
2017-03-28 23:29:22 +00:00
/// **If you are writing a plug-in, use [lazyBody] instead.**
Map get body {
if (_body == null)
throw _unparsed('body', 'Body');
else
return _body.body;
}
2016-04-18 03:27:23 +00:00
/// The content type of an incoming request.
2018-02-07 04:59:05 +00:00
ContentType get contentType;
2016-04-18 03:27:23 +00:00
/// Any and all files sent to the server with this request.
2017-03-28 23:29:22 +00:00
///
/// If you are lazy-parsing request bodies, but have not manually [parse]d this one,
/// then an error will be thrown.
2017-07-09 16:50:46 +00:00
///
2017-03-28 23:29:22 +00:00
/// **If you are writing a plug-in, use [lazyFiles] instead.**
List<FileUploadInfo> get files {
if (_body == null)
throw _unparsed('query', 'Files');
else
return _body.files;
}
2016-04-18 03:27:23 +00:00
2017-01-14 13:56:14 +00:00
/// The original body bytes sent with this request. May be empty.
2017-03-28 23:29:22 +00:00
///
/// If you are lazy-parsing request bodies, but have not manually [parse]d this one,
/// then an error will be thrown.
2017-07-09 16:50:46 +00:00
///
2017-03-28 23:29:22 +00:00
/// **If you are writing a plug-in, use [lazyOriginalBuffer] instead.**
List<int> get originalBuffer {
if (_body == null)
throw _unparsed('original buffer', 'OriginalBuffer');
else
return _body.originalBuffer ?? [];
}
2017-01-14 13:56:14 +00:00
2016-04-18 03:27:23 +00:00
/// The URL parameters extracted from the request URI.
Map params = {};
/// The requested path.
2018-02-07 04:59:05 +00:00
String get path;
2016-04-18 03:27:23 +00:00
/// The parsed request query string.
2017-03-28 23:29:22 +00:00
///
/// If you are lazy-parsing request bodies, but have not manually [parse]d this one,
/// then [uri].query will be returned.
2017-07-09 16:50:46 +00:00
///
/// **If you are writing a plug-in, consider using [lazyQuery] instead.**
2017-03-28 23:29:22 +00:00
Map get query {
if (_body == null)
return _provisionalQuery ??= new Map.from(uri.queryParameters);
2017-03-28 23:29:22 +00:00
else
return _body.query;
}
2016-04-18 03:27:23 +00:00
/// The remote address requesting this resource.
2018-02-07 04:59:05 +00:00
InternetAddress get remoteAddress;
2016-04-18 03:27:23 +00:00
/// The user's HTTP session.
2018-02-07 04:59:05 +00:00
HttpSession get session;
2016-10-22 20:41:36 +00:00
/// The [Uri] instance representing the path this request is responding to.
2018-02-07 04:59:05 +00:00
Uri get uri;
2016-04-18 03:27:23 +00:00
/// Is this an **XMLHttpRequest**?
2018-02-07 04:59:05 +00:00
bool get xhr;
2016-04-18 03:27:23 +00:00
2017-11-28 18:14:50 +00:00
/// 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);
2017-09-24 19:43:14 +00:00
/// Grabs an object by key or type from [params], [_injections], or
2017-01-20 22:11:20 +00:00
/// [app].container. Use this to perform dependency injection
/// within a service hook.
2017-02-25 20:57:28 +00:00
T grab<T>(key) {
2017-01-20 22:11:20 +00:00
if (params.containsKey(key))
return params[key];
2017-09-24 19:43:14 +00:00
else if (_injections.containsKey(key))
return _injections[key];
2017-02-26 21:31:09 +00:00
else if (properties.containsKey(key))
return properties[key];
2017-07-09 16:50:46 +00:00
else {
var prop = app?.findProperty(key);
if (prop != null)
return prop;
else if (key is Type) {
try {
return app.container.make(key);
} catch (e) {
return null;
}
} else
2017-01-20 22:11:20 +00:00
return null;
2017-07-09 16:50:46 +00:00
}
2017-01-20 22:11:20 +00:00
}
2017-09-24 19:43:14 +00:00
/// Shorthand to add to [_injections].
2016-12-23 20:53:39 +00:00
void inject(type, value) {
2017-10-28 08:50:16 +00:00
if (!app.isProduction && type is Type) {
if (!reflect(value).type.isAssignableTo(reflectType(type)))
2017-12-06 14:46:35 +00:00
throw new ArgumentError(
'Cannot inject $value (${value.runtimeType}) as an instance of $type.');
2017-10-28 08:50:16 +00:00
}
2017-09-24 19:43:14 +00:00
_injections[type] = value;
2017-11-28 18:14:50 +00:00
_injectionsCache = null;
2016-11-23 19:50:17 +00:00
}
2017-03-28 23:29:22 +00:00
2017-07-10 23:08:05 +00:00
/// Returns `true` if the client's `Accept` header indicates that the given [contentType] is considered a valid response.
///
/// You cannot provide a `null` [contentType].
/// If the `Accept` header's value is `*/*`, this method will always return `true`.
2017-11-28 18:14:50 +00:00
/// To ignore the wildcard (`*/*`), pass [strict] as `true`.
2017-07-10 23:08:05 +00:00
///
/// [contentType] can be either of the following:
/// * A [ContentType], in which case the `Accept` header will be compared against its `mimeType` property.
/// * Any other Dart value, in which case the `Accept` header will be compared against the result of a `toString()` call.
2017-11-28 18:14:50 +00:00
bool accepts(contentType, {bool strict: false}) {
2017-07-10 23:08:05 +00:00
var contentTypeString = contentType is ContentType
? contentType.mimeType
: contentType?.toString();
2018-02-07 04:59:05 +00:00
// Change to assert
2017-07-10 23:08:05 +00:00
if (contentTypeString == null)
throw new ArgumentError(
'RequestContext.accepts expects the `contentType` parameter to NOT be null.');
_acceptHeaderCache ??= headers.value(HttpHeaders.ACCEPT);
if (_acceptHeaderCache == null)
return false;
2017-11-28 18:14:50 +00:00
else if (strict != true && _acceptHeaderCache.contains('*/*'))
2017-07-10 23:08:05 +00:00
return true;
else
return _acceptHeaderCache.contains(contentTypeString);
}
/// Returns as `true` if the client's `Accept` header indicates that it will accept any response content type.
bool get acceptsAll => _acceptsAllCache ??= accepts('*/*');
2017-03-28 23:29:22 +00:00
/// Retrieves the request body if it has already been parsed, or lazy-parses it before returning the body.
Future<Map> lazyBody() => parse().then((b) => b.body);
/// Retrieves the request files if it has already been parsed, or lazy-parses it before returning the files.
Future<List<FileUploadInfo>> lazyFiles() => parse().then((b) => b.files);
2017-03-29 00:36:54 +00:00
/// Retrieves the original request buffer if it has already been parsed, or lazy-parses it before returning the files.
2017-03-28 23:29:22 +00:00
///
/// This will return an empty `List` if you have not enabled `storeOriginalBuffer` on your [app] instance.
Future<List<int>> lazyOriginalBuffer() =>
parse().then((b) => b.originalBuffer);
/// Retrieves the request body if it has already been parsed, or lazy-parses it before returning the query.
///
/// If [forceParse] is not `true`, then [uri].query will be returned, and no parsing will be performed.
Future<Map<String, dynamic>> lazyQuery({bool forceParse: false}) {
if (_body == null && forceParse != true)
2017-03-29 00:36:54 +00:00
return new Future.value(uri.queryParameters);
2017-03-28 23:29:22 +00:00
else
return parse().then((b) => b.query);
}
/// Manually parses the request body, if it has not already been parsed.
Future<BodyParseResult> parse() async {
if (_body != null)
return _body;
else
_provisionalQuery = null;
2018-02-07 04:59:05 +00:00
return _body = await parseOnce();
2017-03-28 23:29:22 +00:00
}
2017-10-28 08:50:16 +00:00
2018-02-07 04:59:05 +00:00
/// Override this method to one-time parse an incoming request.
@virtual
Future<BodyParseResult> parseOnce();
2017-10-28 08:50:16 +00:00
/// Disposes of all resources.
Future close() async {
_body = null;
_acceptsAllCache = null;
_acceptHeaderCache = null;
_provisionalQuery?.clear();
properties.clear();
_injections.clear();
serviceParams.clear();
params.clear();
}
2016-10-22 20:41:36 +00:00
}