Getting started with ufront

About ufront


Ufront is a web framework for haxe, it is a good starting point for people wishing to make web applications using either PHP or Neko. The ufront library follows the "Model, View, Controller" (MVC) pattern of programming, making it easy to separate different sections of your code and layout.

It has been developed by Franco Ponticelli. See also:

Getting Ready

Assuming that you have a fresh Haxe installation on your system, you start by installing Ufront using Haxelib: (You may have to run this as Administrator, using "sudo" on Linux.)

haxelib install ufront

This should install the following libraries (current version) into {Motion-Twin path}/haxe/lib:
  • thx (0,2,0)
  • hxevents (0,3,3)
  • hscript (1,6)
  • erazor (0,1,1)
  • ufront (0,1,2)

Creating a FlashDevelop 4 PHP target project


Create a new Haxe PHP Project and give it a suitable name and location. (Here we use TestProject as project name, and we save it in C:\haxe\project\TestProject.)

When creating your project, two subfolders are created:

  • /bin, where the compiled php files will be exported (for example to C:\haxe\projects\TestProject\bin)
  • /src, where your haxe files will reside. Right now there should be a Main.hx file and nothing more.

Including required libraries


For smooth and simple compilation, we will include the needed libraries in our project the following way:

1. Go to Project>Properties>Compiler Options
2. Open up General>Libraries and open up the String Collection Editor.
3. Add the following lines:

thx
hxevents
ufront
erazor
hscript

Setting up the localhost webserver


For things to run smoothly, you should also set up a virtual host for your local web server, pointing at the bin directory. This can be done in several ways. If you are using xampp/apache (or equivalent), you can add something like the following to your httpd-vhosts.conf:
<VirtualHost *:80>
    ServerName testproject
    DocumentRoot "C:/haxe/projects/TestProject/bin"
     <Directory "C:/haxe/projects/TestProject/bin" >
         AllowOverride All
         Allow from All
     </Directory>    
</VirtualHost>

You will also need to edit your local dns hosts file (on windows for example C:\WINDOWS\system32\drivers\etc\hosts) by adding

127.0.0.1       testproject

When set up and apache restarted, browsing to http://testproject should point the browser to the C:\xampp\htdocs\TestProject\bin foler. Please note that this folder is empty, as we havent compiled anything yet!

Make FlashDevelop run testproject after compiling


We should also prepare our project to run our webbrowser correctly after setting up the webserver:
  • set you Project>Properties>Output Test Project to Open Ducument
  • press Edit... button and set your Command to http://testproject
    This makse FlashDevelop open the browser on our project address after each new compilation.

Test the localhost webserver setup


We can test our project and localhost setup by tracing a simple message in the browser.
Open the src/Main.hx file and add a simple trace message, something like this:

src/Main.hx

import php.Lib;
class Main 
{
    static function main() 
    {
        trace('Hello world!');
    }
}

