platform/lib/src/http/server.dart

546 lines
17 KiB
Dart
Raw Normal View History

library angel_framework.http.server;
import 'dart:async';
import 'dart:io';
2016-09-17 16:12:25 +00:00
import 'dart:mirrors';
2016-11-28 00:49:27 +00:00
import 'package:angel_route/angel_route.dart';
import 'angel_base.dart';
import 'angel_http_exception.dart';
import 'controller.dart';
2016-12-10 14:12:07 +00:00
import 'fatal_error.dart';
import 'request_context.dart';
import 'response_context.dart';
import 'routable.dart';
import 'service.dart';
2016-09-17 16:12:25 +00:00
export 'package:container/container.dart';
2016-02-28 13:11:17 +00:00
2016-10-22 20:41:36 +00:00
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
2016-04-18 03:27:23 +00:00
/// A function that binds an [Angel] server to an Internet address and port.
2016-02-28 13:11:17 +00:00
typedef Future<HttpServer> ServerGenerator(InternetAddress address, int port);
/// Handles an [AngelHttpException].
2016-09-17 16:12:25 +00:00
typedef Future AngelErrorHandler(
AngelHttpException err, RequestContext req, ResponseContext res);
2017-02-25 00:16:31 +00:00
/// A function that configures an [Angel] server in some way.
typedef Future AngelConfigurer(Angel app);
2016-02-28 13:11:17 +00:00
/// A powerful real-time/REST/MVC server class.
class Angel extends AngelBase {
2016-12-10 14:12:07 +00:00
StreamController<HttpRequest> _afterProcessed =
new StreamController<HttpRequest>.broadcast();
StreamController<HttpRequest> _beforeProcessed =
new StreamController<HttpRequest>.broadcast();
StreamController<AngelFatalError> _fatalErrorStream =
new StreamController<AngelFatalError>.broadcast();
StreamController<Controller> _onController =
new StreamController<Controller>.broadcast();
2016-12-21 18:18:26 +00:00
final List<Angel> _children = [];
Angel _parent;
2016-11-24 07:12:53 +00:00
ServerGenerator _serverGenerator = HttpServer.bind;
2016-09-17 16:12:25 +00:00
2016-12-31 01:46:41 +00:00
final Map<dynamic, InjectionRequest> _preContained = {};
2017-03-02 22:06:02 +00:00
/// Determines whether to allow HTTP request method overrides.
bool allowMethodOverrides = true;
2016-09-17 16:12:25 +00:00
/// Fired after a request is processed. Always runs.
Stream<HttpRequest> get afterProcessed => _afterProcessed.stream;
2016-07-05 22:11:54 +00:00
/// Fired before a request is processed. Always runs.
Stream<HttpRequest> get beforeProcessed => _beforeProcessed.stream;
2016-07-05 22:11:54 +00:00
2016-12-21 18:18:26 +00:00
/// All child application mounted on this instance.
List<Angel> get children => new List<Angel>.unmodifiable(_children);
2016-09-19 06:52:21 +00:00
/// Fired on fatal errors.
2016-12-10 14:12:07 +00:00
Stream<AngelFatalError> get fatalErrorStream => _fatalErrorStream.stream;
2016-09-19 06:52:21 +00:00
2016-12-21 18:18:26 +00:00
/// Indicates whether the application is running in a production environment.
///
/// The criteria for this is the `ANGEL_ENV` environment variable being set to
/// `'production'`.
bool get isProduction => Platform.environment['ANGEL_ENV'] == 'production';
2016-07-05 22:11:54 +00:00
/// Fired whenever a controller is added to this instance.
///
/// **NOTE**: This is a broadcast stream.
Stream<Controller> get onController => _onController.stream;
2016-12-21 18:18:26 +00:00
/// Returns the parent instance of this application, if any.
Angel get parent => _parent;
2017-02-01 21:43:18 +00:00
/// Plug-ins to be called right before server startup.
///
/// If the server is never started, they will never be called.
final List<AngelConfigurer> justBeforeStart = [];
2016-12-19 01:38:23 +00:00
/// Always run before responses are sent.
///
/// These will only not run if an [AngelFatalError] occurs.
final List<RequestHandler> responseFinalizers = [];
2017-03-04 21:12:39 +00:00
/// The handler currently configured to run on [AngelHttpException]s.
AngelErrorHandler errorHandler =
2016-09-17 16:12:25 +00:00
(AngelHttpException e, req, ResponseContext res) {
2016-12-21 20:27:07 +00:00
res.headers[HttpHeaders.CONTENT_TYPE] = ContentType.HTML.toString();
2016-12-19 01:38:23 +00:00
res.statusCode = e.statusCode;
2016-07-03 22:23:55 +00:00
res.write("<!DOCTYPE html><html><head><title>${e.message}</title>");
res.write("</head><body><h1>${e.message}</h1><ul>");
2016-12-31 01:46:41 +00:00
for (String error in e.errors) {
res.write("<li>$error</li>");
}
2016-12-31 01:46:41 +00:00
res.write("</ul></body></html>");
res.end();
};
/// [RequestMiddleware] to be run before all requests.
2016-12-21 18:18:26 +00:00
final List before = [];
/// [RequestMiddleware] to be run after all requests.
2016-12-21 18:18:26 +00:00
final List after = [];
2016-04-18 03:27:23 +00:00
2016-06-21 22:56:04 +00:00
/// The native HttpServer running this instancce.
2016-04-18 03:27:23 +00:00
HttpServer httpServer;
2016-10-22 20:41:36 +00:00
/// Handles a server error.
2016-12-10 14:12:07 +00:00
_onError(e, [StackTrace st]) {
_fatalErrorStream.add(new AngelFatalError(error: e, stack: st));
2016-10-22 20:41:36 +00:00
}
void _printDebug(x) {
if (debug) print(x);
}
2016-06-21 22:56:04 +00:00
/// Starts the server.
///
/// Returns false on failure; otherwise, returns the HttpServer.
2016-09-17 16:12:25 +00:00
Future<HttpServer> startServer([InternetAddress address, int port]) async {
2017-01-20 22:40:48 +00:00
var host = address ?? InternetAddress.LOOPBACK_IP_V4;
2016-11-23 09:10:47 +00:00
this.httpServer = await _serverGenerator(host, port ?? 0);
2017-03-02 04:04:37 +00:00
for (var configurer in justBeforeStart) {
await configure(configurer);
}
2016-12-31 02:00:52 +00:00
preprocessRoutes();
2016-11-23 09:10:47 +00:00
return httpServer..listen(handleRequest);
}
2016-09-17 16:12:25 +00:00
/// Loads some base dependencies into the service container.
void bootstrapContainer() {
2017-01-20 22:11:20 +00:00
if (runtimeType != Angel) container.singleton(this, as: Angel);
2016-09-17 16:12:25 +00:00
container.singleton(this, as: AngelBase);
2016-11-23 19:50:17 +00:00
container.singleton(this, as: Routable);
container.singleton(this, as: Router);
2016-09-17 16:12:25 +00:00
container.singleton(this);
}
2017-03-06 00:52:16 +00:00
/// Shortcut for adding a middleware to inject a key/value pair on every request.
void inject(key, value) {
before.add((RequestContext req, ResponseContext res) async {
req.inject(key, value);
return true;
});
}
/// Shortcut for adding a middleware to inject a serialize on every request.
void injectSerializer(ResponseSerializer serializer) {
before.add((RequestContext req, ResponseContext res) async {
2017-03-06 02:06:52 +00:00
res.serializer = serializer;
2017-03-06 00:52:16 +00:00
return true;
});
}
2017-02-01 21:43:18 +00:00
/// Runs some [handler]. Returns `true` if request execution should continue.
2016-10-22 20:41:36 +00:00
Future<bool> executeHandler(
2016-09-17 16:12:25 +00:00
handler, RequestContext req, ResponseContext res) async {
if (handler is RequestMiddleware) {
var result = await handler(req, res);
2016-09-17 16:12:25 +00:00
if (result is bool)
return result == true;
2017-02-25 00:16:31 +00:00
else if (result is RequestHandler)
return await executeHandler(result, req, res);
else if (result != null) {
2017-02-26 21:31:09 +00:00
res.serialize(result,
contentType: res.headers[HttpHeaders.CONTENT_TYPE] ??
ContentType.JSON.mimeType);
return false;
} else
return res.isOpen;
2016-04-18 03:27:23 +00:00
}
if (handler is RequestHandler) {
2017-02-25 00:16:31 +00:00
var result = await handler(req, res);
if (result is RequestHandler)
return await executeHandler(result, req, res);
2016-04-18 03:27:23 +00:00
return res.isOpen;
2016-09-17 16:12:25 +00:00
}
if (handler is Future) {
var result = await handler;
if (result is bool)
return result == true;
2017-02-25 00:16:31 +00:00
else if (result is RequestHandler)
return await executeHandler(result, req, res);
2016-09-17 16:12:25 +00:00
else if (result != null) {
2017-02-26 21:31:09 +00:00
res.serialize(result,
contentType: res.headers[HttpHeaders.CONTENT_TYPE] ??
ContentType.JSON.mimeType);
2016-09-17 16:12:25 +00:00
return false;
} else
return true;
}
if (handler is Function) {
var result = await runContained(handler, req, res);
if (result is bool)
return result == true;
2017-02-25 00:16:31 +00:00
else if (result is RequestHandler)
return await executeHandler(result, req, res);
else if (result != null) {
2017-02-26 21:31:09 +00:00
res.serialize(result,
contentType: res.headers[HttpHeaders.CONTENT_TYPE] ??
ContentType.JSON.mimeType);
return false;
} else
return true;
2016-09-17 16:12:25 +00:00
}
if (requestMiddleware.containsKey(handler)) {
2016-10-22 20:41:36 +00:00
return await executeHandler(requestMiddleware[handler], req, res);
2016-04-18 03:27:23 +00:00
}
2016-09-17 16:12:25 +00:00
2017-02-26 21:31:09 +00:00
res.serialize(handler,
contentType:
res.headers[HttpHeaders.CONTENT_TYPE] ?? ContentType.JSON.mimeType);
2016-09-17 16:12:25 +00:00
return false;
2016-04-18 03:27:23 +00:00
}
2017-01-20 22:40:48 +00:00
Future<RequestContext> createRequestContext(HttpRequest request) {
_beforeProcessed.add(request);
return RequestContext.from(request, this);
}
Future<ResponseContext> createResponseContext(HttpResponse response) async =>
new ResponseContext(response, this);
2017-01-20 22:11:20 +00:00
/// Handles a single request.
2016-10-22 20:41:36 +00:00
Future handleRequest(HttpRequest request) async {
2017-01-14 15:39:11 +00:00
try {
2017-01-20 22:40:48 +00:00
var req = await createRequestContext(request);
var res = await createResponseContext(request.response);
2017-01-14 15:39:11 +00:00
String requestedUrl = request.uri.path.replaceAll(_straySlashes, '');
2016-10-22 20:41:36 +00:00
2017-01-14 15:39:11 +00:00
if (requestedUrl.isEmpty) requestedUrl = '/';
2016-10-22 20:41:36 +00:00
2017-03-04 21:12:39 +00:00
var resolved = resolveAll(requestedUrl, requestedUrl, method: req.method);
2016-10-22 20:41:36 +00:00
2017-01-20 22:40:48 +00:00
for (var result in resolved) req.params.addAll(result.allParams);
2016-11-23 19:50:17 +00:00
2017-01-14 15:39:11 +00:00
if (resolved.isNotEmpty) {
2017-01-20 22:40:48 +00:00
var route = resolved.first.route;
2017-01-14 15:39:11 +00:00
req.inject(Match, route.match(requestedUrl));
}
2016-10-22 20:41:36 +00:00
2017-01-20 22:40:48 +00:00
var m = new MiddlewarePipeline(resolved);
2017-01-14 15:39:11 +00:00
req.inject(MiddlewarePipeline, m);
2016-12-19 01:38:23 +00:00
2017-01-20 22:40:48 +00:00
var pipeline = []..addAll(before)..addAll(m.handlers)..addAll(after);
2016-10-22 20:41:36 +00:00
2017-01-14 15:39:11 +00:00
_printDebug('Handler sequence on $requestedUrl: $pipeline');
2016-11-23 19:50:17 +00:00
2017-01-20 22:40:48 +00:00
for (var handler in pipeline) {
2017-01-14 15:39:11 +00:00
try {
_printDebug('Executing handler: $handler');
2017-01-20 22:40:48 +00:00
var result = await executeHandler(handler, req, res);
2017-01-14 15:39:11 +00:00
_printDebug('Result: $result');
2016-10-22 20:41:36 +00:00
2017-01-14 15:39:11 +00:00
if (!result) {
_printDebug('Last executed handler: $handler');
break;
} else {
_printDebug(
'Handler completed successfully, did not terminate response: $handler');
}
} catch (e, st) {
_printDebug('Caught error in handler $handler: $e');
_printDebug(st);
if (e is AngelHttpException) {
// Special handling for AngelHttpExceptions :)
try {
res.statusCode = e.statusCode;
List<String> accept =
request.headers[HttpHeaders.ACCEPT] ?? ['*/*'];
if (accept.isEmpty ||
accept.contains('*/*') ||
accept.contains(ContentType.JSON.mimeType) ||
accept.contains("application/javascript")) {
2017-03-06 00:52:16 +00:00
res.serialize(e.toMap(),
contentType: res.headers[HttpHeaders.CONTENT_TYPE] ??
ContentType.JSON.mimeType);
2017-01-14 15:39:11 +00:00
} else {
2017-03-06 00:52:16 +00:00
await errorHandler(e, req, res);
2017-01-14 15:39:11 +00:00
}
// _finalizeResponse(request, res);
} catch (e, st) {
_fatalErrorStream.add(
new AngelFatalError(request: request, error: e, stack: st));
2016-10-22 20:41:36 +00:00
}
2017-01-14 15:39:11 +00:00
} else {
2016-12-19 01:38:23 +00:00
_fatalErrorStream.add(
new AngelFatalError(request: request, error: e, stack: st));
2016-10-22 20:41:36 +00:00
}
2017-01-14 15:39:11 +00:00
break;
}
2016-10-22 20:41:36 +00:00
}
2017-01-14 15:39:11 +00:00
try {
2017-02-13 00:38:33 +00:00
await sendResponse(request, req, res);
2017-01-14 15:39:11 +00:00
} catch (e, st) {
_fatalErrorStream
.add(new AngelFatalError(request: request, error: e, stack: st));
2016-05-01 01:42:52 +00:00
}
2017-01-14 15:39:11 +00:00
} catch (e, st) {
_fatalErrorStream
.add(new AngelFatalError(request: request, error: e, stack: st));
2016-04-22 01:42:39 +00:00
}
}
2016-12-31 02:00:52 +00:00
/// Preprocesses all routes, and eliminates the burden of reflecting handlers
/// at run-time.
void preprocessRoutes() {
_add(v) {
if (v is Function && !_preContained.containsKey(v)) {
_preContained[v] = preInject(v);
}
}
void _walk(Router router) {
2017-01-15 19:52:14 +00:00
if (router is Angel) {
router..before.forEach(_add)..after.forEach(_add);
}
2016-12-31 02:00:52 +00:00
router.requestMiddleware.forEach((k, v) => _add(v));
router.middleware.forEach(_add);
router.routes
.where((r) => r is SymlinkRoute)
.map((SymlinkRoute r) => r.router)
.forEach(_walk);
}
_walk(this);
}
2016-12-31 01:46:41 +00:00
/// Run a function after injecting from service container.
/// If this function has been reflected before, then
/// the execution will be faster, as the injection requirements were stored beforehand.
Future runContained(
Function handler, RequestContext req, ResponseContext res) {
if (_preContained.containsKey(handler)) {
return handleContained(handler, _preContained[handler])(req, res);
}
return runReflected(handler, req, res);
}
/// Runs with DI, and *always* reflects. Prefer [runContained].
Future runReflected(
Function handler, RequestContext req, ResponseContext res) async {
2017-01-12 01:52:06 +00:00
var h =
handleContained(handler, _preContained[handler] = preInject(handler));
return await h(req, res);
2016-12-31 01:46:41 +00:00
// return await closureMirror.apply(args).reflectee;
2016-09-17 16:12:25 +00:00
}
2017-02-13 00:38:33 +00:00
/// Use [sendResponse] instead.
@deprecated
2017-01-20 22:40:48 +00:00
Future sendRequest(
2017-02-13 00:38:33 +00:00
HttpRequest request, RequestContext req, ResponseContext res) =>
sendResponse(request, req, res);
/// Sends a response.
Future sendResponse(
2017-01-20 22:40:48 +00:00
HttpRequest request, RequestContext req, ResponseContext res) async {
_afterProcessed.add(request);
if (!res.willCloseItself) {
for (var finalizer in responseFinalizers) {
await finalizer(req, res);
}
for (var key in res.headers.keys) {
request.response.headers.add(key, res.headers[key]);
}
request.response.headers
..chunkedTransferEncoding = res.chunked ?? true
..set(HttpHeaders.CONTENT_LENGTH, res.buffer.length);
request.response
..statusCode = res.statusCode
..cookies.addAll(res.cookies)
..add(res.buffer.takeBytes());
await request.response.close();
}
}
2016-04-18 03:27:23 +00:00
/// Applies an [AngelConfigurer] to this instance.
Future configure(AngelConfigurer configurer) async {
await configurer(this);
2016-07-05 22:11:54 +00:00
2016-11-28 00:49:27 +00:00
if (configurer is Controller)
2016-12-31 02:00:52 +00:00
_onController.add(controllers[configurer.findExpose().path] = configurer);
2016-02-28 13:11:17 +00:00
}
2016-12-31 02:00:52 +00:00
/// Starts the server, wrapped in a [runZoned] call.
2016-02-28 13:11:17 +00:00
void listen({InternetAddress address, int port: 3000}) {
runZoned(() async {
2016-04-18 03:27:23 +00:00
await startServer(address, port);
}, onError: _onError);
}
2016-12-21 18:18:26 +00:00
/// Mounts the child on this router.
///
/// If the router is an [Angel] instance, all controllers
/// will be copied, as well as services and response finalizers.
///
/// [before] and [after] will be preserved.
2016-12-31 01:46:41 +00:00
///
2016-12-21 18:18:26 +00:00
/// NOTE: The above will not be properly copied if [path] is
/// a [RegExp].
@override
use(Pattern path, Routable routable,
2016-10-22 20:41:36 +00:00
{bool hooked: true, String namespace: null}) {
2017-01-20 22:40:48 +00:00
var head = path.toString().replaceAll(_straySlashes, '');
2016-12-21 18:18:26 +00:00
2016-11-28 00:49:27 +00:00
if (routable is Angel) {
2016-12-21 18:18:26 +00:00
_children.add(routable.._parent = this);
2017-01-15 19:52:14 +00:00
_preContained.addAll(routable._preContained);
2016-12-21 18:18:26 +00:00
if (routable.before.isNotEmpty) {
all(path, (req, res) {
return true;
}, middleware: routable.before);
}
if (routable.after.isNotEmpty) {
all(path, (req, res) {
return true;
}, middleware: routable.after);
}
if (routable.responseFinalizers.isNotEmpty) {
responseFinalizers.add((req, res) async {
if (req.path.replaceAll(_straySlashes, '').startsWith(head)) {
for (var finalizer in routable.responseFinalizers)
await finalizer(req, res);
}
return true;
});
}
2016-11-28 00:49:27 +00:00
routable.controllers.forEach((k, v) {
2017-01-20 22:40:48 +00:00
var tail = k.toString().replaceAll(_straySlashes, '');
2016-11-28 00:49:27 +00:00
controllers['$head/$tail'.replaceAll(_straySlashes, '')] = v;
});
2016-12-21 18:18:26 +00:00
routable.services.forEach((k, v) {
2017-01-20 22:40:48 +00:00
var tail = k.toString().replaceAll(_straySlashes, '');
2016-12-21 18:18:26 +00:00
services['$head/$tail'.replaceAll(_straySlashes, '')] = v;
});
2016-11-28 00:49:27 +00:00
}
if (routable is Service) {
routable.app = this;
}
2016-10-22 20:41:36 +00:00
return super.use(path, routable, hooked: hooked, namespace: namespace);
2016-02-28 13:11:17 +00:00
}
2016-06-21 22:56:04 +00:00
/// Registers a callback to run upon errors.
2017-03-04 21:12:39 +00:00
@deprecated
2016-06-21 22:56:04 +00:00
onError(AngelErrorHandler handler) {
2017-03-04 21:12:39 +00:00
this.errorHandler = handler;
}
2016-04-22 01:42:39 +00:00
2017-01-20 22:11:20 +00:00
/// Default constructor. ;)
Angel({bool debug: false}) : super(debug: debug == true) {
2016-09-17 16:12:25 +00:00
bootstrapContainer();
}
2016-02-28 13:11:17 +00:00
2017-01-20 22:11:20 +00:00
/// An instance mounted on a server started by the [serverGenerator].
factory Angel.custom(ServerGenerator serverGenerator, {bool debug: false}) =>
new Angel(debug: debug == true).._serverGenerator = serverGenerator;
2017-02-26 21:31:09 +00:00
factory Angel.fromSecurityContext(SecurityContext context,
{bool debug: false}) {
var app = new Angel(debug: debug == true);
app._serverGenerator = (InternetAddress address, int port) async {
return await HttpServer.bindSecure(address, port, context);
};
return app;
}
2016-04-18 03:27:23 +00:00
/// Creates an HTTPS server.
2017-01-20 22:11:20 +00:00
///
/// Provide paths to a certificate chain and server key (both .pem).
/// If no password is provided, a random one will be generated upon running
/// the server.
2016-11-23 19:50:17 +00:00
factory Angel.secure(String certificateChainPath, String serverKeyPath,
{bool debug: false, String password}) {
2017-02-26 21:31:09 +00:00
var certificateChain =
Platform.script.resolve(certificateChainPath).toFilePath();
var serverKey = Platform.script.resolve(serverKeyPath).toFilePath();
var serverContext = new SecurityContext();
2017-03-02 04:04:37 +00:00
serverContext.useCertificateChain(certificateChain, password: password);
2017-02-26 21:31:09 +00:00
serverContext.usePrivateKey(serverKey, password: password);
return new Angel.fromSecurityContext(serverContext);
}
}
2016-12-31 02:00:52 +00:00
/// Predetermines what needs to be injected for a handler to run.
InjectionRequest preInject(Function handler) {
var injection = new InjectionRequest();
2017-02-25 00:16:31 +00:00
ClosureMirror closureMirror = reflect(handler);
2016-12-31 02:00:52 +00:00
2017-02-25 20:57:28 +00:00
if (closureMirror.function.parameters.isEmpty) return injection;
2016-12-31 02:00:52 +00:00
// Load parameters
for (var parameter in closureMirror.function.parameters) {
var name = MirrorSystem.getName(parameter.simpleName);
var type = parameter.type.reflectedType;
2017-01-20 22:11:20 +00:00
if (!parameter.isNamed) {
if (parameter.isOptional) injection.optional.add(name);
if (type == RequestContext || type == ResponseContext) {
injection.required.add(type);
} else if (name == 'req') {
injection.required.add(RequestContext);
} else if (name == 'res') {
injection.required.add(ResponseContext);
} else if (type == dynamic) {
injection.required.add(name);
} else {
injection.required.add([name, type]);
}
2016-12-31 02:00:52 +00:00
} else {
2017-01-20 22:11:20 +00:00
injection.named[name] = type;
2016-12-31 02:00:52 +00:00
}
}
return injection;
}