```{r echo = FALSE} options(markdown.HTML.options = "toc") ``` # Non-interactive servers and Rook Deployment At the heart of the server side of the AnalysisPageServer system is a mapping from handler functions to web services. You define the set of functions to represent by building an `AnalysisPageRegistry`, then deploy the application either in Rook (running in your local R server) or under Apache using the [Rapache](http://rapache.net/) system. Your handler functions are built into objects called `AnalysisPage`s. Each `AnalysisPage` can be a service, which might return for example some arbitrary JSON string, or it can be a fully interactive page, for example making a plot and returning a data frame. This document explains the simpler service-type `AnalysisPage`. It also explains how to deploy your application with Rook. Interactive pages and servers are described on [another page](InteractiveApps.html). ## Rook >= 1.1 + fork Rook version >= 1.1 and fork are required for the examples in this vignette. I am going to check if they are available. ```{r message = FALSE, echo = FALSE} library(AnalysisPageServer) rookforkOK <- AnalysisPageServer:::checkRookForkForVignettes() ``` ----------------- ## The simplest AnalysisPage service-type `AnalysisPage`s are built with the `build.service()` constructor. All you have to do is pass a handler function to it and give it a name: ```{r message=FALSE} library(AnalysisPageServer) hello <- build.service(function() "Hello, world!", name = "hello") class(hello) ``` > Note: Even though, as we will demonstrate in a moment, this is just a service and not a full-blown web page we still call it an `AnalysisPage`. Since, from the server's point of view, there is not much difference between a webservice and a web page, in this documentation I usually just say "page" when saying "page or service" would be more accurate. ## The simplest AnalysisPageRegistry Next we build an `AnalysisPageRegistry`. This example only has one `AnalysisPage`, not too interesting, but we still need this object: ```{r} reg <- new.registry(hello) class(reg) ``` An `AnalysisPageRegistry` is not much more than a lookup for `AnalysisPage`s. You can get the names of all registered pages: ```{r} pages(reg, include.services = TRUE) ``` The `include.services` switch has to be turned on because the default is to return only the interactive pages, and `hello` is not interactive. You can retrieve a particular page: ```{r} identical(get.page(reg, "hello"), hello) ``` ## From AnalysisPageRegistry to Rook App Once the registry object is built it can be deployed. In this documentation page we'll deploy all of our applications via Rook using the local R server. This is useful for development and informal purposes but for production scale an RApache or FastRWeb deployment is required. The combination of Rook+Rhttpd does not handle concurrency well and generally tends to crash a lot. Also, some versions of Rook will only listen on the local lookback device, which would prevent sharing your server with anyone else. > Note: Your choice of deployment is completely independent of how you build the `AnalysisPageRegistry`, which is the main focus of this documentation page. Now we'll start the server: ```{r eval=TRUE, message=FALSE} port <- 3198 ## or some other port server <- startRookAnalysisPageServer(reg, port = port) server ``` Be aware that the function `startRookAnalysisPageServer` starts the server in a new process. This seems slightly more stable than the typical Rook deployment in the same process, where your continued computations might interfere with incoming requests. In particular, if you try to call to the server from the same R process it would hang. The object that this function returns can be passed to `kill.process()` to turn off the server: ```{r} kill.process(server) ``` The downside of starting a new process is that you might forget about it when you quit R. Not only does this waste system resources, but it actually holds on to the port, which means you won't be able to start another server on that port. To protect against this, you should get in the habit of starting your servers with two lines of code: ```{r eval = FALSE} server <- startRookAnalysisPageServer(reg, port = port) on.exit(try(kill.process(server))) ``` The second line registers an expression to evalute when R exits, which ensures that the children don't survive. Since the triggering of these `on.exit` handlers is a bit tricky in the `knitr` environment we'll make explicit calls to `kill.process(server)` in this document. If you are a Rook nerd and want to do fancier things, like combine this Rook app with other applications, then you can get the Rook App object like this: ```{r message = FALSE} app <- new.rook.analysis.page.app(reg) ``` With this object you also have the possibility of deploying your server in the normal Rook way, which avoids using the `fork` package. ```{r eval = FALSE} rook.server <- Rook::Rhttpd$new() rook.server$add(app, name = "RAPS") rook.server$start(port = port) ## Ping the server from another R process or from your web browser ... ## Then stop the server rook.server$stop() ``` ```{r echo = FALSE} if(rookforkOK) { rook.server <- Rook::Rhttpd$new() rook.server$add(app, name = "RAPS") rook.server$start(port = port) ## Ping the server from another R process or from your web browser ... ## Then stop the server rook.server$stop() } else { no.rook.fork.msg } ``` ## Starting the server We provide the `startRookAnalysisPageServer()` function to start your server, rather than interfacing directly with `Rook::Rhttpd()`. This function accepts an `AnalysisPageRegistry` object, so you don't have to build your Rook object, and also uses the `fork` package to start the server in a child process. This lets us call to the server from the parent process, which would otherwise block. It also makes it easy to recover from disaster by simply killing the child process without having to restart the parent R process. Let's turn the server back on and see how it works. ```{r} server <- startRookAnalysisPageServer(reg, port = port) ``` Then open your web browser:
> Dissecting the URL: "127.0.0.1" is the loopback device that Rook uses---it means your local host. The port is an arbitrary choice for this demonstration page (sorry if the port from this screen shot does not match the port in this documentation). If you don't specify a port then Rook/R will choose one for you at random, and each time you try your server you will have to modify the port. The "/custom" prefix cannot be modified---that is how Rook+Rhttpd works. "RAPS" is what I've named the server within Rook. "R/analysis" is a prefix within the `AnalysisPageServer` system, which is the location from which all the pages are served. Finally, the name of the page is JSON-encoded to form the full request. Once the server is running we could query it in theory from any language and possibly even remotely. In this document we will demonstrate the server is working by constructing the URL (using the `service.link` function) and then calling `readLines()`. ```{r} app.base.url <- paste0("http://127.0.0.1:", port, "/custom/RAPS") url <- service.link(page = "hello", app.base.url = app.base.url) cat(readLines(url, warn = FALSE), "\n") ``` (`warn = FALSE` is added to the `readLines` call to suppress a warning message about incompete final lines.) That's "Hello, World!". Astute observers will note that the greeting string was quoted in the response, even though it was displayed with `cat` rather than just normal R output. The reason for this is that all responses are automatically JSON-encoded. More on this later, and also how to turn it off. ```{r} kill.process(server) ``` ## Example: Multiple services in one application Let's put two pages in the same application. We'll re-use the `hello` page but also have a page which calculates the sine of an argument: ```{r} sine <- build.service(function(theta) sin(theta), name = "sine") reg <- new.registry(sine, hello) server <- startRookAnalysisPageServer(reg, port = port) ``` As mentioned above, all of the return values are by default JSON-encoded. We can use the `fromJSON` function in R to turn them back into R objects. For demonstration purposes within this document we'll wrap up the steps of constructing the URL, calling the server, and decoding the response into a function: ```{r} show.analysis <- function(page, params = list(), show.url = FALSE) { url <- service.link(page, params, app.base.url) if(show.url) cat(url, "\n") response <- readLines(url, warn = FALSE) rjson::fromJSON(response) } show.analysis("hello", show.url = TRUE) show.analysis("sine", list(theta = 3.141592653), show.url = TRUE) kill.process(server) ``` ## Example: JSON encoding of complex return value Whatever your function returns will be JSON encoded with `toJSON`. ```{r} cx.return <- build.service(function() { list(A = 1:5, B = "Able was I", C = list(x = 1, y = c(TRUE, FALSE, TRUE))) }, name = "complex") reg <- new.registry(cx.return) server <- startRookAnalysisPageServer(reg, port = port) show.analysis("complex") kill.process(server) ``` ## Example: URL and JSON decoding of parameter values The parameters are URL decoded then JSON decoded and *then* passed to your function. This handler takes a complicated structure and an integer as parameters. The structure should be a hash with an array element keyed by "A". The element of that array indexed by the integer is returned. ```{r} cx.param <- build.service(function(struct, n) { ## In Javascript: ## struct["A"][n - 1] struct$A[n] }, name = "cxpar") reg <- new.registry(cx.param) server <- startRookAnalysisPageServer(reg, port = port) show.analysis("cxpar", list(struct = list(A = c(3,1,4,1,5,9,2,6,5,3)), n = 6), show.url = TRUE) kill.process(server) ``` ## Example: Arbitrary response Sometimes the automatic JSON encoding of the response is a hassle. Or, you might want to set the MIME type of the response. If you explicitly return an `AnalysisPageResponse` object then you can have finer control. This is done with the `new.response` constructor. In this example we return a complete HTML page. ```{r} poem.file <- system.file("examples/in-a-station-of-the-metro.html", package="AnalysisPageServer") poem.html <- readLines(poem.file, warn = FALSE) poem <- build.service(function() { new.response(paste0(poem.html, "\n"), content.type = "text/html") }, name = "poem") reg <- new.registry(poem) server <- startRookAnalysisPageServer(reg, port = port) url <- service.link("poem", app.base.url = app.base.url) readLines(url, warn = FALSE) ``` Or open it in the browser:
```{r} kill.process(server) ``` ## Example: Plot response The `new.response` constructor lets us return other things. For example, we could even put a plot into the response. Let's encode the Maunga Whau Volcano plot that we did in the static content example page. We'll use R's `readBin` to read it into a raw vector since it is binary data. The single parameter to the function will be an array of colors to use for the color ramp. ```{r eval = FALSE} volcano <- build.service(function(colors = c("red","white","purple")) { plotfile <- tempfile(fileext = ".png") png(plotfile, width = 600, height = 400) par(mar=c(1, 1, 4, 1)) col <- colorRampPalette(colors)(50) image(datasets::volcano, xaxt = "n", yaxt = "n", main = "Maunga Whau Volcano", col = col, cex.main = 2) dev.off() plot.data <- readBin(plotfile, "raw", n = file.info(plotfile)[, "size"]) new.response(body = plot.data, content.type = "image/png") }, name = "volcano") reg <- new.registry(volcano) server <- startRookAnalysisPageServer(reg, port = port) ``` A four-colored volcano:
If the `colors` parameter is omitted then the defaults from the function definition will be used (in this case red, white and purple):
```{r eval=FALSE} kill.process(server) ``` ------------- # Next [Building Interactive Applications](InteractiveApps.html)