diff --git a/core/CrimsonLib.dart b/core/CrimsonLib.dart index a237464..bdd7f15 100644 --- a/core/CrimsonLib.dart +++ b/core/CrimsonLib.dart @@ -2,18 +2,16 @@ // Use of this source code is governed by a MIT style licence // can be found in the LICENSE file. // website: +library crimson_core; +import '../../log4dart/lib/log4dart.dart'; +import 'dart:io'; +import 'dart:uri'; +import 'dart:isolate' as isolate; -#library("crimson:core"); - -#import('../../log4dart/LogLib.dart'); -#import("dart:io"); -#import("dart:uri"); -#import("dart:isolate", prefix:"isolate"); - -#source('crimson.dart'); -#source('crimson_impl.dart'); -#source('crimson_utils.dart'); -#source('crimsonPrivate.dart'); -#source('crimsonHttpServer.dart'); -#source('crimsonModule.dart'); \ No newline at end of file +part 'crimson.dart'; +part 'crimson_impl.dart'; +part 'crimson_utils.dart'; +part 'crimsonPrivate.dart'; +part 'crimsonHttpServer.dart'; +part 'crimsonModule.dart'; \ No newline at end of file diff --git a/core/crimson.dart b/core/crimson.dart index 5558976..bbc33c0 100644 --- a/core/crimson.dart +++ b/core/crimson.dart @@ -153,7 +153,111 @@ interface CrimsonEndpoint extends CrimsonHandler { } -interface CrimsonData extends Map { +interface CrimsonData extends Map + default _CrimsonData { + /** Constructor with default implementation */ + CrimsonData(); + +} + +/** An default implementation */ +class _CrimsonData implements CrimsonData { + /** a delegate that implements a given methods */ + Map _delegate; + + _CrimsonData() { + _delegate = new Map(); + } + + /** + * Returns whether this map contains the given [value]. + */ + bool containsValue(V value) { + return _delegate.containsValue(value); + } + + /** + * Returns whether this map contains the given [key]. + */ + bool containsKey(K key) { + return _delegate.containsKey(key); + } + + /** + * Returns the value for the given [key] or null if [key] is not + * in the map. Because null values are supported, one should either + * use containsKey to distinguish between an absent key and a null + * value, or use the [putIfAbsent] method. + */ + V operator [](K key) { + return _delegate[key]; + } + + /** + * Associates the [key] with the given [value]. + */ + void operator []=(K key, V value) { + _delegate[key] = value; + } + + /** + * If [key] is not associated to a value, calls [ifAbsent] and + * updates the map by mapping [key] to the value returned by + * [ifAbsent]. Returns the value in the map. + */ + V putIfAbsent(K key, V ifAbsent()) { + _delegate.putIfAbsent(key, ifAbsent); + } + + /** + * Removes the association for the given [key]. Returns the value for + * [key] in the map or null if [key] is not in the map. Note that values + * can be null and a returned null value does not always imply that the + * key is absent. + */ + V remove(K key) { + _delegate.remove(key); + } + + /** + * Removes all pairs from the map. + */ + void clear() { + _delegate.clear(); + } + + /** + * Applies [f] to each {key, value} pair of the map. + */ + void forEach(void f(K key, V value)) { + _delegate.forEach(f); + } + + /** + * Returns a collection containing all the keys in the map. + */ + Collection getKeys() { + return _delegate.getKeys(); + } + + /** + * Returns a collection containing all the values in the map. + */ + Collection getValues() { + return _delegate.getValues(); + } + + /** + * The number of {key, value} pairs in the map. + */ + int get length => _delegate.length; + + /** + * Returns true if there is no {key, value} pair in the map. + */ + bool isEmpty() { + return _delegate.isEmpty(); + } } diff --git a/core/crimsonHttpServer.dart b/core/crimsonHttpServer.dart index 1b1c5ef..31b63f5 100644 --- a/core/crimsonHttpServer.dart +++ b/core/crimsonHttpServer.dart @@ -2,8 +2,7 @@ class _CrimsonHttpServer implements CrimsonHttpServer { /// Contains the [Logger] - Logger logger; - + Logger logger; /// Constructor _CrimsonHttpServer([HttpServer httpServer]) : @@ -26,9 +25,7 @@ class _CrimsonHttpServer implements CrimsonHttpServer { // start the server listening _httpServer.listen(host, port); print("Listening on ${host}:${port}"); - } - - + } /// This is the core of method of [CrimsonHttpServer] /// It first loops through each of the [filters] in turn, calling handle @@ -54,18 +51,25 @@ class _CrimsonHttpServer implements CrimsonHttpServer { module = modules["*"]; } + res.persistentConnection = true; //we need this line for nginx proxy. if (module != null) { try { Future handled = module.handle(req,res); handled.then((result) { - res.outputStream.close(); + try { + res.outputStream.close(); + } + catch (ex, stack) { + print("${ex}, ${stack}"); + } }); handled.handleException((error) { res.outputStream.close(); _httpErrorHandler(error); + return true; //this consider a given error to be handled. }); } - catch (var ex, var stack) { + catch (ex, stack) { logger.error("${ex}, ${stack}"); } } @@ -73,32 +77,15 @@ class _CrimsonHttpServer implements CrimsonHttpServer { logger.debug("404, not found"); res.statusCode = HttpStatus.NOT_FOUND; res.outputStream.close(); - } - - } - + } + } _httpErrorHandler(Exception error) { + print("we invocked http error handler"); this.logger.error(error.toString()); - //CrimsonHttpException ex = new CrimsonHttpException(HttpStatus.INTERNAL_SERVER_ERROR, error); - //_crimsonErrorHandler(ex, null, null); - } - -// _crimsonErrorHandler(CrimsonHttpException error, HttpRequest req, HttpResponse res) { -// res.statusCode = error.status; -// res.setHeader("Content-Type", "text/plain"); -// res.outputStream.writeString("CrimsonHttp: Error" -// "${error}" -// "Method: ${req.method}: ${req.uri}"); -// res.outputStream.close(); -// //TODO: If an error handler filter has been registered, then use that. -// //Might be better to have an errorHandler collection in the same -// //way that we have filters and endpoints. -// } - - + } - LinkedHashMap get modules() => _modules; + LinkedHashMap get modules => _modules; final HttpServer _httpServer; final LinkedHashMap _modules; diff --git a/core/crimsonModule.dart b/core/crimsonModule.dart index fe4a6b2..f0a9802 100644 --- a/core/crimsonModule.dart +++ b/core/crimsonModule.dart @@ -4,7 +4,7 @@ class CrimsonModule { var logger; CrimsonHandlerList _handlers; - CrimsonHandlerList get handlers() => _handlers; + CrimsonHandlerList get handlers => _handlers; CrimsonModule(this._server) { logger = LoggerFactory.getLogger("CrimsonModule"); @@ -14,7 +14,7 @@ class CrimsonModule { Future handle(HttpRequest req, HttpResponse res) { logger.debug("New requst passed to module"); - var data = new Map(); + CrimsonData data = new CrimsonData(); Completer completer = new Completer(); @@ -23,7 +23,7 @@ class CrimsonModule { if (data["SUCCESS"] != true) { if (handlerIterator.hasNext()) { CrimsonHandler handler = handlerIterator.next(); - print("trying handler: ${handler.NAME}"); + logger.debug("trying handler: ${handler.NAME}"); Future onHandled = handler.handle(req,res,data); //it is valid for a handler to return null, when they are not even @@ -32,12 +32,13 @@ class CrimsonModule { // for index.html. In this case, just go to the next // handler/ if (onHandled != null) { - onHandled.then((result) { logger.debug("handler handled."); if (result["SUCCESS"] != true) { logger.debug("handler handled = false."); handleNext(); //recurse + } else { + completer.complete("handled"); } }); onHandled.handleException((error) { @@ -45,20 +46,17 @@ class CrimsonModule { try { completer.completeException(error); } - catch (var ex, var stack) { + catch (ex, stack) { print(ex); print(stack); - //res.outputStream.close(); } - + return true; }); } else { logger.debug("handler returned null - trying next"); handleNext(); //recurse - } - - + } } else { completer.complete("all handled"); @@ -66,7 +64,7 @@ class CrimsonModule { } else { logger.info("Success=true"); - res.outputStream.close(); + completer.complete("handled"); } } @@ -74,96 +72,5 @@ class CrimsonModule { return completer.future; } - -// CrimsonHandlerList _filters; -// CrimsonHandlerList _endpoints; -// -// ///TODO: Aggregate the filters and endpoints into handlers. -///// Contains the complete list of filters which implement [CrimsonFilter] -// CrimsonHandlerList get filters() => _filters; -// -// /// Contains the complete list of handlers which implement [CrimsonEndpoint] -// CrimsonHandlerList get endpoints() => _endpoints; -// -// /// Process all the [filters] in the list. -// /// If a filter generates an error, then it is logged, but we still contiune. -// _processFilters(HttpRequest req, HttpResponse res, void onAllComplete()) { -// Iterator filterIterator = filters.iterator(); -// CrimsonFilter filter = null; -// -// //closure to allow chaining async handlers -// next([CrimsonHttpException error]) { -// if (error != null) { -// print("in filter error handler: ${error}"); -// logger.error(error.toString()); -// } -// -// if (filterIterator.hasNext()) { -// filter = filterIterator.next(); -// try { -// filter.handle(req, res, (var err) => next(err)); -// } -// catch (Exception ex, var stack){ -// //call next, passing in the exception details so that we can log it. -// next(new CrimsonHttpException(HttpStatus.INTERNAL_SERVER_ERROR, ex, stack)); -// } -// } -// else { -// //call the onAllComplete handler when we've processed all the filters -// onAllComplete(); -// } -// -// } -// -// //start the chain running by calling next(); -// next(); -// } -// -// /// Process all the [endpoints] in the list -// /// If an endpoint generates and error, it is logged, and we fail with a 500 -// _processEndpoints(HttpRequest req, HttpResponse res) { -// Iterator endpointIterator = endpoints.iterator(); -// CrimsonEndpoint endpoint = null; -// -// //recursive closure to allow chaining async handlers -// next([CrimsonHttpException error = null]) { -// -// if (error != null) { -// //if there is an error, then END (no more recursing - note the else... -// logger.error(error.toString()); -// _crimsonErrorHandler(error,req,res); -// } -// else if (endpointIterator.hasNext()) { -// endpoint = endpointIterator.next(); -// //call the handler, passing in this function to allow recursing. -// try { -// endpoint.handle(req, res, -// (var err) => next(err), -// success() => res.outputStream.close()); //second closure represents success -// } -// catch (Exception ex, var stack) { -// next(new CrimsonHttpException(HttpStatus.INTERNAL_SERVER_ERROR, ex, stack)); -// } -// } else { -// //if we've got here, and there are no errors -// //then we've not found a matching endpoint, so return a 404 error. -// _crimsonErrorHandler(new CrimsonHttpException(HttpStatus.NOT_FOUND, "Not found"), req, res); -// } -// -// } -// -// //start the chain running by calling next(); -// next(); -// } -// -// handleRequest(req,res) { -// HttpRequest request = req; -// logger.info("${req.method}: ${req.uri}"); -// -// -// _processFilters(request,res,onAllComplete() { -// _processEndpoints(req,res); -// }); -// } - + } diff --git a/core/crimsonPrivate.dart b/core/crimsonPrivate.dart index 8c13842..5c05ee0 100644 --- a/core/crimsonPrivate.dart +++ b/core/crimsonPrivate.dart @@ -47,7 +47,7 @@ class _CrimsonHandlerList implements CrimsonHandlerLis Iterator iterator() => _internalMap.getValues().iterator(); // CrimsonHandler operator[](int i) => _internalList[i]; // void operator []=(int index,value) => _internalList[index] = value; -// int get length() => _internalList.length; +// int get length => _internalList.length; // void addLast(CrimsonHandler value) => _internalList.addLast(value); // void sort(int compare(CrimsonHandler, CrimsonHandler)) => _internalList.sort(compare); // int indexOf(E element, [int start]) => _internalList.indexOf(element, start); diff --git a/core/crimson_impl.dart b/core/crimson_impl.dart index da1cbe0..cc705c2 100644 --- a/core/crimson_impl.dart +++ b/core/crimson_impl.dart @@ -45,7 +45,7 @@ class SessionImpl implements Session { Collection getValues() => _internalList.getValues(); - int get length() => _internalList.length; + int get length => _internalList.length; bool isEmpty() => _internalList.isEmpty(); } \ No newline at end of file diff --git a/handlers/.children b/handlers/.children index c4c037d..3113ffa 100644 --- a/handlers/.children +++ b/handlers/.children @@ -1 +1 @@ -handlers.dart +HandlersLib.dart diff --git a/handlers/HandlersLib.dart b/handlers/HandlersLib.dart index cad8852..9acc21c 100644 --- a/handlers/HandlersLib.dart +++ b/handlers/HandlersLib.dart @@ -1,10 +1,11 @@ -#library("crimson:handlers"); -#import("../core/CrimsonLib.dart"); -#import('../../log4dart/LogLib.dart'); -#import('../../dart-crypto-lib/src/md5.dart'); -#import("dart:io"); +library crimson_handlers; +import '../core/CrimsonLib.dart'; +import '../../log4dart/lib/log4dart.dart'; +import 'dart:crypto'; +import 'dart:io'; -#source("endpoints/favicon.dart"); -#source("endpoints/staticFile.dart"); -#source("filters/cookieSession.dart"); -#source("endpoints/route.dart"); \ No newline at end of file +part 'endpoints/favicon.dart'; +part 'endpoints/staticFile.dart'; +part 'filters/cookieSession.dart'; +part 'endpoints/route.dart'; +part 'endpoints/controllerRoute.dart'; \ No newline at end of file diff --git a/handlers/endpoints/controllerRoute.dart b/handlers/endpoints/controllerRoute.dart new file mode 100644 index 0000000..0f2ed11 --- /dev/null +++ b/handlers/endpoints/controllerRoute.dart @@ -0,0 +1,65 @@ +class ControllerRoute implements CrimsonEndpoint { + String _name; + String _path; + AppController _controller; + var logger; + CrimsonHttpServer server; + + /** Contruct route that forwards all matched requests to a given controller. */ + ControllerRoute(String this._path, AppController controller) { + _name = "ROUTE:${_path}"; + logger = LoggerFactory.getLogger(_name); + _controller = controller; + controller.route = _path; + } + + Future handle(HttpRequest req, HttpResponse res, CrimsonData data) { + logger.debug("Request:${req.method}:${req.path} - Handler:${this._path}"); + bool isMatched = false; + if (req.path.startsWith(this._path)) { + isMatched = true; + } + if (isMatched) { + logger.debug("Routable handler for request: ${_name}"); + Completer completer = new Completer(); + Future handlerComplete = _controller.handler(req,res,data); + + if (handlerComplete != null) { + logger.debug("handling"); + handlerComplete.then((completeData) => onSuccess(res, completer, data)); + handlerComplete.handleException((error) { + print("have error in controller: ${error}"); + completer.completeException(error); + return true; + }); + } + else { + onSuccess(res, completer,data); + } + + return completer.future; + } + else { + return null; + } + } + + onSuccess(res, completer, data) { + print("we successfully processed a route: ${_controller.route}"); + data["SUCCESS"] = true; + completer.complete(data); + } + + String get NAME => _name; + +} + +interface AppController { + + /** A base method that handles a http requests. */ + Future handler(HttpRequest, HttpResponse, CrimsonData); + /** Path to the controller a knowledge about given route path. */ + void set route(String path); + + String get route(); +} \ No newline at end of file diff --git a/handlers/endpoints/route.dart b/handlers/endpoints/route.dart index 29b7212..2757fc2 100644 --- a/handlers/endpoints/route.dart +++ b/handlers/endpoints/route.dart @@ -71,6 +71,6 @@ class Route implements CrimsonEndpoint { completer.complete(data); } - String get NAME() => _name; + String get NAME => _name; } diff --git a/handlers/endpoints/staticFile.dart b/handlers/endpoints/staticFile.dart index 4aa4ce5..9206f8a 100644 --- a/handlers/endpoints/staticFile.dart +++ b/handlers/endpoints/staticFile.dart @@ -18,7 +18,7 @@ class StaticFile implements CrimsonEndpoint { Future handle(HttpRequest request, HttpResponse response, CrimsonData data) { Completer completer = new Completer(); - String fileToLoad = this.rootPath + request.uri; + String fileToLoad = "${this.rootPath}${request.uri}"; onSuccess(List filedata) { logger.debug("Read file: ${fileToLoad}"); @@ -46,19 +46,14 @@ class StaticFile implements CrimsonEndpoint { _loadFromPath(String path, success(List data), onNotFound(), fail(exception)) { File file = new File(path); - //file.fullPath((String fullPath) => print(fullPath)); + Future existFuture = file.exists(); - file.onError = (Exception error) { - logger.debug("${path} doesn't exist: ${error}"); - fail(error); - }; - - logger.debug("trying to open file: ${path}"); - file.exists((bool exists) { + existFuture.then((exists) { logger.debug("in exists callback: ${path}, ${exists}"); if (exists) { logger.debug("${path} exists, so reading"); - file.readAsBytes( (List buffer) { + Future> bytesFuture = file.readAsBytes(); + bytesFuture.then((List buffer) { logger.debug("successfully read ${path}"); success(buffer); }); @@ -66,8 +61,16 @@ class StaticFile implements CrimsonEndpoint { else { logger.debug("${path} doesn't exist"); onNotFound(); - } + } }); + + existFuture.handleException((Exception error) { + logger.debug("${path} doesn't exist: ${error}"); + fail(error); + return true; + }); + + logger.debug("trying to open file: ${path}"); } final String NAME = "StaticFile"; diff --git a/handlers/filters/cookieSession.dart b/handlers/filters/cookieSession.dart index ac608fd..6d0a683 100644 --- a/handlers/filters/cookieSession.dart +++ b/handlers/filters/cookieSession.dart @@ -121,7 +121,7 @@ class CookieSession implements CrimsonFilter { //generate a new ID. //this is a toy - don't use for real! - var md5 = new Md5(); + var md5 = new MD5(); String s = (Math.random() * Clock.now()).toInt().toString(); md5.update(s.charCodes()); var hash = md5.digest(); diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..c32542f --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,3 @@ +dependencies: + log4dart: + git: git://github.com/zhygr/log4dart.git \ No newline at end of file diff --git a/test/crimsonTest.dart b/test/crimsonTest.dart index 27f5891..57d0b25 100644 --- a/test/crimsonTest.dart +++ b/test/crimsonTest.dart @@ -1,8 +1,8 @@ -#library('crimsonTest'); +library crimsonTest; -#import("../core/CrimsonLib.dart"); -#import("../handlers/HandlersLib.dart"); -#import("dart:io"); +import '../core/CrimsonLib.dart'; +import '../handlers/HandlersLib.dart'; +import 'dart:io'; ///Simple test server main() {