Handlers
Understanding handlers is central to creating a service using Serviceberry. All things that interact with service requests do so through a common handler API. This means your endpoints, plugins, error coping, serializers and deserializers are all created in the same way. This makes creating services easier and it makes creating plugins easier.
A handler can be a function, or an object,
or a promise that resolves to a handler function
or object. Handler objects can be any object with a use
method that is a handler function.
Handler Functions
Handler functions all have the same arguments signature (request, response)
and ability to control requests.
Request and response are wrapper objects for the
http.IncomingRequest
and http.ServiceResponse
arguments passed to the underlining http.Server
request
event listener.
Handlers are added to the trunk, branches, and leaves
of your service using methods use
, cope
and on
. When Serviceberry routes each request through your service,
it builds a queue of handlers. Each handler in the queue is then called one after the other. Each given control of the
request and response until control is yielded to the next handler. Handlers yield
control through request and response methods, by the value they return, or by throwing
an error.
Three Possible Outcomes
Each handler has exactly one opportunity to control the request and response. Once action has been taken there are three possible outcomes.
- Proceed with the request (typical for a plugin, serializer, or deserializer).
- Complete the request and send a response to the client (typical for an endpoint).
- Fallback to the nearest error coping handler.
Proceeding (1) or falling back (3) could implicitly result in sending a response when there are no more handlers in the queue
Controlling the Outcome
Which outcome occurs depends primarily on the value returned from a handler. A handler can control the request and response by the following actions.
- Return nothing and call
request.proceed([result])
. - Return the result.
- Return a
promise
for the result (any thennable should work).
Calling request.proceed([result])
, returning anything else other than an error, and
returning a promise
that resolves
to the result are all functionally equivalent. They all cause the request to proceed. The next handler in the queue
will be called and control will be given to it. If the queue has no more handlers, the request will end and
response.send([options])
is call implicitly.
- Return nothing and call
response.send([options])
.
Returning nothing and calling response.send([options])
will complete the request (as far
as the handler queue is concerned) and begin the process of serializing the writing the response to the client.
- Return nothing and call
request.fail(error[, status[, headers]])
. Throws the resulting error halting execution of the handler. - Return, resolve to, or reject with an error.
- Throw an error.
Calling request.fail(error[, status[, headers]])
, returning an error, throwing
an error, or returning a promise
that is rejected with an error or resolves to an error are all functionally equivalent. They all cause control to fallback
to the nearest error coping handler. If no error coping handler is found, the request will be end and
response.send([options])
is call implicitly sending the error to the client. Errors are
available in error coping handlers at request.error
.
The first of any of the actions above after a handler is called and the handler's control is yielded. When control
is yielded, request.proceed([result])
, request.fail(error[, status[, headers]])
,
and response.send([options])
methods given to the handler are all rendered useless.
request.proceed([result]))
, request.fail(error[, status[, headers]])
,
and response.send([options]))
can all be called asynchronously as long as a handler returns nothing
or a promise
. Additionally none
are required to be called with context so each can be safely passed directly as callbacks - such as
.then(request.proceed, request.fail)
.
Handler Objects
Everywhere a handler is accepted as an argument, an object can be used. It can be any object that has a use
method that is a handler function. When Serviceberry is passed a handler object it binds the object's use
method to
the object so that handlers can be stateful. This can be particularly useful for plugins.
Handler Promises
Everywhere a handler is accepted as a argument, a promise
can be used. The promise
must
resolve to a handler function or a handler object. The service trunk
will wait until all handlers are resolved before starting.
To learn about using and creating plugins, checkout the next guide.