Skip to content
wmeyer edited this page May 22, 2011 · 12 revisions

The Roads Web Application Framework

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

Contents

Getting Started

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.

Installing Roads

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.

Hello World

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.

The Arc Challenge

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:

  1. 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.
  2. href and action 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.

Sharing HTML code between Functions

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

Wolfgang.Meyer@gmx.net