options(markdown.HTML.options = "toc")
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 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.
Rook version >= 1.1 and fork are required for the examples in this vignette. I am going to check if they are available.
library(AnalysisPageServer) rookforkOK <- AnalysisPageServer:::checkRookForkForVignettes()
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:
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.
Next we build an AnalysisPageRegistry
. This example only has one
AnalysisPage
, not too interesting, but we still need this object:
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:
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:
identical(get.page(reg, "hello"), hello)
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:
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:
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:
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:
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.
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()
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 }
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.
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()`. wzxhzdk:13 (`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. wzxhzdk:14 ## 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: wzxhzdk:15 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: wzxhzdk:16 ## Example: JSON encoding of complex return value Whatever your function returns will be JSON encoded with `toJSON`. wzxhzdk:17 ## 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. wzxhzdk:18 ## 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. wzxhzdk:19 Or open it in the browser:
wzxhzdk:20 ## 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. wzxhzdk:21 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):
wzxhzdk:22
-------------
# Next
[Building Interactive Applications](InteractiveApps.html)
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.