Dart Documentationclean_backendBackend

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 final bool COOKIE_HTTP_ONLY = true
static final String COOKIE_PATH = "/"

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

final Router router #

Register routes which are matched with request.uri .

final Router router

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;
 });
}

List<int> sign(String msg) #

List<int> sign(String msg) {
 HMAC hmac = _hmacFactory();
 _stringToHash(msg, hmac);
 return hmac.close();
}

bool verifySignature(String msg, List<int> signature) #

bool verifySignature(String msg, List<int> signature) {
 HMAC hmac = _hmacFactory();
 _stringToHash(msg, hmac);
 return hmac.verify(signature);
}