Skip to content

Commit

Permalink
Stop using literate programming
Browse files Browse the repository at this point in the history
Couldn't build from source without knitr installed, and knitr
is not a required dependency
  • Loading branch information
jcheng5 committed May 8, 2014
1 parent 72fa9a2 commit dee6fbc
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 297 deletions.
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,3 @@
/src-x86_64/
shinyapps/
README.html

R/*.GENERATED.R
Rmd/*.html
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ Collate:
'imageutils.R'
'jqueryui.R'
'middleware-shiny.R'
'middleware.GENERATED.R'
'middleware.R'
'priorityqueue.R'
'react.R'
'reactive-domains.GENERATED.R'
'reactive-domains.R'
'reactives.R'
'run-url.R'
'server.R'
Expand Down
15 changes: 0 additions & 15 deletions Makefile

This file was deleted.

159 changes: 96 additions & 63 deletions Rmd/middleware.Rmd → R/middleware.R
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
---
title: middleware.Rmd
output:
html_document:
toc: yes
---

This file contains a general toolkit for routing and combining bits of HTTP-handling logic. It is similar in spirit to Rook (and Rack, and WSGI, and Connect, and...) but adds cascading and routing.

This file is called "middleware" because that's the term used for these bits of logic in these other frameworks. However, our code uses the word "handler" so we'll stick to that for the rest of this document; just know that they're basically the same concept.

## Intro to handlers

A **handler** (or sometimes, **httpHandler**) is a function that takes a `req` parameter--a request object as described in the Rook specification--and returns `NULL`, or an `httpResponse`.

```{r}
# This file contains a general toolkit for routing and combining bits of
# HTTP-handling logic. It is similar in spirit to Rook (and Rack, and WSGI, and
# Connect, and...) but adds cascading and routing.
#
# This file is called "middleware" because that's the term used for these bits
# of logic in these other frameworks. However, our code uses the word "handler"
# so we'll stick to that for the rest of this document; just know that they're
# basically the same concept.
#
# ## Intro to handlers
#
# A **handler** (or sometimes, **httpHandler**) is a function that takes a
# `req` parameter--a request object as described in the Rook specification--and
# returns `NULL`, or an `httpResponse`.
#
## ------------------------------------------------------------------------
httpResponse <- function(status = 200,
content_type = "text/html; charset=UTF-8",
content = "",
Expand All @@ -27,17 +27,31 @@ httpResponse <- function(status = 200,
class(resp) <- 'httpResponse'
return(resp)
}
```

You can think of a web application as being simply an aggregation of these functions, each of which performs one kind of duty. Each handler in turn gets a look at the request and can decide whether it knows how to handle it. If so, it returns an `httpResponse` and processing terminates; if not, it returns `NULL` and the next handler gets to execute. If the final handler returns `NULL`, a 404 response should be returned.

We have a similar construct for websockets: **websocket handlers** or **wsHandlers**. These take a single `ws` argument which is the websocket connection that was just opened, and they can either return `TRUE` if they are handling the connection, and `NULL` to pass responsibility on to the next wsHandler.

### Combining handlers

Since it's so common for httpHandlers to be invoked in this "cascading" fashion, we'll introduce a function that takes zero or more handlers and returns a single handler. And while we're at it, making a directory of static content available is such a common thing to do, we'll allow strings representing paths to be used instead of handlers; any such strings we encounter will be converted into `staticHandler` objects.

```{r eval=FALSE}
#
# You can think of a web application as being simply an aggregation of these
# functions, each of which performs one kind of duty. Each handler in turn gets
# a look at the request and can decide whether it knows how to handle it. If
# so, it returns an `httpResponse` and processing terminates; if not, it
# returns `NULL` and the next handler gets to execute. If the final handler
# returns `NULL`, a 404 response should be returned.
#
# We have a similar construct for websockets: **websocket handlers** or
# **wsHandlers**. These take a single `ws` argument which is the websocket
# connection that was just opened, and they can either return `TRUE` if they
# are handling the connection, and `NULL` to pass responsibility on to the next
# wsHandler.
#
# ### Combining handlers
#
# Since it's so common for httpHandlers to be invoked in this "cascading"
# fashion, we'll introduce a function that takes zero or more handlers and
# returns a single handler. And while we're at it, making a directory of static
# content available is such a common thing to do, we'll allow strings
# representing paths to be used instead of handlers; any such strings we
# encounter will be converted into `staticHandler` objects.
#
## ------------------------------------------------------------------------
joinHandlers <- function(handlers) {
# Zero handlers; return a null handler
if (length(handlers) == 0)
Expand Down Expand Up @@ -71,19 +85,30 @@ joinHandlers <- function(handlers) {
return(NULL)
}
}
```

Note that we don't have an equivalent of `joinHandlers` for wsHandlers. It's easy to imagine it, we just haven't needed one.

### Handler routing

Handlers do not have a built-in notion of routing. Conceptually, given a list of handlers, all the handlers are peers and they all get to see every request (well, up until the point that a handler returns a response).

You could implement routing in each handler by checking the request's `PATH_INFO` field, but since it's such a common need, let's make it simple by introducing a `routeHandler` function. This is a handler [decorator](http://en.wikipedia.org/wiki/Decorator_pattern) and it's responsible for 1) filtering out requests that don't match the given route, and 2) temporarily modifying the request object to take the matched part of the route off of the `PATH_INFO` (and add it to the end of `SCRIPT_NAME`). This way, the handler doesn't need to figure out about what part of its URL path has already been matched via routing.

(BTW, it's safe for `routeHandler` calls to nest.)

```{r eval=FALSE}
#
# Note that we don't have an equivalent of `joinHandlers` for wsHandlers. It's
# easy to imagine it, we just haven't needed one.
#
# ### Handler routing
#
# Handlers do not have a built-in notion of routing. Conceptually, given a list
# of handlers, all the handlers are peers and they all get to see every request
# (well, up until the point that a handler returns a response).
#
# You could implement routing in each handler by checking the request's
# `PATH_INFO` field, but since it's such a common need, let's make it simple by
# introducing a `routeHandler` function. This is a handler
# [decorator](http://en.wikipedia.org/wiki/Decorator_pattern) and it's
# responsible for 1) filtering out requests that don't match the given route,
# and 2) temporarily modifying the request object to take the matched part of
# the route off of the `PATH_INFO` (and add it to the end of `SCRIPT_NAME`).
# This way, the handler doesn't need to figure out about what part of its URL
# path has already been matched via routing.
#
# (BTW, it's safe for `routeHandler` calls to nest.)
#
## ------------------------------------------------------------------------
routeHandler <- function(prefix, handler) {
force(prefix)
force(handler)
Expand Down Expand Up @@ -113,11 +138,12 @@ routeHandler <- function(prefix, handler) {
}
}
}
```

We have a version for websocket handlers as well. Pity about the copy/paste job.

```{r eval=FALSE}
#
# We have a version for websocket handlers as well. Pity about the copy/paste
# job.
#
## ------------------------------------------------------------------------
routeWSHandler <- function(prefix, wshandler) {
force(prefix)
force(wshandler)
Expand Down Expand Up @@ -148,15 +174,17 @@ routeWSHandler <- function(prefix, wshandler) {
}
}
}
```

### Handler implementations

Now let's actually write some handlers. Note that these functions aren't *themselves* handlers, you call them and they *return* a handler. Handler factory functions, if you will.

Here's one that serves up static assets from a directory.

```{r eval=FALSE}
#
# ### Handler implementations
#
# Now let's actually write some handlers. Note that these functions aren't
# *themselves* handlers, you call them and they *return* a handler. Handler
# factory functions, if you will.
#
# Here's one that serves up static assets from a directory.
#
## ------------------------------------------------------------------------
staticHandler <- function(root) {
force(root)
return(function(req) {
Expand All @@ -181,15 +209,19 @@ staticHandler <- function(root) {
return(httpResponse(200, content.type, response.content))
})
}
```

## Handler manager

The handler manager gives you a place to register handlers (of both http and websocket varieties) and provides an httpuv-compatible set of callbacks for invoking them.

Create one of these, make zero or more calls to `addHandler` and `addWSHandler` methods (order matters--first one wins!), and then pass the return value of `createHttpuvApp` to httpuv's `startServer` function.

```{r eval=FALSE}
#
# ## Handler manager
#
# The handler manager gives you a place to register handlers (of both http and
# websocket varieties) and provides an httpuv-compatible set of callbacks for
# invoking them.
#
# Create one of these, make zero or more calls to `addHandler` and
# `addWSHandler` methods (order matters--first one wins!), and then pass the
# return value of `createHttpuvApp` to httpuv's `startServer` function.
#
## ------------------------------------------------------------------------
HandlerList <- setRefClass("HandlerList",
fields = list(
handlers = "list"
Expand All @@ -199,7 +231,7 @@ HandlerList <- setRefClass("HandlerList",
if (!is.null(handlers[[key]]))
stop("Key ", key, " already in use")
newList <- structure(names=key, list(handler))

if (length(handlers) == 0)
handlers <<- newList
else if (tail)
Expand Down Expand Up @@ -309,8 +341,9 @@ HandlerManager <- setRefClass("HandlerManager",
}
)
)
```

## Next steps

See server.R and middleware-shiny.R to see actual implementation and usage of handlers in the context of Shiny.
#
# ## Next steps
#
# See server.R and middleware-shiny.R to see actual implementation and usage of
# handlers in the context of Shiny.
Loading

0 comments on commit dee6fbc

Please sign in to comment.