From: Jean Privat Date: Thu, 26 May 2016 15:43:35 +0000 (-0400) Subject: Merge: Popcorn: use_before, use_after X-Git-Url: http://nitlanguage.org?hp=050d6399fe900bc85b96f1ae567b24937233b0ed Merge: Popcorn: use_before, use_after This PR introduce two changes in the popcorn request-response cycle: * Introduce placeholders `use_before` and `use_after` to force handler execution before or after each request. This makes the life of developer easier when using a lot of routers/routers. * Break response cycle if a between (between as between before and after) handler gives a response then call after_handler. This encourage the use of middleware into `use_before` and `use_after` and avoid requests sent twice. Bonus the api makes more sense like this. Pull-Request: #2125 Reviewed-by: Jean Privat --- diff --git a/lib/popcorn/README.md b/lib/popcorn/README.md index 4712faf..a136621 100644 --- a/lib/popcorn/README.md +++ b/lib/popcorn/README.md @@ -428,6 +428,23 @@ receive a `404 Not found` error. * `res.send()` Send a response of various types. * `res.error()` Set the response status code and send its message as the response body. +## Response cycle + +When the popcorn `App` receives a request, the response cycle is the following: + +1. `pre-middlewares` lookup matching middlewares registered with `use_before(pre_middleware)`: + 1. execute matching middleware by registration order + 2. if a middleware send a response then let the `pre-middlewares` loop continue + with the next middleware +2. `response-handlers` lookup matching handlers registered with `use(handler)`: + 1. execute matching middleware by registration order + 2. if a middleware send a response then stop the `response-handlers` loop + 3. if no hander matches or sends a response, generate a 404 response +3. `post-middlewares` lookup matching handlers registered with `use_after(post_handler)`: + 1. execute matching middleware by registration order + 2. if a middleware send a response then let the `post-middlewares` loop continue + with the next middleware + ## Middlewares ### Overview @@ -465,7 +482,7 @@ end var app = new App -app.use("/*", new MyLogger) +app.use_before("/*", new MyLogger) app.use("/", new HelloHandler) app.listen("localhost", 3000) ~~~ @@ -474,8 +491,9 @@ By using the `MyLogger` handler to the route `/*` we ensure that every requests (even 404 ones) pass through the middleware handler. This handler just prints “Request Logged!” when a request is received. -The order of middleware loading is important: middleware functions that are loaded first are also executed first. -In the above example, `MyLogger` will be executed before `HelloHandler`. +Be default, the order of middleware execution is that are loaded first are also executed first. +To ensure our middleware `MyLogger` will be executed before all the other, we add it +with the `use_before` method. ### Ultra cool, more advanced logger example @@ -519,9 +537,9 @@ class HelloHandler end var app = new App -app.use("/*", new RequestTimeHandler) +app.use_before("/*", new RequestTimeHandler) app.use("/", new HelloHandler) -app.use("/*", new LogHandler) +app.use_after("/*", new LogHandler) app.listen("localhost", 3000) ~~~ @@ -530,9 +548,15 @@ Doing so we can access our data from all handlers that import our module, direct from the `req` parameter. We use the new middleware called `RequestTimeHandler` to initialize the request timer. +Because of the `use_before` method, the `RequestTimeHandler` middleware will be executed +before all the others. + +We then let the `HelloHandler` produce the response. Finally, our `LogHandler` will display a bunch of data and use the request `timer` to display the time it took to process the request. +Because of the `use_after` method, the `LogHandler` middleware will be executed after +all the others. The app now uses the `RequestTimeHandler` middleware for every requests received by the Popcorn app. diff --git a/lib/popcorn/examples/middlewares/example_advanced_logger.nit b/lib/popcorn/examples/middlewares/example_advanced_logger.nit index 7f8e3b1..4ca9120 100644 --- a/lib/popcorn/examples/middlewares/example_advanced_logger.nit +++ b/lib/popcorn/examples/middlewares/example_advanced_logger.nit @@ -48,7 +48,7 @@ class HelloHandler end var app = new App -app.use("/*", new RequestTimeHandler) +app.use_before("/*", new RequestTimeHandler) app.use("/", new HelloHandler) -app.use("/*", new LogHandler) +app.use_after("/*", new LogHandler) app.listen("localhost", 3000) diff --git a/lib/popcorn/examples/middlewares/example_simple_logger.nit b/lib/popcorn/examples/middlewares/example_simple_logger.nit index 98be552..4052169 100644 --- a/lib/popcorn/examples/middlewares/example_simple_logger.nit +++ b/lib/popcorn/examples/middlewares/example_simple_logger.nit @@ -30,6 +30,6 @@ end var app = new App -app.use("/*", new LogHandler) +app.use_before("/*", new LogHandler) app.use("/", new HelloHandler) app.listen("localhost", 3000) diff --git a/lib/popcorn/pop_handlers.nit b/lib/popcorn/pop_handlers.nit index 2c85e15..1c20b67 100644 --- a/lib/popcorn/pop_handlers.nit +++ b/lib/popcorn/pop_handlers.nit @@ -307,28 +307,71 @@ class Router # List of handlers to match with requests. private var handlers = new Map[AppRoute, Handler] + # List of handlers to match before every other. + private var pre_handlers = new Map[AppRoute, Handler] + + # List of handlers to match after every other. + private var post_handlers = new Map[AppRoute, Handler] + # Register a `handler` for a route `path`. # # Route paths are matched in registration order. fun use(path: String, handler: Handler) do - var route - if handler isa Router or handler isa StaticHandler then - route = new AppGlobRoute(path) - else if path.has_suffix("*") then - route = new AppGlobRoute(path) - else - route = new AppParamRoute(path) - end + var route = build_route(handler, path) handlers[route] = handler end + # Register a pre-handler for a route `path`. + # + # Prehandlers are matched before every other handlers in registrastion order. + fun use_before(path: String, handler: Handler) do + var route = build_route(handler, path) + pre_handlers[route] = handler + end + + # Register a post-handler for a route `path`. + # + # Posthandlers are matched after every other handlers in registrastion order. + fun use_after(path: String, handler: Handler) do + var route = build_route(handler, path) + post_handlers[route] = handler + end + redef fun handle(route, uri, req, res) do if not route.match(uri) then return + handle_pre(route, uri, req, res) + handle_in(route, uri, req, res) + handle_post(route, uri, req, res) + end + + private fun handle_pre(route: AppRoute, uri: String, req: HttpRequest, res: HttpResponse) do + for hroute, handler in pre_handlers do + handler.handle(hroute, route.uri_root(uri), req, res) + end + end + + private fun handle_in(route: AppRoute, uri: String, req: HttpRequest, res: HttpResponse) do for hroute, handler in handlers do handler.handle(hroute, route.uri_root(uri), req, res) if res.sent then break end end + + private fun handle_post(route: AppRoute, uri: String, req: HttpRequest, res: HttpResponse) do + for hroute, handler in post_handlers do + handler.handle(hroute, route.uri_root(uri), req, res) + end + end + + private fun build_route(handler: Handler, path: String): AppRoute do + if handler isa Router or handler isa StaticHandler then + return new AppGlobRoute(path) + else if path.has_suffix("*") then + return new AppGlobRoute(path) + else + return new AppParamRoute(path) + end + end end # Popcorn application. diff --git a/lib/popcorn/popcorn.nit b/lib/popcorn/popcorn.nit index 887c122..64e4459 100644 --- a/lib/popcorn/popcorn.nit +++ b/lib/popcorn/popcorn.nit @@ -48,12 +48,19 @@ redef class App redef fun answer(req, uri) do uri = uri.simplify_path var res = new HttpResponse(404) + for route, handler in pre_handlers do + handler.handle(route, uri, req, res) + end for route, handler in handlers do handler.handle(route, uri, req, res) + if res.sent then break end if not res.sent then res.send(error_tpl(res.status_code, res.status_message), 404) end + for route, handler in post_handlers do + handler.handle(route, uri, req, res) + end res.session = req.session return res end diff --git a/lib/popcorn/tests/res/test_example_static_multiple.res b/lib/popcorn/tests/res/test_example_static_multiple.res index 1b14f14..3a6692f 100644 --- a/lib/popcorn/tests/res/test_example_static_multiple.res +++ b/lib/popcorn/tests/res/test_example_static_multiple.res @@ -28,13 +28,6 @@ alert("Hello World!"); [Client] curl -s localhost:*****/ -Warning: Headers already sent! - - - -

Another Index

- - diff --git a/lib/popcorn/tests/test_example_advanced_logger.nit b/lib/popcorn/tests/test_example_advanced_logger.nit index 41193d2..29b3fc2 100644 --- a/lib/popcorn/tests/test_example_advanced_logger.nit +++ b/lib/popcorn/tests/test_example_advanced_logger.nit @@ -28,9 +28,9 @@ class TestClient end var app = new App -app.use("/*", new RequestTimeHandler) +app.use_before("/*", new RequestTimeHandler) app.use("/", new HelloHandler) -app.use("/*", new LogHandler) +app.use_after("/*", new LogHandler) var host = test_host var port = test_port diff --git a/lib/popcorn/tests/test_example_simple_logger.nit b/lib/popcorn/tests/test_example_simple_logger.nit index 50f6af9..e8aab41 100644 --- a/lib/popcorn/tests/test_example_simple_logger.nit +++ b/lib/popcorn/tests/test_example_simple_logger.nit @@ -28,7 +28,7 @@ class TestClient end var app = new App -app.use("/*", new LogHandler) +app.use_before("/*", new LogHandler) app.use("/", new HelloHandler) var host = test_host