11 Middleware
Middlewares are functions that run before anything in the application, and are mostly used for request pre-processing.
Let’s see an example application that uses middleware:
library(ambiorix)
#' Middleware to print current date & time
#'
#' @param req The request object.
#' @param res The response object.
1now <- \(req, res) {
print(Sys.time())
}
home_get <- \(req, res){
res$send("Using {ambiorix}!")
}
about_get <- \(req, res){
res$text("About")
}
app <- Ambiorix$
new()$
2 use(now)$
get("/", home_get)$
get("/about", about_get)
app$start()- 1
- Just like any request handler in Ambiorix, a middleware must take the request and response objects. Why, you ask?
- 2
-
To register the middleware, pass it to the
use()method of the app.
Go ahead and run the app. Observe the output on your console when you visit either of the two routes (/ & /about).
You will notice that the current date & time is printed to the console each time a request is made.
Now stop the app and modify home_get() as follows:
home_get <- \(req, res) {
print("In home_get()")
res$send("Using {ambiorix}")
}Re-run the app and visit /. What do you observe? Which print statement is made first?
That should be enough to show you that middlewares indeed run before anything else in the application.
Why must a middleware take the request and response objects, you ask?
- Middlewares can do request pre-processing. This means they can modify a request way before it gets to any other handler.
- You can choose to return a response from the middleware without the http request having to get to any other handler.
11.1 Request pre-processing
There is no better way to explain this than by using an example.
library(ambiorix)
library(htmltools)
#' Auth middleware
#'
#' @param req The request object.
#' @param res The response object.
auth <- \(req, res) {
user <- list(
first_name = "Kennedy",
last_name = "Mwavu",
email = "mwavu@mail.com"
)
req$user <- user
}
home_get <- \(req, res) {
# get 'user' from the request object:
user_details <- req$user
html <- tags$div(
tags$h3("User details:"),
tags$ul(
tags$li("First name:", user_details$first_name),
tags$li("Last name:", user_details$last_name)
),
tags$p(
"To see the user email, visit",
tags$a(
href = "/about",
"the about page"
)
)
)
res$send(html)
}
about_get <- \(req, res) {
user_details <- req$user
html <- tags$div(
tags$h3(user_details$first_name, user_details$last_name, ":"),
tags$ul(
tags$li("Email:", user_details$email)
)
)
res$send(html)
}
Ambiorix$
new()$
use(auth)$
get("/", home_get)$
get("/about", about_get)$
start()When our auth middleware runs, it adds the object user to request object. Any other handler can then access the user object using req$user.
Be careful not to unknowingly overwrite existing fields in the request object.
11.2 Returning from a middleware
Sometimes, when a request lacks something you need or the client making the request is not authorized, you can opt for a middleware which checks for that and returns early before the request hits any other handler.
Using the same example as above, modify the auth middleware to now be this:
#' Auth middleware
#'
#' @param req The request object.
#' @param res The response object.
auth <- \(req, res) {
user <- list(
first_name = "Kennedy",
last_name = "Mwavu",
email = "mwavu@mail.com",
status = "disabled"
)
if (!identical(user$status, "enabled")) {
return(
res$set_status(401L)$send("Unauthorized!")
)
}
req$user <- user
}Start the app and try visiting any of the endpoints defined earlier. What happens if you toggle the user status to “enabled”?
If you have noticed, a middleware behaves more or less like a normal function:
- It takes parameters (
req,res) - Has return value
When its return value is the response object, the application immediately returns that as the response to the client, without any further handling.
12 Specific routes
13 Specific Methods
14 Order matters
15 CORS
16 Auth
A good use-case of middleware is protecting certain routes in your application and requiring the client to be authenticated and/or authorized to access those routes.