new_app: Create a new web application

Description Usage Details Value See Also Examples

View source: R/app.R

Description

Create a new web application

Usage

1

Details

The typical workflow of creating a web application is:

  1. Create a presser_app object with new_app().

  2. Add middleware and/or routes to it.

  3. Start is with the preser_app$listen() method, or start it in another process with new_app_process().

  4. Make queries to the web app.

  5. Stop it via CTRL+C / ESC, or, if it is running in another process, with the $stop() method of new_app_process().

A web application can be

The presser API is very much influenced by the express.js project.

Create web app objects

new_app()

new_app() returns a presser_app object the has the methods listed on this page.

An app is an environment with S3 class presser_app.

The handler stack

An app has a stack of handlers. Each handler can be a route or middleware. The differences between the two are:

Routes

The following methods define routes. Each method corresponds to the HTTP verb with the same name, except for app$all(), which creates a route for all HTTP methods.

app$all(path, ...)
app$delete(path, ...)
app$get(path, ...)
app$head(path, ...)
app$patch(path, ...)
app$post(path, ...)
app$put(path, ...)
... (see list below)

presser also has methods for the less frequently used HTTP verbs: CONNECT, MKCOL, OPTIONS, PROPFIND, REPORT. (The method names are always in lowercase.)

If a request is not handled by any routes (or handler functions in general), then presser will send a simple HTTP 404 response.

Middleware

app$use() adds a middleware to the handler stack. A middleware is a handler function, see 'Handler functions' below. presser comes with middleware to perform common tasks:

app$use(...)

Handler functions

A handler function is a route or middleware. A handler function is called by presser with the incoming HTTP request and the outgoing HTTP response objects (being built) as arguments. The handler function may query and modify the members of the request and/or the response object. If it returns the string "next", then it is not a terminal handler, and once it returns, presser will move on to call the next handler in the stack.

A typical route:

app$get("/user/:id", function(req, res) {
  id <- req$params$id
  ...
  res$
    set_status(200L)$
    set_header("X-Custom-Header", "foobar")$
    send_json(response, auto_unbox = TRUE)
})

A typical middleware:

app$use(function(req, res) {
  ...
  "next"
})

Errors

If a handler function throws an error, then the web server will return a HTTP 500 text/plain response, with the error message as the response body.

Request and response objects

See presser_request and presser_response for the methods of the request and response objects.

Path specification

Routes are associated with one or more API paths. A path specification can be

Path parameters

Paths that are specified as parameterized strings or regular expressions can have parameters.

For parameterized strings the keys may contain letters, numbers and underscores. When presser matches an API path to a handler with a parameterized string path, the parameters will be added to the request, as params. I.e. in the handler function (and subsequent handler functions, if the current one is not terminal), they are available in the req$params list.

For regular expressions, capture groups are also added as parameters. It is best to use named capture groups, so that the parameters are in a named list.

If the path of the handler is a list of parameterized strings or regular expressions, the parameters are set according to the first matching one.

Templates

presser supports templates, using any template engine. It comes with a template engine that uses the glue package, see tmpl_glue().

app$engine() registers a template engine, for a certain file extension. The $render() method of presser_response can be called from the handler function to evaluate a template from a file.

app$engine(ext, engine)

An example template engine that uses glue might look like this:

app$engine("txt", function(path, locals) {
  txt <- readChar(path, nchars = file.size(path), useBytes = TRUE)
  glue::glue_data(locals, txt)
})

(The built-in tmpl_glue() engine has more features.)

This template engine can be used in a handler:

app$get("/view", function(req, res) {
 txt <- res$render("test")
 res$
   set_type("text/plain")$
   send(txt)
})

The location of the templates can be set using the views configuration parameter, see the $set_config() method below.

In the template, the variables passed in as locals, and also the response local variables (see locals in presser_response), are available.

Starting and stopping

app$listen(port = NULL, opts = server_opts())

This method does not return, and can be interrupted with CTRL+C / ESC or a SIGINT signal. See new_app_process() for interrupting an app that is running in another process.

When port is NULL, the operating system chooses a port where the app will listen. To be able to get the port number programmatically, before the listen method blocks, it advertises the selected port in a presser_port condition, so one can catch it:

presser by default binds only to the loopback interface at 127.0.0.1, so the presser web app is never reachable from the network.

withCallingHandlers(
  app$listen(),
  "presser_port" = function(msg) print(msg$port)
)

Logging

presser can write an access log that contains an entry for all incoming requests, and also an error log for the errors that happen while the server is running. This is the default behavior for local app (the ones started by app$listen() and for remote apps (the ones started via new_app_process():

See server_opts() for changing the default logging behavior.

Shared app data

app$locals

It is often useful to share data between handlers and requests in an app. app$locals is an environment that supports this. E.g. a middleware that counts the number of requests can be implemented as:

1
2
3
4
5
6
app$use(function(req, res) {
  locals <- req$app$locals
  if (is.null(locals$num)) locals$num <- 0L
  locals$num <- locals$num + 1L
  "next"
})

presser_response objects also have a locals environment, that is initially populated as a copy of app$locals.

Configuration

app$get_config(key)
app$set_config(key, value)

Currently used configuration values:

Value

A new presser_app.

See Also

presser_request for request objects, presser_response for response objects.

Examples

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# see example web apps in the `/examples` directory in
system.file(package = "presser", "examples")

app <- new_app()
app <- new_app()
app$use(mw_log())

app$get("/hello", function(req, res) {
  res$send("Hello there!")
})

app$get(new_regexp("^/hi(/.*)?$"), function(req, res) {
  res$send("Hi indeed!")
})

app$post("/hello", function(req, res) {
  res$send("Got it, thanks!")
})

app

# Start the app with: app$listen()
# Or start it in another R session: new_app_process(app)

presser documentation built on July 1, 2020, 5:49 p.m.