Heads are the basic building blocks for RoboHydra-based servers. They
are pieces of code that define the server behaviour when a request
comes for a given URL path. For example, one head might “listen to”
requests to any URL path under /static
(say, serving static files
from the filesystem), while another head might listen to requests to
/api
(say, returning a canned response that happens to be useful for
testing, or never replying, or waiting for a couple of seconds before
returning a 500 Internal Server Error response).
When RoboHydra receives a new HTTP request, it goes through all active heads in order. When it finds a head that matches the path of the incoming request, RoboHydra dispatches the request with that head. Otherwise, it returns 404.
The most important type of head is RoboHydraHead
. This is the
generic head that all other heads inherit from. It’s the most generic
because it’s defined by a URL path regular expression and a Javascript
handling function. To create a generic head, you need to pass the
following parameters:
name
(optional): a string with a symbolic name for the head.path
: a string with a regular expression matching the paths this
head should handle. Note that there are implicit “^” and “$” at the
beginning and end of the string, and that GET parameters are
stripped from the incoming request before trying to match with this
regular expression. Also, URL endings are normalised in such a way
that a head with path /foo
or /foo/
will match requests for both
/foo
and /foo/
.method
(optional): a string or array specifying the HTTP
methods the head will match. It defaults to *
, meaning it matches
every method, but can be set to a specific method like GET
or
OPTIONS
, or to an array of accepted methods (eg. ["GET",
"OPTIONS"]
).hostname
(optional): a string with a regular expression matching
the hostnames this head should handle (by default, .*
). It matches
the Host
header of the incoming request, although it will not
contain the port, just the hostname itself (eg. if a request that
has a Host
header set to localhost:3000
, the head will check if
its hostname
regular expression matches localhost
).handler
: a function that receives three parameters: req
, res
and next
. This function will be called for every request the head
has to handle (it’s not enough that the URL path in the request
matches the path
property, because there could be another head
before handling the request –see the introduction in this
page). This function must always call res.end()
to end the
request, either explicitly or implicitly (eg. by passing the res
object to the next
function).detached
(optional): a boolean specifying if the head should be
detached when starting RoboHydra. If this is true, RoboHydra will
behave as if the head wasn’t there. Heads can be detached and
re-attached from the RoboHydra admin interface.reset
(optional): a function that gets executed when the head
has to be reset (when the scenario it belongs to is started).Apart from a normal regular expression, the path string supports the
special syntax :foobar
that matches any URL path fragment (eg. the
path /articles/:id
would match /articles/123
,
/articles/title-of-the-article
and so on, but not
/articles/view/123
). See the documentation for the request
object to see how to access these
parameters.
NOTE: parameters name
, method
, detached
, hostname
and
reset
are implicitly part of all heads, regardless of type.
In some circumstances, heads might want to call other heads below it. The main use cases for doing this are:
This is akin to a very simple middleware system. In order to call
other heads, the handler
function receives a third parameter,
next
. This third parameter is a function that receives two
parameters (request and response objects). This function, when called,
will try to dispatch the given request object with any heads below the
current one. Naturally, this processing will end up with a call to the
end
function in the response object passed to it.
The head calling the next
function (let’s call it the “middleware
head”) can decide what is being passed as request and response
objects. Often you will just pass the middleware head’s request and
response objects themselves, possibly after modifying them; but
sometimes you will want to pass mock requests and responses. For
example, passing a mock response object will allow you to inspect its
contents before you decide if you pass that response as is (by using
the forward
method), tweak the response before sending
(eg. modifying it before calling forward
), retry the request with a
different URL if you didn’t like the response, etc.
This is a simple head that echoes back some of the information from the original request (eg. for debugging purposes). It illustrates how to access different request data:
This is another head that never closes the connection:
This is another head that sets some headers on the response:
And this is an example of how to use the next
function. Say that we
have a head that proxies to the development server. If we place the
following head before it, we’ll never get 500 pages:
See the tutorial for other uses of the next
function.
Apart from the generic RoboHydra head, there are other classes of
heads available. Namely, RoboHydraHeadStatic
,
RoboHydraHeadFilesystem
, RoboHydraHeadProxy
,
RoboHydraHeadFilter
and RoboHydraHeadWatchdog
.
This head always returns fixed, static content. A head of this class has the following properties:
content
(optional): the content to be sent back for every
request. This can be a string or a plain Javascript object. While
this property is optional, either this or responses
must be
present.responses
(optional): an array of responses to send to the client
(round-robin by default, but see repeatMode
). Each response in the
array can contain the properties content
, contentType
, headers
and statusCode
. For each of these properties that is not given,
the head’s property is used instead. While this property is
optional, either this or content
must be present.path
(optional): the regular expression matching URL paths to be
handled by this head. Defaults to /.*
if not present.contentType
(optional): the value for the Content-Type
header in
the response. If not present and content is an object, defaults to
application/json
.headers
(optional): extra headers to set in the response. The
contentType
property has precedence over the possible value of the
Content-Type
header passed in this property.statusCode
(optional): the status code for the response. By
default it’s 200.repeatMode
(optional): the way RoboHydra will repeat the responses
when using the responses
property. By default it’s round-robin
,
but it can be set to repeat-last
to make the head repeat the last
response in the list.Simple example setting the status code and content:
Another example that round-robins between several responses:
This head serves files from the filesystem. It has the following properties:
documentRoot
: the root local filesystem path from which to serve
files.mountPath
(optional): the path to “mount” the head in. This works
a bit differently than the path
property in other heads. In this
case, it’s not a regular expression, but a path under which
everything is considered handled by the head. It defaults to /
.indexFiles
(optional): the list of files that will be considered
“index” (ie. if a request comes for a directory, and that
directory contains one of the index files, the index file is served
instead). Defaults to index.html
, index.htm
, home.html
,
home.htm
. Order matters, first matching file will be used as
index.fs
(optional): an object that behaves like Node’s fs
module. Useful if you need to fake stuff.mime
(optional): an object that behaves like Node’s mime
module. Useful if you need to fake stuff.This head will serve files from the filesystem, taking into account
If-Modified-Since
request headers and sending correct
Last-Modified
headers and 304 status codes when necessary. For
example, a head with documentRoot = /var/www
and mountPath =
/static
that receives a request to /static/css/main.css
will try to
serve the file /var/www/css/main.css
.
This trivial example serves the static files in the local directory
staticfiles/
for requests to the URL path /static
, in such a way
that the request http://localhost:3000/static/foo/bar.css
will serve
the local file staticfiles/foo/bar.css
. Note how this head uses
mountPath
, not path
, and it’s a URL path, not a regular
expression:
This other example uses README
and README.md
as possible index
pages, instead of the more common index.html
and such:
This head reverse-proxies request to another URL. It has the following properties:
proxyTo
: the root URL the requests are going to be proxied to.mountPath
(optional): the path to “mount” the head in. See the
documentation for RoboHydraHeadFilesystem
.setHostHeader
(optional): sets the Host
header to the hostname
of the proxied URL (proxyTo
property) in the requests to the
target URL. Defaults to true
.httpRequestFunction
(optional): an object that behaves like
Node’s http.createClient
function. Useful if you need to fake
stuff.httpsRequestFunction
(optional): an object that behaves like
Node’s https.createClient
function. Useful if you need to fake
stuff.This head will proxy requests to the given proxyTo
URL. For
example, a head with proxyTo = http://github.com/operasoftware
and
mountPath = /github
that receives a request to /github/robohydra
will proxy the request to the URL
http://github.com/operasoftware/robohydra
.
The first example proxies everything to what is presumably an internal development server. This is useful for a number of reasons, like (1) combined with a filesystem head, frontend files can be served from the local filesystem, while the backend of the development server can be used; (2) as a logger for the requests that go to the server; or (3) to use the development server most of the time, but opening the possibility to override certain URL paths temporarily to do exploratory testing:
Note, however, that those requests will be made with the original host
(ie. probably something like localhost
). If you want the requests to
be made with develop.example.com
as a request host, eg. because the
server has several virtual hosts, you can set the setHostHeader
property:
This head filters a request processed by another head. It has the following properties:
path
(optional): the regular expression matching URL paths to be
handled by this head. Defaults to /.*
if not present.filter
: a function receiving a Buffer
object with the response
body and returning the filtered response to be sent back to the
client. The returned value can be either a string or another
Buffer
object.This head will match certain URLs and pass those requests through for
processing by other heads (see the next
function
documentation). When the response comes back from the next head, the
RoboHydraHeadFilter
head will take the response body, apply the
given filter
function (transparently uncompressing and compressing
back if necessary, and also updating the Content-Length
header, if
present) and send that as a response.
This trivial example shows how to turn the whole response body into uppercase letters:
Note that the parameter to the filter
function is a Buffer
, so you
can treat binary data, too. Also, if you want to treat it as a string,
you must call the toString()
method.
This head acts as a watchdog, looking for requests/responses matching certain criteria and executing a given action. It has the following properties:
path
(optional): the regular expression matching URL paths to be
handled by this head. Defaults to /.*
if not present.watcher
: a function receiving the Request
and Response
objects
for every request. If this function returns true, the reporter
function will be executed.reporter
(optional): a function receiving the Request
and
Response
objects for the requests that make watcher
return true.This head will look for “interesting” request/responses (by checking
if the watcher
function returns true), and when finding one, it will
execute a given action (the reporter
function; by default, printing
some extra output to the console). Note that both these functions will
receive a special Response
object that guarantees that its body
property is always uncompressed. If you need the original (whether it
was compressed or not), you can check the rawBody
property.
The first example shows how to set a “watchdog” for requests from a concrete browser (Opera in this case). By default, when such a request is found, RoboHydra will print a warning in the console (together with the server log):
This other example shows how to set a watchdog for requests that produced a certain kind of response. It also customises what happens when such a request is received:
This head replays the given traffic. The traffic is a JSON string or an object. It has the following properties:
traffic
: a JSON string or an object describing the traffic to
replay in the same format saved by the replayer
plugin. Essentially, it’s an object with URL
paths as keys, and arrays as values. Each value in the array is a
response that will be used in a round-robin fashion, consisting of
three keys: statusCode
(optional), headers
(optional) and
body
(mandatory, in base64 format).Let’s say that we have saved the traffic for the interaction between a
client and a very simple API server that has two endpoints: /login
and /timeline
. That interaction only had one call to /login
, and
two calls to /timeline
that yielded two different results. If we
replay that traffic, the client will always receive the same response
when trying to login, but it will alternatively receive the first and
the second saved responses when trying to fetch the timeline:
If we have the traffic saved, it’s much more likely that we just want to load it from a file:
Back to the documentation index.