After adding a few simple routes to your server, you will eventually come to the point where you need to add things like authentication, authorization, and have other use cases that need to be solved before your handler is ever reached. There are multiple approaches to this, such as creating a function to check authentication credentials, another for assigning authorization tokens that are called when every request is received initially by the handler. You could do this in the route prerequisites mentioned previously. However, if you forget to add these to a single route, or had something executed before your authentication function is called, you've left yourself open to secure data being accessible to unauthenticated users.
The approach which hapi uses to solve this is to have a well-defined request life cycle, with a reliable series of events that happen on every request. This gives you a fairly granular control over a request in an easy-to-extend and readable way.
The problem with the preceding approach of route or function ordering is that it is an implied life cycle, not a well-defined one, and is it is not always obvious where we are in the request life cycle. I see this with notes on modules such as express-validator
, which must be called after body-parser
or else the payload won't be validated, and no error will be thrown. This is a logic issue, not one related to code, and as such, there will be no errors for problems like this, which makes debugging a huge pain. I mentioned building infrastructure instead of application logic in the previous chapter, and this is a classic example of that.
With hapi, you don't have to worry about any of these things, because at all times you'll know where you are in the request life cycle. This is one feature that also makes it much easier to understand other codebases and the intention of the original developers when taking action on points within the request life cycle.
Let's look at the different stages in the request life cycle, bearing in mind that it is a lot to take in at once, and not necessary to remember. I often find myself returning to the API documentation to look up the event names to know when I want a certain action to take place, each time quite pleased that it was not my responsibility to implement, test, and document the life cycle I've built in each application. The full life cycle in the order of events can be found on hapijs.com at http://hapijs.com/api#request-lifecycle. I will cover it briefly here. So, when a request is received by a hapi server, it goes through the following events:
onRequest
extension point is called.Every request will call this extension point. This is before any authentication, payload parsing, and so on is done. This extension point is unique in that it occurs before any route matching has occurred. The request
object is decorated with the request.setUrl()
and request.setMethod()
methods, which can be used for rewriting requests only at this extension point.
request.path
.onPreAuth
extension point is called.After the onPreAuth
extension point, the request is authenticated. The payload is always parsed in this step so that payload authentication can also be performed.
onPostAuth
extension point is called.onPreHandler
extension point is called.After the onPreHandler
extension point is called, the route prerequisites will be evaluated, and then the route handler itself.
onPostHandler
extension point is called.Here request.response
can be modified or a new response generated via a call to reply()
. Usually, this would be to replace an error with an HTML page, like a 'not found' or internal server page.
onPreResponse
extension point is called.Again, as in the onPostHandler
extension point, request.response
can be modified or a new response generated via reply()
. However, here the response will not go through the onPreResponse
point to prevent an infinite loop.
Take note that some events are always called, and some are not. You may have not noticed the extension points throughout the life cycle; let's now look at how to add actions to extension points during different stages of the request life cycle.
Extending a request life cycle event has an easy-to-use API:
server.ext(event, method, [options])
So, if you wanted to log in to the console every time you received a request, it would be as follows:
… server.ext('onRequest', function (request, reply) { console.log(`request received: ${request.}`); return reply.continue(); }); …
Note the function reply.continue()
here—this is to return the control to the framework and indicate continuing with the request life cycle. The function reply()
without continuing at this extension point would indicate that a response should be sent to the user from here, as it is likely there has been an error, and does not continue with the request life cycle. Not realizing this can be a common error when starting out with hapi. I will cover this in more detail when talking about the reply
interface.
18.222.184.200