Arc includes a web server with several interesting features, including a continuation-based design. The web server includes many different operations to create forms and links with associated continuations.
The Arc distribution comes with sample web applications including a new site (news.arc
) and a blog (blog.arc
).
arc>(serve 8080)
However, it is generally better to start the server in a separate thread, so the Arc REPL can be used. This allows the web server to be modified while it is running.
arc> (thread (serve 8080))A handler can be associated with a URL using the
defop
macro; this defines a handler function, taking the req
parameter:
(defop hello req (prn "Hello world!"))The resulting page can be accessed at http://localhost:8080/hello.
The Arc web server is rather fragile and platform-dependent. If you encounter problems, the easiest solution is to use the unofficial Anarki version, which has multiple patches.
To define a top-level page (http://localhost:8080), use the page name ||
(the MzScheme representation of the empty symbol, between quoting vertical bars).
(defop || req (pr "This is the home page."))
To redirect a page to a different page, the defopr
macro is used, with a function that outputs the name of the target page. For example, to redirect http://localhost:8080/index.html to the earlier "hello" page:
(defopr index.html req (prn "hello"))
The req
parameter receives a table that has the field ip
holding the IP address of the client, and potentially cooks
holding the cookies, and args
holding a list of key value pairs from the URL's query string.
The defop-raw
and defopr-raw
macros let the handler function add HTTP headers. The handler must then output a blank line followed by the HTML content or redirect path. One use of this is to add cookies to the headers.
(defop-raw bar (str req) (w/stdout str (prn "Set-Cookie: mycookie=42") (prn) (prn (req 'ip)) (br) (prn (req 'cooks)) (br) (prn (req 'args))))On the second reload (after the cookie gets assigned), http://localhost:8080/bar?x=1&y=2&z will display:
127.0.0.1 ((mycookie 42)) ((x 1) (y 2) (z ))This illustrates how the handler can access the client's IP address, the cookies, and the URL query parameters. The handle does not have access to other HTTP headers.
This functionality can be used to implement a simple form and form handler; the form is at http://localhost:8080/myform. This example uses some of the HTML functions.
(defop myform req (form "myhandler" (single-input "Enter:" 'foo 10 "Submit"))) (defop myhandler req (prn "You entered") (prbold (alref (req 'args) "foo")))
Several types of output functions are supported by Arc. The simplest is a function that outputs HTML. A function can also optionally output additional HTTP headers. Arc also supports redirect functions that can perform server-side operations and then redirect the browser to a new page; these functions can optionally output additional headers. Finally, Arc has partial support for asynchronous functions, which don't return any response to the request.
The following table shows the macros for generating arbitrary server-side handlers of the given types.
HTML | Headers + HTML | Redirect | Headers + Redirect | |
---|---|---|---|---|
Macro | defop |
defop-raw |
defopr |
defopr-raw |
The continuations used by the web server are explicit function. (The web server does not use ccc
first-class continuations.) The functions take a request object as argument and print the appropriate response. By using closures, the functions can maintain state across requests; the state will be included in the closure when the function is defined, and the function can access the state when it is later invoked.
The web server includes many operations to associate continuation functions and fnids with links and forms. The web server contains operations to generate links and forms of the various types, as shown in the following table.
The previous example can be implemented with continuations as follows:
(defop myform2 req (aform myfunc (single-input "Enter:" 'foo 10 "Submit"))) (def myfunc (req) (prn "You entered") (prbold (alref (req 'args) "foo")))Generally, the continuation function is defined within the original form definition, so a closure can be created. (In this case, the closure is unnecessary, as no state from the first operation is preserved.)
(defop myform2 req (aform [do (prn "You entered") (prbold (alref (_ 'args) "foo"))] (single-input "Enter:" 'foo 10 "Submit")))
Internally, the web server associates a token called the fnid
with each instance of a link or form, and records the continuation function for each token. Periodically, old fnids are deleted.
The following table illustrates the operations to generate links, URLs, or forms, for the four different types of output functions. (In practice, both arform
and arformh
give access to the headers.)
HTML | Headers + HTML | Redirect | Headers + Redirect | |
---|---|---|---|---|
Link generation | w/link, w/link-if, onlink, linkf |
|
w/rlink, rlinkf |
|
URL generation | flink, url-for |
|
rflink |
|
Form generation | aform, timed-aform |
aformh |
arform |
arformh |
Different operations use one of five different mechanisms to specify the continuation function. The function may be specified as an expression, a function of one variable (the request object), a request parameter and body, output stream and request parameters and a body, or a body alone. The documentation should be consulted to see which mechanism goes with which function.
The solution in Arc is as follows:
(defop said req (aform [w/link (pr "you said: " (arg _ "foo")) (pr "click here")] (input "foo") (submit)))The solution may be easier to understand if the continuation functions are made explicit:
(defop said req (aform form-continuation (input "foo") (submit))) (def form-continuation (req) (w/link (pr "you said: " (arg req "foo")) ;link continuation expression (pr "click here")))The
said
operation creates a form that when submitted
executes the form-continuation
function. The continuation function
creates a link that will execute the link continuation expression.
The important thing to notice is the link continuation expression is defined
inside form-continuation
, which results in a closure with the
req
object from form-continuation
. That is,
when the link continuation executes, potentially a long time after
form-continuation
completes, it will still have the state.
This illustrates how closures can be used to carry state from one request
to another.
The form can be executed multiple times, and each execution will have a unique state in the link continuation closure.