# routr
routr is a simple and versatile router for R based web servers. For
people not familiar with back-end development, a router is a piece of
middleware that delegates HTTP requests to the correct handler function.
The delegation is based in the URL of the request and in essence means
that requests directed at */persons/thomas/* ends up in another handler
than */packages/routr/*.
routr is heavily inspired by other routers build for other platforms,
especially those for [Express.js](https://github.com/expressjs) and
[Ruby on Rails](https://github.com/rails/rails), though it doesn’t
mimick either.
## Installation
`routr` is available on CRAN and can be installed in the regular way.
``` r
install.packages('routr')
```
Alternatively you can grab the development version from Github with
``` r
# install.packages('devtools')
devtools::install_github('thomasp85/routr')
```
## Functionality
A router is build up of several seperate routes that are collected in a
route stack. The stack recieves the request and passes it on to the
first route in the stack. Depending on whether the route can handle the
request and whether the handler signals a fall-through, the request is
passed along the stack until a handler signals that no further
processing should be done. This means that it is possible to stack
different functionality like user verification, static resource serving,
etc. on top of each other.
### The handler
A handler is a function that accepts the arguments `request`,
`response`, `keys`, and `...`. The handler must return a boolean
indicating if the request should be passed down the stack (`TRUE`) or
not (`FALSE`). `routr` uses the
[`reqres`](https://github.com/thomasp85/reqres#reqres) package to
provide powerful request and response classes that makes it easy to work
with an HTTP exchange. An example of a simple handler is:
``` r
h <- function(request, response, keys, ...) {
response$status <- 200L
response$type <- 'html'
response$body <- '
Hello World!
'
return(FALSE)
}
```
No matter the content of the request passed to this handler it will
return a “Hello World!” to the client. Because it returns `FALSE` it
block any other handlers below it to modify the response.
### The route
A route is a collection of handlers. For any given request, only one
handler in the route will be called. A route is an object of the R6
Route class and can be created as so:
``` r
route <- Route$new()
route$add_handler('get', '/hello/:what/', h)
```
The first argument to `add_handler` defines the [request
type](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods)
while the second defines the path that the handler responds to. The path
need not be static. In the above example the `:what` defines a variable
meaning that the handler will respond to any `/hello//`
variation. The variable and the value is available to the handler in the
keys argument. For instance, if a request with the URL `/hello/mars/`
were passed through the route, the keys argument passed to the handler
would contain `list(what = 'mars')`. Variables can only span a single
level, meaning that the above handler would not respond to
`/hello/jupiter/saturn/`. To match to anything use `/hello/*` for
responding to any sub-URL to `hello`. Matches to `*` will not end up in
the keys list. If several paths in a route matches a URL the most
specific will be used, meaning that `/*` will match everything but will
always chosen last. With all that in mind lets change the handler to
respond to the `what` variable:
``` r
h <- function(request, response, keys, ...) {
response$status <- 200L
response$type <- 'html'
response$body <- paste0('Hello ', keys$what, '!
')
return(FALSE)
}
route$add_handler('get', '/hello/:what/', h)
```
Let’s also add a fallback handler that captures everything:
``` r
hFallback <- function(request, response, keys, ...) {
response$status <- 200L
response$type <- 'html'
response$body <- 'I\'m not saying hello to you
'
return(FALSE)
}
route$add_handler('get', '/*', hFallback)
```
### The route stack
The route stack manages several routes and takes care of receiving a
request and returning a response. A route stack is an object of the R6
class `RouteStack` and is created like this:
``` r
router <- RouteStack$new()
router$add_route(route, 'test')
```
The order in which routes are added to the stack determines the calling
order, with those added first taking precedence over those added later.
Request are handled by the `dispatch` method like so:
``` r
router$dispatch(request)
```
### Use with fiery
A `RouteStack` is a
[fiery](https://github.com/thomasp85/fiery)-compliant plugin meaning
that it can be passed to the
[`attach()`](https://rdrr.io/r/base/attach.html) method of a fiery
server. This will set the server up to pass requests through the route
stack and use the resulting response automatically
``` r
app <- fiery::Fire$new()
app$attach(router)
app$ignite(block = FALSE)
# In Terminal (or visit in browser)
# curl http://127.0.0.1:8080/hello/mars/
# Hello Mars!
app$extinguish()
```
By default the router responds to `request` events but can also be used
to dispatch on `header` and `message` events. In the latter case the
request that is send through the handlers is a modified version of the
request used to establish the WebSocket version. If used as a WebSocket
router a way to extract the path to dispatch on must be provided as part
of the `RouteStack` construction.
## Code of Conduct
Please note that the ‘routr’ project is released with a [Contributor
Code of Conduct](https://routr.data-imaginist.com/CODE_OF_CONDUCT.html).
By contributing to this project, you agree to abide by its terms.
# Package index
## Tidy API
While the R6 classes that are the foundation of routr allow you to
manipulate and work with them directly through their class methods, it
is often more convenient to use a functional and pipe-friendly
interface.
- [`route()`](https://routr.data-imaginist.com/reference/Route.md) :
Construct a new route
- [`route_stack()`](https://routr.data-imaginist.com/reference/route_stack.md)
: Combine routes in a stack
- [`route_add()`](https://routr.data-imaginist.com/reference/route_add.md)
[`route_remove()`](https://routr.data-imaginist.com/reference/route_add.md)
[`route_get()`](https://routr.data-imaginist.com/reference/route_add.md)
: Route handlers
- [`route_merge()`](https://routr.data-imaginist.com/reference/route_merge.md)
: Merge one route into another
## Base Classes
A router based on routr is build up of Route objects combined together
into a RouteStack object. Requests are passed through each route in a
RouteStack and subjected to whatever handlers get matched to the URL at
each step.
- [`Route`](https://routr.data-imaginist.com/reference/Route-class.md) :
Single route dispatch
- [`RouteStack`](https://routr.data-imaginist.com/reference/RouteStack.md)
: Combine multiple routes for sequential routing
- [`AssetRoute`](https://routr.data-imaginist.com/reference/AssetRoute.md)
: Static file serving
## Route constructors
A few predetermined route functionalities have constructors that help
you create them easily.
- [`asset_route()`](https://routr.data-imaginist.com/reference/asset_route.md)
: High performance route for serving static files
- [`resource_route()`](https://routr.data-imaginist.com/reference/resource_route.md)
: Create a route for fetching files
- [`sizelimit_route()`](https://routr.data-imaginist.com/reference/sizelimit_route.md)
: Limit the size of requests
- [`openapi_route()`](https://routr.data-imaginist.com/reference/openapi_route.md)
: Create a route for serving OpenAPI documentation of your server
- [`report_route()`](https://routr.data-imaginist.com/reference/report_route.md)
: Create a route that renders and serves an Rmarkdown or Quarto report
- [`shared_secret_route()`](https://routr.data-imaginist.com/reference/shared_secret_route.md)
: Reject requests not in possession of the correct shared secret