2016-02-28 13:11:17 +00:00
|
|
|
part of angel_framework.http;
|
|
|
|
|
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);
|
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// A function that configures an [Angel] server in some way.
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
typedef Future AngelConfigurer(Angel app);
|
2016-04-18 03:27:23 +00:00
|
|
|
|
2016-02-28 13:11:17 +00:00
|
|
|
/// A powerful real-time/REST/MVC server class.
|
|
|
|
class Angel extends Routable {
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
ServerGenerator _serverGenerator =
|
|
|
|
(address, port) async => await HttpServer.bind(address, port);
|
|
|
|
|
|
|
|
/// Default error handler, show HTML error page
|
|
|
|
var _errorHandler = (AngelHttpException e, req, ResponseContext res) {
|
|
|
|
res.status(e.statusCode);
|
|
|
|
res.write("<DOCTYPE html><html><head><title>${e.message}</title>");
|
|
|
|
res.write("</head><body><h1>${e.message}</h1><ul>");
|
|
|
|
for (String error in e.errors) {
|
|
|
|
res.write("<li>$error</li>");
|
|
|
|
}
|
|
|
|
res.write("</ul></body></html>");
|
|
|
|
res.end();
|
|
|
|
};
|
|
|
|
|
|
|
|
var viewGenerator =
|
|
|
|
(String view, [Map data]) => "No view engine has been configured yet.";
|
|
|
|
|
|
|
|
/// [Middleware] to be run before all requests.
|
|
|
|
List before = [];
|
|
|
|
|
|
|
|
/// [Middleware] to be run after all requests.
|
|
|
|
List after = [];
|
2016-04-18 03:27:23 +00:00
|
|
|
|
|
|
|
HttpServer httpServer;
|
|
|
|
God god = new God();
|
|
|
|
|
|
|
|
startServer(InternetAddress address, int port) async {
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
var server =
|
|
|
|
await _serverGenerator(address ?? InternetAddress.LOOPBACK_IP_V4, port);
|
2016-04-18 03:27:23 +00:00
|
|
|
this.httpServer = server;
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
|
|
|
|
server.listen((HttpRequest request) async {
|
|
|
|
String req_url =
|
|
|
|
request.uri.toString().replaceAll(new RegExp(r'\/+$'), '');
|
|
|
|
RequestContext req = await RequestContext.from(request, {}, this, null);
|
|
|
|
ResponseContext res = await ResponseContext.from(request.response, this);
|
|
|
|
|
|
|
|
bool canContinue = true;
|
|
|
|
|
|
|
|
var execHandler = (handler, req) async {
|
|
|
|
if (canContinue) {
|
|
|
|
canContinue = await new Future.sync(() async {
|
|
|
|
return _applyHandler(handler, req, res);
|
|
|
|
}).catchError((e, [StackTrace stackTrace]) async {
|
|
|
|
if (e is AngelHttpException) {
|
|
|
|
// Special handling for AngelHttpExceptions :)
|
|
|
|
try {
|
|
|
|
String accept = request.headers.value(HttpHeaders.ACCEPT) ?? "*/*";
|
|
|
|
if (accept == "*/*" ||
|
|
|
|
accept.contains("application/json") ||
|
|
|
|
accept.contains("application/javascript")) {
|
|
|
|
res.json(e.toMap());
|
|
|
|
} else {
|
|
|
|
await _applyHandler(_errorHandler, req, res);
|
|
|
|
}
|
|
|
|
} catch (_) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_onError(e, stackTrace);
|
|
|
|
canContinue = false;
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
} else
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
for (var handler in before) {
|
|
|
|
await execHandler(handler, req);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Route route in routes) {
|
|
|
|
if (!canContinue) break;
|
|
|
|
if (route.matcher.hasMatch(req_url) &&
|
|
|
|
(request.method == route.method || route.method == '*')) {
|
|
|
|
req.params = route.parseParameters(request.uri.toString());
|
|
|
|
req.route = route;
|
|
|
|
|
|
|
|
for (var handler in route.handlers) {
|
|
|
|
await execHandler(handler, req);
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
}
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
}
|
2016-04-18 03:27:23 +00:00
|
|
|
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
for (var handler in after) {
|
|
|
|
await execHandler(handler, req);
|
|
|
|
}
|
2016-04-22 01:42:39 +00:00
|
|
|
_finalizeResponse(request, res);
|
|
|
|
});
|
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
return server;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool> _applyHandler(handler, RequestContext req,
|
|
|
|
ResponseContext res) async {
|
|
|
|
if (handler is Middleware) {
|
2016-04-21 20:37:02 +00:00
|
|
|
var result = await handler(req, res);
|
|
|
|
if (result is bool)
|
|
|
|
return result == true;
|
|
|
|
else if (result != null) {
|
|
|
|
res.json(result);
|
|
|
|
return false;
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
} else
|
|
|
|
return res.isOpen;
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
|
2016-04-21 20:37:02 +00:00
|
|
|
if (handler is RequestHandler) {
|
2016-04-18 03:27:23 +00:00
|
|
|
await handler(req, res);
|
|
|
|
return res.isOpen;
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
} else if (handler is RawRequestHandler) {
|
2016-04-18 03:27:23 +00:00
|
|
|
var result = await handler(req.underlyingRequest);
|
2016-04-21 20:37:02 +00:00
|
|
|
if (result is bool)
|
|
|
|
return result == true;
|
|
|
|
else if (result != null) {
|
|
|
|
res.json(result);
|
|
|
|
return false;
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
} else
|
|
|
|
return true;
|
|
|
|
} else if (handler is Function || handler is Future) {
|
2016-04-18 03:27:23 +00:00
|
|
|
var result = await handler();
|
2016-04-21 20:37:02 +00:00
|
|
|
if (result is bool)
|
|
|
|
return result == true;
|
|
|
|
else if (result != null) {
|
|
|
|
res.json(result);
|
|
|
|
return false;
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
} else
|
|
|
|
return true;
|
|
|
|
} else if (middleware.containsKey(handler)) {
|
2016-04-18 03:27:23 +00:00
|
|
|
return await _applyHandler(middleware[handler], req, res);
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
} else {
|
2016-04-18 03:27:23 +00:00
|
|
|
res.willCloseItself = true;
|
|
|
|
res.underlyingResponse.write(god.serialize(handler));
|
|
|
|
await res.underlyingResponse.close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-22 01:42:39 +00:00
|
|
|
_finalizeResponse(HttpRequest request, ResponseContext res) async {
|
|
|
|
if (!res.willCloseItself) {
|
|
|
|
res.responseData.forEach((blob) => request.response.add(blob));
|
|
|
|
await request.response.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
String _randomString(int length) {
|
|
|
|
var rand = new Random();
|
|
|
|
var codeUnits = new List.generate(length, (index) {
|
|
|
|
return rand.nextInt(33) + 89;
|
|
|
|
});
|
|
|
|
|
|
|
|
return new String.fromCharCodes(codeUnits);
|
|
|
|
}
|
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// Applies an [AngelConfigurer] to this instance.
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
Future configure(AngelConfigurer configurer) async {
|
|
|
|
await configurer(this);
|
2016-02-28 13:11:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Starts the server.
|
|
|
|
void listen({InternetAddress address, int port: 3000}) {
|
|
|
|
runZoned(() async {
|
2016-04-18 03:27:23 +00:00
|
|
|
await startServer(address, port);
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
}, onError: _onError);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
use(Pattern path, Routable routable) {
|
|
|
|
if (routable is Service) {
|
|
|
|
routable.app = this;
|
|
|
|
}
|
|
|
|
super.use(path, routable);
|
2016-02-28 13:11:17 +00:00
|
|
|
}
|
|
|
|
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
onError(handler) {
|
|
|
|
_errorHandler = handler;
|
|
|
|
}
|
2016-04-22 01:42:39 +00:00
|
|
|
|
2016-02-28 13:11:17 +00:00
|
|
|
/// Handles a server error.
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
_onError(e, [StackTrace stackTrace]) {
|
2016-04-18 03:27:23 +00:00
|
|
|
stderr.write(e.toString());
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
if (stackTrace != null) stderr.write(stackTrace.toString());
|
2016-04-22 01:42:39 +00:00
|
|
|
}
|
2016-04-18 03:27:23 +00:00
|
|
|
|
|
|
|
Angel() : super() {}
|
2016-02-28 13:11:17 +00:00
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// Creates an HTTPS server.
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +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.
|
|
|
|
Angel.secure(String certificateChainPath, String serverKeyPath,
|
|
|
|
{String password})
|
|
|
|
: super() {
|
|
|
|
_serverGenerator = (InternetAddress address, int port) async {
|
|
|
|
var certificateChain =
|
|
|
|
Platform.script.resolve('server_chain.pem').toFilePath();
|
|
|
|
var serverKey = Platform.script.resolve('server_key.pem').toFilePath();
|
|
|
|
var serverContext = new SecurityContext();
|
|
|
|
serverContext.useCertificateChain(certificateChain);
|
|
|
|
serverContext.usePrivateKey(serverKey,
|
|
|
|
password: password ?? _randomString(8));
|
|
|
|
|
|
|
|
return await HttpServer.bindSecure(address, port, serverContext);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|