Backend class
class Backend { static final String COOKIE_PATH = "/"; static final bool COOKIE_HTTP_ONLY = true; /** * Register routes which are matched with request.uri . */ final Router router; /** * Calls handlers associated with a particular routeName. */ final RequestNavigator _requestNavigator; /** * Handles the incoming stream of [HttpRequest]s */ final HttpServer _server; List _defaulHttpHeaders = new List(); /** * For cookies. */ final _hmacFactory; /** * HttpBodyHandler.processRequest */ final _httpBodyExtractor; /** * Default handler for NotFoundView. */ RequestHandler _notFoundViewHandler = (Request request) { request.response ..statusCode = HttpStatus.NOT_FOUND ..close(); }; /** * Constructor. */ Backend.config(this._server, this.router, this._requestNavigator, this._hmacFactory, this._httpBodyExtractor) { // Add default handler for not found views. Before not found view response // is sent, check if URI ends with a slash and if not, try to add it. _requestNavigator.registerDefaultHandler((httpRequest, urlParams) => prepareRequestHandler(httpRequest, urlParams, (Request request) { var uri = request.httpRequest.uri; if (!uri.path.endsWith('/')) { //we set request.httpRequest.response because of consistency // as (request.response := request.httpRequest.response) request.httpRequest.response.redirect(new Uri( scheme: uri.scheme, host: uri.host, port: uri.port, path: uri.path + '/', query: uri.query )); } else{ _notFoundViewHandler(request); } })); } /** * Creates a new backend. */ static Future<Backend> bind(key, hashMethod, {String host: "0.0.0.0", int port: 8080}){ return HttpServer.bind(host, port).then((httpServer) { var router = new Router("http://$host:$port", {}); var requestNavigator = new RequestNavigator(httpServer.asBroadcastStream(), router); return new Backend.config(httpServer, router, requestNavigator, () => new HMAC(hashMethod, key), HttpBodyHandler.processRequest); }); } /** * Adds header which will be attached to each response. Could be overwritten. */ void addDefaultHttpHeader(name, value) { _defaulHttpHeaders.add({'name': name, 'value': value}); } /** * Transforms [httpRequest] with [urlParams] and creates [Request] which is * passed asynchronously to [handler]. */ Future prepareRequestHandler(HttpRequest httpRequest, Map urlParams, RequestHandler handler) { return _httpBodyExtractor(httpRequest).then((HttpBody body) { if (_defaulHttpHeaders != null) { _defaulHttpHeaders.forEach((header) => httpRequest.response.headers.add(header['name'], header['value'])); } Request request = new Request(body.type, body.body, httpRequest.response, httpRequest.headers, httpRequest, urlParams); request.authenticatedUserId = getAuthenticatedUser(request.headers); handler(request); return true; }); } /** * Adds [route] for a particular [route] so handler could be attached to [routeName]s. */ void addRoute(String routeName, Route route){ router.addRoute(routeName, route); } /** * Adds [handler] for a particular [routeName]. */ void addView(String routeName, RequestHandler handler) { _requestNavigator.registerHandler(routeName, (httpRequest, urlParams) => prepareRequestHandler(httpRequest, urlParams, handler)); } /** * Corresponding [Route] for [routeName] should be in the prefix format, * i.e. "/uploads/*", as backend will look for files documentRoot/matchedSufix */*/ void addStaticView(String routeName, String documentRoot) { var vd = new VirtualDirectory(documentRoot); _requestNavigator.registerHandler(routeName, (httpRequest, urlParams) { var relativePath = urlParams['_tail']; if (relativePath.split('/').contains('..')) { httpRequest.response.statusCode = HttpStatus.NOT_FOUND; httpRequest.response.close(); return; } String path = p.join(documentRoot, relativePath); FileSystemEntity.type(path).then((type) { switch (type) { case FileSystemEntityType.FILE: // If file, serve as such. vd.serveFile(new File(path), httpRequest); break; case FileSystemEntityType.DIRECTORY: // File not found, fall back to 404. httpRequest.response.statusCode = HttpStatus.NOT_FOUND; httpRequest.response.close(); break; default: // File not found, fall back to 404. httpRequest.response.statusCode = HttpStatus.NOT_FOUND; httpRequest.response.close(); break; } }); }); } /** * If nothing is matched. There is a default [_notFoundViewHandler], but it * can be overwritten by this method. */ void addNotFoundView(RequestHandler handler) { _notFoundViewHandler = handler; } List<int> sign(String msg) { HMAC hmac = _hmacFactory(); _stringToHash(msg, hmac); return hmac.close(); } bool verifySignature(String msg, List<int> signature) { HMAC hmac = _hmacFactory(); _stringToHash(msg, hmac); return hmac.verify(signature); } void _stringToHash(String value, HMAC hmac) { Utf8Codec codec = new Utf8Codec(); List<int> encodedUserId = codec.encode(value); hmac.add(encodedUserId); } void authenticate(HttpResponse response, String userId) { List<int> userIdSignature = sign(userId); Cookie cookie = new Cookie('authentication', JSON.encode({ 'userID': userId, 'signature': userIdSignature})); cookie.expires = new DateTime.now().add(new Duration(days: 365)); cookie.path = COOKIE_PATH; cookie.httpOnly = COOKIE_HTTP_ONLY; response.headers.add(HttpHeaders.SET_COOKIE, cookie); } String getAuthenticatedUser(HttpHeaders headers) { if (headers[HttpHeaders.COOKIE] == null) { return null; } for (String cookieString in headers[HttpHeaders.COOKIE]) { Cookie cookie = new Cookie.fromSetCookieValue(cookieString); if (cookie.name == 'authentication') { Map authentication = JSON.decode(cookie.value); if (verifySignature(authentication['userID'], authentication['signature'])) { return authentication['userID']; } } } return null; } void logout(Request request) { Cookie cookie = new Cookie('authentication', ""); cookie.expires = new DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); cookie.path = COOKIE_PATH; cookie.httpOnly = COOKIE_HTTP_ONLY; request.response.headers.add(HttpHeaders.SET_COOKIE, cookie); } }
Static Properties
Static Methods
Future<Backend> bind(key, hashMethod, {String host: "0.0.0.0", int port: 8080}) #
Creates a new backend.
static Future<Backend> bind(key, hashMethod, {String host: "0.0.0.0", int port: 8080}){ return HttpServer.bind(host, port).then((httpServer) { var router = new Router("http://$host:$port", {}); var requestNavigator = new RequestNavigator(httpServer.asBroadcastStream(), router); return new Backend.config(httpServer, router, requestNavigator, () => new HMAC(hashMethod, key), HttpBodyHandler.processRequest); }); }
Constructors
new Backend.config(HttpServer _server, Router router, RequestNavigator _requestNavigator, _hmacFactory, _httpBodyExtractor) #
Constructor.
Backend.config(this._server, this.router, this._requestNavigator, this._hmacFactory, this._httpBodyExtractor) { // Add default handler for not found views. Before not found view response // is sent, check if URI ends with a slash and if not, try to add it. _requestNavigator.registerDefaultHandler((httpRequest, urlParams) => prepareRequestHandler(httpRequest, urlParams, (Request request) { var uri = request.httpRequest.uri; if (!uri.path.endsWith('/')) { //we set request.httpRequest.response because of consistency // as (request.response := request.httpRequest.response) request.httpRequest.response.redirect(new Uri( scheme: uri.scheme, host: uri.host, port: uri.port, path: uri.path + '/', query: uri.query )); } else{ _notFoundViewHandler(request); } })); }
Properties
Methods
void addDefaultHttpHeader(name, value) #
Adds header which will be attached to each response. Could be overwritten.
void addDefaultHttpHeader(name, value) { _defaulHttpHeaders.add({'name': name, 'value': value}); }
void addNotFoundView(RequestHandler handler) #
If nothing is matched. There is a default _notFoundViewHandler, but it can be overwritten by this method.
void addNotFoundView(RequestHandler handler) { _notFoundViewHandler = handler; }
void addRoute(String routeName, Route route) #
Adds route for a particular route so handler could be attached to routeNames.
void addRoute(String routeName, Route route){ router.addRoute(routeName, route); }
void addStaticView(String routeName, String documentRoot) #
Corresponding Route for routeName should be in the prefix format, i.e. "/uploads/*", as backend will look for files documentRoot/matchedSufix /
void addStaticView(String routeName, String documentRoot) { var vd = new VirtualDirectory(documentRoot); _requestNavigator.registerHandler(routeName, (httpRequest, urlParams) { var relativePath = urlParams['_tail']; if (relativePath.split('/').contains('..')) { httpRequest.response.statusCode = HttpStatus.NOT_FOUND; httpRequest.response.close(); return; } String path = p.join(documentRoot, relativePath); FileSystemEntity.type(path).then((type) { switch (type) { case FileSystemEntityType.FILE: // If file, serve as such. vd.serveFile(new File(path), httpRequest); break; case FileSystemEntityType.DIRECTORY: // File not found, fall back to 404. httpRequest.response.statusCode = HttpStatus.NOT_FOUND; httpRequest.response.close(); break; default: // File not found, fall back to 404. httpRequest.response.statusCode = HttpStatus.NOT_FOUND; httpRequest.response.close(); break; } }); }); }
void addView(String routeName, RequestHandler handler) #
Adds handler for a particular routeName.
void addView(String routeName, RequestHandler handler) { _requestNavigator.registerHandler(routeName, (httpRequest, urlParams) => prepareRequestHandler(httpRequest, urlParams, handler)); }
void authenticate(HttpResponse response, String userId) #
void authenticate(HttpResponse response, String userId) { List<int> userIdSignature = sign(userId); Cookie cookie = new Cookie('authentication', JSON.encode({ 'userID': userId, 'signature': userIdSignature})); cookie.expires = new DateTime.now().add(new Duration(days: 365)); cookie.path = COOKIE_PATH; cookie.httpOnly = COOKIE_HTTP_ONLY; response.headers.add(HttpHeaders.SET_COOKIE, cookie); }
String getAuthenticatedUser(HttpHeaders headers) #
String getAuthenticatedUser(HttpHeaders headers) { if (headers[HttpHeaders.COOKIE] == null) { return null; } for (String cookieString in headers[HttpHeaders.COOKIE]) { Cookie cookie = new Cookie.fromSetCookieValue(cookieString); if (cookie.name == 'authentication') { Map authentication = JSON.decode(cookie.value); if (verifySignature(authentication['userID'], authentication['signature'])) { return authentication['userID']; } } } return null; }
void logout(Request request) #
void logout(Request request) { Cookie cookie = new Cookie('authentication', ""); cookie.expires = new DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); cookie.path = COOKIE_PATH; cookie.httpOnly = COOKIE_HTTP_ONLY; request.response.headers.add(HttpHeaders.SET_COOKIE, cookie); }
Future prepareRequestHandler(HttpRequest httpRequest, Map urlParams, RequestHandler handler) #
Transforms httpRequest with urlParams and creates Request which is passed asynchronously to handler.
Future prepareRequestHandler(HttpRequest httpRequest, Map urlParams, RequestHandler handler) { return _httpBodyExtractor(httpRequest).then((HttpBody body) { if (_defaulHttpHeaders != null) { _defaulHttpHeaders.forEach((header) => httpRequest.response.headers.add(header['name'], header['value'])); } Request request = new Request(body.type, body.body, httpRequest.response, httpRequest.headers, httpRequest, urlParams); request.authenticatedUserId = getAuthenticatedUser(request.headers); handler(request); return true; }); }