Proxy

Just want to share a nice trick, maybe you know it already.
It's a misuse of the Proxy Class, but very helpful.
Especially for creating Stubs and Mocks in TDD.

You have an Interface

interface MyInterface
{
    public function getString ():String;
}

Then you need a Resolver like this, it's important to implement Connection:

package;
import haxe.remoting.Connection;

class Resolver implements Connection
{
    var methodToCall:String;
    var registry:Hash<Void -> Dynamic>;
    
    public function new ()
    {
        registry = new Hash();
    }
    
    public function addFunction (method:String, func:Void -> Dynamic) 
    {
        registry.set(method, func);
    }
    
    public function resolve( method: String ) : Connection 
    {
        methodToCall = method;
        return this;
    }
   
    public function call( params : Array<Dynamic> ) : Dynamic 
    {
        var method = registry.get(methodToCall);
        if (method == null) throw "Error, method is not defined"; 
        return method();
    }
}

Add this line:

class MyInterfaceDynamic extends haxe.remoting.Proxy<MyInterface>, implements MyInterface;

declare your Interface-implementations on the fly

var res = new Resolver();

// define your functions
res.addFunction("getString", function () return "a new String"); 

 // create an Implemention whenever you want
var dynamicImpl = new MyInterfaceDynamic(res);

// call your dynamic interface functions
trace( dynamicImpl.getString() ) // returns "a new String"

You're now able to create an Interface Implementation on the fly and you get also auto-completion in your IDE.

I explored this, while creating a Remoting connection that needs authentication.
This is really great stuff, you can implement an AsyncConnection, and handle the function calls in your favoured way.

My Remote Interface:

interface IServerApi:
{
    public function getString (name:String):String;
}

My Async Connection that delegates every call to the secureCall (not really secure ;) ) to check authentication:

package;
import haxe.remoting.AsyncConnection;

class SecureAsyncConnection implements AsyncConnection
{
    var username:String;
    var password:String;
    var methodToCall:String;
    var conn:AsyncConnection;

    public function new (conn:AsyncConnection) 
    {
        this.conn = conn;
    }

    public function resolve( method : String ) : AsyncConnection 
    {
        methodToCall = method;
        return this;
    }

    public function call( params : Array<Dynamic>, ?result : Dynamic -> Void ) : Void
    {          
        params = [username, password, methodToCall, params];
        return conn.secureCall.call(params, result);
    }
    
    public function setErrorHandler( error : Dynamic -> Void ) : Void 
    {
        conn.setErrorHandler(error);
    }
    
    public function setCredentials (username:String, password:String) 
    {
        this.username = username;
        this.password = password;
    }

}

and usage:

declare Proxy:

class MyProxy extends haxe.remoting.AsyncProxy <IServerApi> {}

use it:

var conn = HttpAsyncConnection.urlConnect("http://anyhost/gateway.php");
conn.setErrorHandler(function (e) trace(e));

var sec = new SecureAsyncConnection(conn.api);
var proxy = new MyProxy(sec);

sec.setCredentials("user", "pass");

// you can use it in the same way as before,
// but every call is delegated to the function secureCall, which checks authentication on the server side
proxy.getString("tim", function (r) trace(r)); // hello tim

The ServerSide

class ServerGateway implements IServerApi
{
    
    var authenticated:Bool;

    public function new () 
    {
        authenticated = false;
    }
    
    public function getString (name:String):String 
    {
        checkAuthentication();
        return "hello " + name;
    }
    
    function checkAuthentication () 
    {
        if (!authenticated) {
            throw "Error you are not authorized";
        } else {
            authenticated = false; // reset Authentication
        }
    }
   
    public function secureCall (userName:String, passWord:String, func:String, args:Array<Dynamic>):Dynamic 
    {
        if (userName == "user" && passWord == "pass") {
            authenticated = true;
            return Reflect.callMethod(this, func, args);
        } else {
            throw "Error you're not authorized";
        }
    }

    public static function main ():Void
    {
        var ctx = new haxe.remoting.Context();
        ctx.addObject("api", new ServerGateway());
        if( haxe.remoting.HttpConnection.handleRequest(ctx) ) return;

        trace("this is a remoting server");
    }
}

That's it, maybe someone can create useful stuff with this.

Edited on 24.08.2009 - fixed a few naming mistakes

version #8048, modified 2010-02-06 06:19:41 by hhoelzer