Press F5 to test run the project. It should compile the source files to the necessary files and classes in the bin folder (bin/index.php and bin/lib/* with lots of php class files).
Your browser should start at the address http://testproject and you shold se your message, something like this:
Main.hx:6: Hello world! 

Setting up a minimalistic Ufront application


In it's most basic form, just to make our application able to respond to anything, we need to do the following:
1. Kick off our application by create and execute a MvcApplication instance with some basic configuration.
2. Create a HomeController that will take care of our web request, in this case by printing out a greeting message.

We start by adding the simplistic controller. Create a src/controller folder, and add the class file HomeController.hx to that folder. We make the HomeController extend Controller, and we add one single public method, index():

src/controller/HomeController.hx

package controller;
import ufront.web.mvc.Controller;

class HomeController extends Controller
{
    public function index() 
    {
        return "Hello from HomeController.index()";
    }    
}

Then we will change the Main.hx to the following, where we create an instance of MvcApplication:

src/Main.hx

 
import php.Lib;
import thx.util.Imports;
import ufront.web.AppConfiguration;
import ufront.web.mvc.MvcApplication;

class Main 
{
    static function main() 
    {
        Imports.pack("controller");
        new MvcApplication(new AppConfiguration("controller")).execute();
    }
}

The Imports.pack() directive is a part of the thx library, and gives us a handy way to import all classes in the selected package at once (here "controller" package). No real benefit right now - we could easily add an import controller.HomeController statement - but very convenient when the controller list gets longer.

Press F5 to testrun the application. The browser should open up as before, but this time you should see the greeting message from the home controller.

So, what happens here?

First of all, we create MvcApplication instance, and we kick it off with the .execute() method. That's what gets the application going. But we also feed the MvcApplication with some configuration information. In this case we are giving it an AppConfiguration instance that happens to know the name of the package where our controllers live - AppConfiguration("controller"). This way our application knows where to look for controllers.
(More on application configuration below.)

Convention over Configuration

The MvcApplication is created according to a convention over configuration pattern, wich means that it makes some assumptions that saves us some boilerplate coding. One assumption is that our application root url http://testproject should run the HomeController.index() method - unless we tell our app something else. If you try other url's, for example http://testproject/x, you will get an 404 error, because we haven't yet set up any rules (routes and controllers) for these.

Some refactoring

We started our application above with a simple "oneliner". Let's rewrite it to give us both configuration and application objects as variables. In this way we get a bit more prepared for what's coming next:

Inside of src/Main.hx

    
static function main() 
{
    Imports.pack("controller");
    var config = new AppConfiguration("controller");
    var application = new MvcApplication(config);
    application.execute();
}

Simple debugging with TraceModule

We started our project with some tracing from the Main.hx bootstrap. No problem there, the message is displayed in the browser as expected. But have you tried to trace a message from your controller? If you do so, you won't see it in the browser. So where does it go?

Ufront is fetaured with the a plugin solution that makes it possible to extend the basic functionality using modules. Two modules are added by default:

The TraceModule replaces the native haxe.Log.trace function, collects the messages and sends them back to the browser after the headers have been sent. There they can be seen in the Firebug console (or equivalent).

The ErrorModule automatically takes care of error handling and displays the pretty error pages that you might have seen. (More on this later.)

So, look out for your controller-traced messages in the Firebug console! You don't need to worry about traced messages interfering with your well-styled page layout anymore!

Basic routing

To handle what url's a visitor is allowed to use, we create routes. In our basic example above there's only one url allowed. This is also a convention: No route defined = one default route allowed (the base url)!

We can explicitly define the base root (that we already have) by adding it to a RouteCollection instance:

    var routes = new RouteCollection();
    route.addRoute("/",    { controller : "home", action : "index" } );

Above, we define our root url with two parameters:
  • the first parameter "/" tells us that we are dealing with the root url (in our case http:/testproject or http:/testproject/)
  • the second parameter is an object with two fields: controller and action. The controller is set to "home" wich will invoke our HomeController class. (Here's another convention: The Home controller can be named Home or HomeController, both alternatives work!). And the action is set to "index" wich will (you guessed it!) call our .index() method.

Our complete Main class, with the default route explicitly added, now looks like the following.

src/Main.hx

import php.Lib;
import thx.util.Imports;
import ufront.web.AppConfiguration;
import ufront.web.mvc.MvcApplication;
import ufront.web.routing.RouteCollection;
class Main 
{
    static function main() 
    {
        Imports.pack("controller");
        var config = new AppConfiguration("controller");
        
        var routes = new RouteCollection();
        routes.addRoute("/",    { controller : "home", action : "index" } );
        
        var application = new MvcApplication(config, routes);
        application.execute();
    }
}

(Make sure that you add routes as the second parameter to the new MvcApplication(config, routes)!;

Please note that we haven't added any functionality yet! Our app still does exactly the same thing as our first working example above, only in a more code-verbose way...

So, let's add some routes!

Change the route parte of the Main.hx to the following:

var routes = new RouteCollection()
    .addRoute("/",        { controller : "home", action : "index" } )
    .addRoute("/info",    { controller : "home", action : "info" } )
;    

As you can see, we have added the possibility to use the /info url. Using that url will invoke the HomeController as before, but the action method is set to info. Let's create that one:

/src/controller/HomeController.hx

package controller;
import ufront.web.mvc.Controller;
class HomeController extends Controller
{
    public function index() 
    {
        return "Hello from HomeController.index()";
    }
    
    public function info()
    {
        return "Here's some information presented by HomeController.info()";
    }    
}

After compiling, you should be able to try this route on the url http://testproject/index.php/info.

Pretty urls with .htaccess

To get rid of the "index.php" part of the url http://testproject/index.php/info, so that we can use the "pretty url" http://testproject/info instead, we have to set up a .htaccess file. On a standard Apache server installation, with rewrite module enabled, you can add something like the following in a .htaccess file:

bin/.htaccess

RewriteEngine On
RewriteBase /

# this gets rid of index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1

Passing parameters to controllers


So, what if we want to pass a parameter, let's say a item id, to our controller?
We add a new route for that, right below the ones already defined:

Inside src/Main.hx

.addRoute("/item/{id}",    { controller : "home", action : "item" } )    

We define parameters inside curly braces, and as you can see above the /item url is followed by an id parameter. This route expects an HomeController.item() action, so we've better add it:

src/controller/HomeController.hx

    // add the following method to HomeController
    public function item(id=0)
    {
        return "You passed " + id + " as id value";
    }

As you can see above, the route parameter /item/id is corresponded by the method parameter item(id = 0). This means that any id number that we add to our url (for example /item/12345) will be passed to our HomeController.item method, with its id parameter loaded as 12345.

Try it out by compiling and adding some test values as id parameter:
http://testproject/index.php/item/333 will output the message "You passed 333 as id value" in the browser.

Please note that the value is silently casted to the type specified in the controller method (in this case an Int) - without generating any runtime errors. That means that the url http://testproject/index.php/item/xyz gives us 0 as id value.

Also note that the id in this case is mandatory. Using the /item url without any id will cause a 404 error.

Optional parameters

To make it possible to use optional parameters, we add a questionmark prefix to the id parameter:

.addRoute("/item/{?id}", { controller : "home", action : "item" } )        

This way we can use the /item url without parameter, getting the default value set by the method parameter definition (in our case id=0).

Incoming data - HttpRequest

Data can be passed into the controller in several ways: url query strings, http post or put, and http headers among others. This data is neatly packed and accessable in the Ufront controllers via the controllerContext.request object. Lets say that we want to handle some query string data coming from the following url: http:/testproject/index.php?search=abc&dir=asc. This can be done using the .queryString property of the controllerContext.request object, in the following way:

scr/controller/HomeController.hx

public function index() 
{
    return "Your queryString: " 
        + this.controllerContext.request.queryString;        
}

As you can see, this gives us the query string, in its raw form, as written in the url. We can also get it neatly split into a hash using the .query property:

public function index() 
{
    var qs:Hash<String> = this.controllerContext.request.query;
    trace(qs);     // Trace the query hash to the Firebug browser console
    return "You are searching for : " + qs.get('search') 
        + ", direction: " + qs.get('dir');        
}

We can acces http post data (coming from a form post, an ajax or rest request) in the same way, using two corresponding post methods:
.post (returns a Hash<String>)
.postString (returns a String)

Apart from query and post data, the HttpRequest object serves us usable information about headers, http method etc. There's even a clientIP property!

Outgoing data - ActionResult decendants

So far, we've returned plain strings from our controller action methods. Example:

class FooController extends Controller
{
    public function bar() 
    {
        return "Simple string return from FooController.bar()";
    }
}

In a real application we need to output more complex content than simple strings:
  • For a web page, we want to output a page template that we can load with dynamic content data
  • For a web service we want to deliver data in json, xml or other formats - along with the correct mime-type headers etc.
  • for a file service we want to deliver the file content mime-type and filesize headers etc.

To simplify the process of composing more complex outgoing data, Ufront gives us the ActionResult class, or more specifically decendants of ActionResult:

  • ViewResult takes care of automatically grabbing the right html template, and populate it with dynamic data using the Erazor templating engine
  • JsonResult takes care of serializing an outgoing data object to json, and adding the correct json mime-type header
  • TODO - Add more ActionResult decendants

JsonResult example

Let's try a simple JsonResult example. We can modify our simple HomeController.index() action like this:

src/HomeController.hx

    public function index() 
    {
        var data = { name:'Diana', age:'30' };
        return new JsonResult(data);
    }

When you testrun (http://testproject/index.php) you'll notice that the json data isn't simply printed out in the browser main window, but served as an downloadable file. That's because the JsonResult takes care of setting the right header content json mim-type for us.

Simple templating using ViewResult and Erazor template engine


The ViewResult class takes care of presenting html data. At the most basic level, it does so by grabbing the right a html template file based on the current controller and action name.

Template path and file naming convention

If we we want to use a controller named Foo (or FooController) with an action method called .bar() using the ViewResult templating system, we will have to create a html template file called bin/view/foo/bar.html.

Please note that the html template files are put into the bin/view/ folder, not in the src folder.

Here are some examples

  • controller Foo, action method: .bar() - view template: bin/view/foo/bar.html
  • controller FooController, action method: .bar() - view template: bin/view/foo/bar.html
  • controller Site, action method: .menu() - view template: bin/view/site/menu.html
  • controller Home, action method: .index() - view template: bin/view/home/index.html

Please not that it doesn't matter if the controllers are named "Foo" or "FooController", the ViewResult still looks for the html template files in the bin/view/foo directory.

Let's try an example:

Create src/FooController.hx and add a .bar() action method that returns a ViewResult instance:

class FooController extends Controller
{
    public function bar() 
    {
        return new ViewResult();
    }
}

Then create the corresponding html template file. Just a simple html file, something like this:

bin/view/foo/bar.html

<!DOCTYPE html>
<head>
    <title></title>
</head>
<body>
    <h1>Hello from FooController.bar() template!</h1>
</body>
</html>

TODO: The following shouldn't be needed due to the default {controller}/{action}/{?id} routing solution:
We will also need to define a rout to make this url accessible:

Main.hx

    // added to the RouteCollection
    .addRoute("/foo/bar",    { controller : "foo", action : "bar" } )    

Compile using F5 and try the new webpage out on the http://testproject/index.php/foo/bar url. You should see the simple "Hello from FooController.bar()" message displayed with the heading size defined in your template. Nothing fancy yet.

Now let's add some dynamic data, to make things a little more interesting:

We start by injecting some data in our ViewResult object. Here we add a simple hash object with a name and an age field:

src/controller/FooController.hx

public function bar() 
{
    var user = { name:'Diana', age:30 };
    return new ViewResult(user);
}

Then we modify our template to make use of that dynamic data. Note that we prefix the data variables with @ sign:

bin/view/foo/bar.html

<!DOCTYPE html>
<head>
    <title>User: @name</title>
</head>
<body>
    <h1>Hello, @name, @age!</h1>
</body>
</html>

Now your web page as well as the the page title (browser tab) should display the name "Diana".

List iterators

The templating engine can handle more complex structured data. Let's add a array of hobbies to our user data:
src/controller/FooController.hx

var user = { name:'Diana', age:30, hobbies:['knitting', 'fishing', 'programming'] };

Then we add a for loop in our html template to take care of this by displaying an unordered list (ul):

...
    <h1>Hello @name, @age!</h1>
    <p>Hobbies:</p>
    <ul>
        @for(hobby in hobbies)
        {
            <li>@hobby</li>
        }
    </ul>
...

If conditionals

We can use conditionals to customize the display depending on the incoming data:

...
    <h1>Hello @name, @age!</h1>
    @if(age < 18) 
    {
        <p>You are under 18! Some restrictions in life, you know...</p> 
    }
    else
    {
        <p>Age verification ok</p>
    }
...

Basic Authentication

Ufront comes loaded with authorization solutions for session and memory storage mechanisms.
Here we will have a look at how to set up a session storage authorization.

Please note that the following is nothing but a brief explanation of how the authentication solution basically works. This means that there's no real web-application authentication example to find here yet

We begin with creating a new SessionStorage instance. This one takes one argument, a FileSession that should point to a server directory where we store the file sessions:

var sessionStorage = new SessionStorage(FileSession.create('path-to-session-directory'));

For testing purposes we will create a directory in the /bin folder called /bin/sessions.
When we have our session storage set up, we can create an instance of the Auth class:

var sessionStorage = new SessionStorage(FileSession.create('sessions'));
var auth = new Auth(sessionStorage);

The Digest class - filebased md5 hash authentication


Our Auth instance has to check the user identity against som kind of list or table. Here we will use
the Digest class that implements the IAuthAdapter interface, and therefore has the needed authenticate() method. The Digest class uses a simple text file with md5 hashed values to identify the user.

It uses a combination of the following values:

  • username
  • realm (an extra word/string that can be used to increase security)
  • password

and uses these values separated by colon sign, according to the following pattern: username:realm:password. We can use something like the following function to create our hash value for the user Bob with password pazz in the testrealm realm:

trace(haxe.Md5.encode('Bob' + ':' + 'testrealm' + ':' + 'pazz'));

// will output the following hash:
// d19d169d44ddf4440e13d830ddaff634 

The Digest class takes four constructor parameters: the first one is the path to a textfile that holds the hashed information, then the realm, the username and the password. In the following example the Digest class will look for the user information in a file called auth/digest.txt (wich means that the compiled program will look for it in bin/auth/digest.txt):

var digest = new Digest('auth/digest.txt', 'testrealm', 'Bob', 'pazz');

The content of the auth/digest.txt file should be created like this:

  • Each line represents one user
  • The line should start with username:realm (username + ":" + realm)
  • From character column 32 and onwards, there should be a md5 hashed value of username:realm:password

This means that the content of auth/digest.txt could be something like the following, with username:realm to the very left of each row, and the md5 hash from character column 32 and onwards:

username:realm                 md5 hash of username:realm:password
===================            ==========================================
Bob:testrealm                  d19d169d44ddf4440e13d830ddaff634
Angela:testrealm               2d7e061bbac27357a47fae9c225b1d73
Fooboy:testrealm               ac27357a47fae9c225b1d737e061bbac

Now we have what we need to start to authenticate our users. Here's a full example that sets up the Auth class with SessionStorage, and performs an Digest authentication against the digest file in the example above:

var sessionStorage = new SessionStorage(FileSession.create('sessions'));
var auth = new Auth(sessionStorage);
var digest = new Digest('auth/digest.txt', 'testrealm', 'Bob', 'pazz');
auth.authenticate(digest);        
var identity = auth.getIdentity();
trace(identity);

When run, the Digest authenticate() method will look for the hash for username Bob with password pazz in the testrealm, and as it finds him it will return the following identity typedef instance:
{ realm : "testrealm", username : "Bob" } 

As the user is authenticated and the authentication information is stored in a session, we can later on run the following:

var sessionStorage = new SessionStorage(FileSession.create('sessions'));
var auth = new Auth(sessionStorage);
var identity = auth.getIdentity();
trace(identity);

It will return username Bob as long as the session is alive (default set in FileSession to ten days).
If we want to log out our user, we can do so with the clearIdentity() method:
var sessionStorage = new SessionStorage(FileSession.create('sessions'));
var auth = new Auth(sessionStorage);
auth.clearIdentity();

(To be continued...)

version #15451, modified 2012-09-12 10:26:45 by jason