Harfang release 0.2

104 views
Skip to first unread message

Nicolas Juneau

unread,
Apr 20, 2012, 12:20:43 PM4/20/12
to Haxe language and help discussion group
Hello everyone,

I released a couple hours ago version 0.2 of the Harfang Web framework
(http://haxe.org/com/libs/harfang). If a couple of you were using 0.1,
there should be no alarming changes to the structure. Here are the changes:

* There's a new (optional) helper class to accelerate URL mapping
configuration right inside controllers using macros and metadata - make
sure to check the tutorial at
http://haxe.org/com/libs/harfang/tutorials/howto_macro_configurator/0.2 .

* If you implemented your own version of the Controller interface,
"handleRequest" now requires a string parameter. This parameter
indicates the method that is about to be called in the controller class
when a request is done.

* Regarding the URL dispatcher, controllers are now initialized only if
the dispatcher is certain that it can find the mapped method in the
target controller. This change is "under the hood" and shouldn't impact
anything directly if the URL mappings are done correctly.

* A test suite has been built to test the framework's functionality and
look for regressions.

Not familiar with Harfang? It's a (very) small framework to help
developers start PHP Web applications (works for Neko too). In a
nutshell, it provides a structure for modular applications and a URL
dispatcher.

As usual, the library can be installed from haxelib
(http://lib.haxe.org/p/Harfang) and the code can be checked out from
Github (https://github.com/njuneau/Harfang).

Enjoy!

--
Nicolas Juneau

Mihail Ivanchev

unread,
Apr 20, 2012, 1:56:05 PM4/20/12
to haxe...@googlegroups.com
Congratulations Nicolas, nice to see progress on harfang, I really think your way of mapping requests kicks ass, smart way to use macros for sure there! Keep up the good work. I would suggest writing a somewhat larger tutorial for people not so well accustomed to web-development frameworks.

Regards,
Mihail


--
Nicolas Juneau

--
To post to this group haxe...@googlegroups.com
http://groups.google.com/group/haxelang?hl=en

Nicolas Juneau

unread,
Apr 21, 2012, 8:21:39 AM4/21/12
to haxe...@googlegroups.com
Thanks for the kind comments!

If you are looking for a more elaborate tutorial, there's the quick start (http://haxe.org/com/libs/harfang/tutorials/quickstart/0.2) that covers the framework's basic functionality - from creating a project structure to creating controllers. Of course, there's also the API online (http://lib.haxe.org/d/Harfang/0.2/) if you ever want to explore the innards of the framework. I hope that helps!

Cambiata

unread,
Apr 25, 2012, 9:31:41 AM4/25/12
to haxe...@googlegroups.com
Thank you, Nicolas!

Nice and clean!
Some questions:

1. How to implement a basic 404 error handler? Following the 0.2 quick start docs works fine, except for the somwhat unexpected that there is no error thrown at all when there's an unmapped url.
2. The MacroConfigurator example works fine, except for the $prefix example - running "/demo/3bob/abc" isn't mapped to handleRequestC method, and no error thrown.
3. What would be the Harfang way of implementing a basic REST controller? I would like a base controller where a mapped entity (for example "book")

this.addURLMapping(~/book\/$/, LibraryController, "book");

would dispatch to different controller methods depending on the request method, something like this:

class LibraryController extends RestController {   
    public function getBook(id:Int) { }
    public function putBook()...
    public function postBook()...
    public function deleteBook()...
}
 
Regards! / Jonas



Cambiata

unread,
Apr 25, 2012, 9:41:04 AM4/25/12
to haxe...@googlegroups.com
Sorry for not reading carefully! There's an excellent explanation on how to handle exceptions in the 0.2 quick start doc...

Cambiata

unread,
Apr 25, 2012, 9:43:24 AM4/25/12
to haxe...@googlegroups.com
...and now the macro $prefix example works at i should, too! :-) Not sure what I changed though...

Nicolas Juneau

unread,
Apr 25, 2012, 2:32:31 PM4/25/12
to haxe...@googlegroups.com

No problem - the error handling section might not be that obvious on first read! There maybe was a typo when you worked with the MacroConfigurator - it should work fine. However, should you have trouble making it work again, show me the steps for repeating the error and I'll try to see what's the poblem.

 

There is no "Harfang" way of doing your application :) - you are encouraged to work the way you are comfortable with. But here's a suggestion to help you get on the way!

 

From what I see of your code sample in your previous message, the controller seems to act a REST resource (correct me if I am wrong). Here's a suggestion on how to achieve a correct mapping of all your method's classes (I changed RestController to AbstractController, but you can of course have your own implementation of the Controller interface).

 

If you use HTTP methods in your request, you could map a single function, detect the HTTP method in it and route the request to the correct controller method aftwerwards. Here's the example:

 

====================================

#if php

import php.Web;

#elseif neko

import neko.Web;

#else

#error "Unsupported platform"

#end

 

class LibraryController extends AbstractController {

 

/**

* This method receives all requests and detect the HTTP method

*/

public function book(? id : Int) {

// Route the request correctly depending on the HTTP method

switch(Web.getMethod()) {

case "GET":

this.getBook(id);

case "POST":

this.postBook();

case "DELETE":

this.deleteBook(id);

[...]

}

}

public function getBook(id : Int) { }

public function putBook()...

public function postBook()...

public function deleteBook(id : Int)...

}

 

[...]

 

// This would appear in your module.

// Notice the (\d*) part, an optional decimal parameter

// that will be sent to your controller

this.addURLMapping(~/book\/(\d*)$/, LibraryController, "book");

====================================

 

In this example, only the "book" function is mapped - this function takes an optional argument, "id", which would be used in the get and delete functions. You then route the request yourself with the help of the "switch" block.

 

For error detection, you could check the presence of the id parameter when needed (when the method is GET, for example) and throw an HTTP error yourself.

 

This is just one example on how to achieve the mapping - the LibraryController in this example could become a generic RestController so you don't have to re-write the "switch" part again and again.

 

One other way to do it would be to extend or write a new URLMapping class which would be aware of the HTTP method - this could be more complicated and lengthy to implement, but still an alternative.



Le 25 avril 2012 06:43:24 Cambiata a écrit :

...and now the macro $prefix example works at i should, too! :-) Not sure what I changed though...

