platform/lib/src/http/server.dart

239 lines
7.1 KiB
Dart
Raw Normal View History

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);
/// Handles an [AngelHttpException].
typedef Future AngelErrorHandler(AngelHttpException err, RequestContext req,
ResponseContext res);
2016-04-18 03:27:23 +00:00
/// A function that configures an [Angel] server in some way.
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 {
ServerGenerator _serverGenerator =
(address, port) async => await HttpServer.bind(address, port);
/// Default error handler, show HTML error page
var _errorHandler = (AngelHttpException e, req, ResponseContext res) {
2016-05-01 02:03:31 +00:00
res.header(HttpHeaders.CONTENT_TYPE, ContentType.HTML.toString());
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.";
/// [RequestMiddleware] to be run before all requests.
List before = [];
/// [RequestMiddleware] 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 {
var server =
await _serverGenerator(address ?? InternetAddress.LOOPBACK_IP_V4, port);
2016-04-18 03:27:23 +00:00
this.httpServer = server;
server.listen((HttpRequest request) async {
2016-05-01 01:42:52 +00:00
String req_url = request.uri.toString().replaceAll(
new RegExp(r'\/+$'), '');
2016-04-29 00:12:57 +00:00
if (req_url.isEmpty)
req_url = '/';
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 {
2016-06-19 05:02:41 +00:00
res.status(e.statusCode);
2016-05-01 01:42:52 +00:00
String accept = request.headers.value(HttpHeaders.ACCEPT);
if (accept == "*/*" ||
accept.contains("application/json") ||
accept.contains("application/javascript")) {
res.json(e.toMap());
} else {
2016-05-01 02:01:35 +00:00
await _errorHandler(e, req, res);
}
2016-05-01 02:01:35 +00:00
_finalizeResponse(request, res);
2016-05-01 01:42:52 +00:00
} 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
}
}
}
2016-04-18 03:27:23 +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 RequestMiddleware) {
var result = await handler(req, res);
if (result is bool)
return result == true;
else if (result != null) {
res.json(result);
return false;
} else
return res.isOpen;
2016-04-18 03:27:23 +00:00
}
if (handler is RequestHandler) {
2016-04-18 03:27:23 +00:00
await handler(req, res);
return res.isOpen;
} else if (handler is RawRequestHandler) {
2016-04-18 03:27:23 +00:00
var result = await handler(req.underlyingRequest);
if (result is bool)
return result == true;
else if (result != null) {
res.json(result);
return false;
} else
return true;
} else if (handler is Function || handler is Future) {
2016-04-18 03:27:23 +00:00
var result = await handler();
if (result is bool)
return result == true;
else if (result != null) {
res.json(result);
return false;
} else
return true;
} else if (requestMiddleware.containsKey(handler)) {
return await _applyHandler(requestMiddleware[handler], req, res);
} 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 {
2016-05-01 01:42:52 +00:00
try {
if (!res.willCloseItself) {
res.responseData.forEach((blob) => request.response.add(blob));
await request.response.close();
}
} catch (e) {
// Remember: This fails silently
2016-04-22 01:42:39 +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.
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);
}, onError: _onError);
}
@override
use(Pattern path, Routable routable,
{bool hooked: false, String middlewareNamespace: null}) {
if (routable is Service) {
routable.app = this;
}
super.use(
path, routable, hooked: hooked, middlewareNamespace: middlewareNamespace);
2016-02-28 13:11:17 +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.
_onError(e, [StackTrace stackTrace]) {
2016-04-18 03:27:23 +00:00
stderr.write(e.toString());
if (stackTrace != null) stderr.write(stackTrace.toString());
2016-04-22 01:42:39 +00:00
}
2016-04-18 03:27:23 +00:00
2016-05-01 01:42:52 +00:00
Angel
()
:
super() {}
2016-02-28 13:11:17 +00:00
2016-04-18 03:27:23 +00:00
/// Creates an HTTPS server.
/// 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-05-01 01:42:52 +00:00
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);
};
}
}