Home
Welcome to the official Roads documentation. Roads is a web application framework for Mozart/Oz.
Some highlights:
- easy to get started
- (anonymous) functions as form actions and link targets
- composable form parts (like “formlets”)
- effective measures against common security threats
- Getting Started
- The Arc Challenge
- The Session Object
- Security
- Application Development
- Other Features
- Future Development
We expect the reader to be familiar with basic HTML and the fundamentals of the Oz programming language.
Roads was developed using Mozart 1.4.0. But it was also tested successfully with Mozart 1.3.2 on Debian stable.
Clone Roads:
git clone "git://github.com/wmeyer/roads.git"
or download and unpack it:
wget "http://cloud.github.com/downloads/wmeyer/roads/roads-full-0.2.0.zip" unzip "roads-full-0.2.0.zip"
Build it (with g++ installed; on Windows: with Cygwin1 and g++ installed):
cd roads ./buildAll.sh
In addition to Roads itself, this installs a number of helpful Oz libraries and the Sawhorse webserver. Sawhorse is basically a port of the Haskell Web Server to Oz, extended with a plugin system. Sawhorse is currently the only web server supported by Roads.
Lets try a “Hello, world”-application by starting the Emacs-based Mozart OPI and copy-pasting the following code (/roads/examples/Hello.oz
):
declare
[Roads] = {Module.link ['x-ozlib://wmeyer/roads/Roads.ozf']} %% link to Roads
fun {HelloWorld Session} %% 'Session': unused parameter
html(head(title("Hello"))
body(p("Hello, world!"))
)
end
in
{Roads.registerFunction hello HelloWorld}
{Roads.run}
Execute the code in Emacs (menu Oz→Feed Buffer) and navigate to http://localhost:8080/hello in your web browser. You can stop the application (and the web server) by feeding the line {Roads.shutDown}
or by halting Oz with C-. h
.
In case of problems, take a look at the *Oz Emulator*
buffer, which by default receives Sawhorse and Roads log messages.
This simple example just returns a record value which represents a minimalistic, static HTML page. Let’s continue with a more interesting example.
In Februar 2008, Paul Graham proposed The Arc Challenge:
Write a program that causes the url
said
(e.g.http://localhost:port/said
) to produce a page with an input field and a submit button. When the submit button is pressed, that should produce a second page with a single link saying “click here.” When that is clicked it should lead to a third page that says “you said: …” where … is whatever the user typed in the original input field. The third page must only show what the user actually typed. I.e. the value entered in the input field must not be passed in the url, or it would be possible to change the behavior of the final page by editing the url.
A possible solution to this challenge in Roads looks like this (/roads/examples/SaidSimple.oz
):
declare
[Roads] = {Module.link ['x-ozlib://wmeyer/roads/Roads.ozf']}
fun {Said Session}
Foo %% declares a local variable "Foo"
in
html(
body(
form(input(type:text bind:Foo)
input(type:submit)
method:post
action:fun {$ _} %% anonymous function with one unused argument
p(a("click here"
href:fun {$ _}
p("you said: " # Foo) %% '#': string concatenation
end
))
end
)))
end
in
{Roads.registerFunction said Said}
{Roads.run}
Most of the code in the Said
function is just standard HTML, encoded as an Oz record. There are however two Roads-specific extensions in this example:
- Input tags can have an additional
bind
attribute. It takes either a variable which will receive the submitted value (like in this example) or a unary procedure which will be called with the input value. -
href
andaction
attributes can take function values instead of URLs. These are automatically converted to unique URLs. When these URLs are requested, the framework will make sure that the right function is called in the right context.
The generated HTML for the outer function looks like this:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<body>
<form action="/said/29T8MW" method="post" >
<input name="roadsFormBinding0" type="text" >
<input type="submit" >
<input name="roadsSecret" type="hidden" value="~589607788" >
</form>
</body>
</html>
By using anonymous functions and exploiting lexical scoping, we are able to access the Foo
variable and embed the user-entered text into the output of the innermost function. (A more conventional method to access parameters is also available, see next chapter).
What happens if we submit multiple times, e.g. by using the Back button? Foo
will be bound to a different value. This would normally cause an exception, because Oz variables are immutable logic variables (similar to single assignment variables). To avoid this situation, Roads uses computation spaces. Every nested function is executed within a subordinate computation space, and variable bindings are only visible in that space. We will discuss this in more detail in chapter Application Development.
The bottom line is that the function will behave as expected. Every invocation of the innermost nested function will have its own, independent value for Foo
. Consequent or concurrent submission will never accidentally interact with each other.
The bind
attribute is not merely a matter of convenience. It is also essential to make fragments of HTML forms composable, as discussed in chapter Other Features.
There is a small problem with this example, though. The inner functions do not return valid HTML documents but only fragments. We could fix this by extending these functions with html(head(title(...)) body(...))
. However, there is a better way to share code between multiple functions.
To use this method, we encapsulate the Said
function in a functor (an Oz module). A functor can export multiple functions which are mapped to URLs according to the name which is used to export them. Additionally, a Roads functor can have Before
and After
functions which are called before and after every regular function. In this example we use the After
function to embed the HTML fragments of the Said
function and the two nested functions into an HTML document (/roads/examples/Said.oz
):
declare
[Roads] = {Module.link ['x-ozlib://wmeyer/roads/Roads.ozf']}
functor Pages
export
said:Said %% export as 'said'
After
define
fun {Said Session}
Foo
in
form(input(type:text bind:Foo)
input(type:submit)
method:post
action:fun {$ _}
p(a("click here"
href:fun {$ _}
p("you said: " # Foo)
end
))
end
)
end
fun {After Session Doc}
html(head(title("Said"))
body(Doc)
)
end
end
in
{Roads.registerFunctor '' Pages} %% map functor to the empty path
{Roads.run}
Note that we use registerFunctor
instead of registerFunction
.
We are registering the functor using the empty atom ''
. The resulting URL is still http://localhost:8080/said because Said
is exported using the atom said
.
Instead of the literal functor value, we could also have specified the path to a compiled functor.
You might have noticed that all HTML-generating functions take one argument: the session object. We did not use it so far, but in the next section you will see why it is useful.
Next: The Session Object
1 In order to compile successfully, you need to use the Cygwin Legacy version In Cygwin 1.7. the g++ option -mno-cygwin
(which is required by ozmake) does not seem to work anymore.
Also, you have to replace the link to g++ with the actual file to make ozmake (the Oz build tool) work:
rm /usr/bin/g++.exe cp /etc/alternatives/g++ /usr/bin/g++.exe