--

Nicolas Juneau

Cambiata

unread,
Apr 26, 2012, 2:31:43 AM4/26/12
to haxe...@googlegroups.com
Thank you for ideas, Nicolas!

Have you considered adding controller method result objects, something like ufront's ActionResult?
Makes it so easy to DRY, and also makes the controllers more testable...

Here's a very simple version that I knocked together yesterday. Seems to work as expected.

/ Jonas

- - - - -

1. A ActionResult base class and a simple JsonResult implementation:

class ActionResult
{
    public function execute(): String { return ''; }
}

class JsonResult extends ActionResult
{
    private var object:Dynamic;
    public function new(object:Dynamic) {
        this.object = object;
    }
   
    override public function execute():String
    {
        Web.setHeader('content-type', "application/json");       
        return JSON.encode(this.object);
    }   
}

2. A simple rewrite of the URLDispatcher.scanURLs method, line 110.
It checks the controller method for return value.
If it returns an ActionResult, it fires it's execute() method.
If it returns something else, it simply prints it.
If it returns null, it does nothing at all. (This makes it transparent to the current behaviour.)

            if(Reflect.isFunction(controllerMethod)) {
                // Init module
                controller.init(module);
               
                // Handle request
                if(controller.handleRequest(currentMapping.getControllerMethodName())) {
                    var result = Reflect.callMethod(
                            controller,
                            controllerMethod,
                            currentMapping.extractParameters(this.currentURL)
                    );
                   
                    if (Std.is(result, server.ActionResult)) {
                        Lib.println(cast(result, ActionResult).execute());                       
                    } else if (result != null) {
                        Lib.println(result);
                    }
                }
               
            } else {
                // Controller function was not found - error!
                throw new ServerErrorException();
            }

3. The ActionResult can be used in the controllers like this:

class ExampleController extends AbstractController {

    @URL("/json/$")
    public function handleJson() {
        return new JsonResult( { name:'Jane', age:32 } );
    }   

    @URL("/string/$")
    public function handleString() {
        return "This is a simple string returned directly from the controller without the need of printing i manually with Lib.println()...";
    }   
}

Nicolas Juneau

unread,
Apr 26, 2012, 8:47:09 AM4/26/12
to haxe...@googlegroups.com

I thought of returning values from controller methods at some point, but in order to keep the framework as generic as possible, I opted not to require the user to return anything from the controllers (especially since the Controller instance and return value would not be visible to the user).

 

ActionResult is a way of providing return values, and there are others. Some may directly output HTML or text without relying on result objects. Some actions may not output anything at all. Although you cover the use cases in your suggested alteration of the URLDispatcher class, it would make it manage output, which is just not the task of the dispatcher.

 

However, you have a perfectly valid point of wanting uniform outputting across controllers in order to achieve DRY.

 

What you can do right now to have the behavior you seek is having an AbstractController subclass:

 

========================================

 

class MyController extends AbstractController {

private function processResult(ActionResult ar) {

Lib.print(ar.execute());

}

}

 

class ExampleController extends MyController {

    @URL("/json/$")
    public function handleJson() : Void {
        this.processResult( new JsonResult( { name:'Jane', age:32 } ) );
    }   

    @URL("/string/$")
    public function handleString() : Void {

this.processResult( new StringResult("This is a simple string...") );
    }   
}

 

========================================

 

In this example, ActionResult would be an interface instead of a class and two implementations would exist - JsonResult and StringResult. This way, cohesion is maintained in URLDispatcher and uniform output is achieved, without the need to detect result objects with "if" statements.

 

Another way that this behavior could be achieved is by having a "postRequest" event. Similar to handleRequest, postRequest would be a Controller event called after the mapped method. The sequence would be "handleRequest" -> "[mappedMethod]" -> "postRequest". With that kind of event, you could set the result in the mapped method and handle it in postRequest (whether there's a result or not).

 

Right now the framework only provides an onClose event in the ServerConfiguration - it's a bit too far from the controller. Anyway, I'll have to review event handling. I think it can be done a bit better. I'll have to work this out for 0.3.

 

I opened 2 issues to address the problems (https://github.com/njuneau/Harfang/issues/1 and https://github.com/njuneau/Harfang/issues/2).

 

Thanks for the feedback!

Reply all
Reply to author
Forward
0 new messages