Haxe Framework Tutorial


Introduction

All Apache Thrift tutorials require that you have:

  1. Built and installed the Apache Thrift Compiler and Libraries, see Building from source for more details.
  2. Generated the tutorial.thrift and shared.thrift files as discussed here.

    thrift -r --gen haxe tutorial.thrift
    
  3. Followed all prerequisites listed below.

Prerequisites

Client

For this tutorial, we put both the server and the client main code into one single program. Depending on the arguments passed, it runs as a server or as a client program.

package;

import org.apache.thrift.; import org.apache.thrift.protocol.; import org.apache.thrift.transport.; import org.apache.thrift.server.; import org.apache.thrift.meta_data.*;

import tutorial.; import shared.;

enum Prot { binary; json; }

enum Trns { socket; http; }

class Main {

private static var server : Bool = false;
private static var framed : Bool = false;
private static var buffered : Bool = false;
private static var prot : Prot = binary;
private static var trns : Trns = socket;

private static var targetHost : String = "localhost";
private static var targetPort : Int = 9090;

static function main() {

    #if ! (flash || js || phpwebserver)
    try {
          ParseArgs();
    } catch (e : String) {
        trace(e);
        trace(GetHelp());
        return;
    }

    #elseif  phpwebserver
    //forcing server
    server = true;
    trns = http;
    initPhpWebServer();
    //check method
    if(php.Web.getMethod() != 'POST') {
      Sys.println('http endpoint for thrift test server');
      return;
    }
    #end

    try {
        if (server)
            RunServer();
        else
            RunClient();
    } catch (e : String) {
        trace(e);
    }

    trace("Completed.");
}

#if phpwebserver
private static function initPhpWebServer()
{
    //remap trace to error log
    haxe.Log.trace = function(v:Dynamic, ?infos:haxe.PosInfos)
    {
      // handle trace
      var newValue : Dynamic;
      if (infos != null && infos.customParams!=null) {
        var extra:String = "";
        for( v in infos.customParams )
          extra += "," + v;
        newValue = v + extra;
      }
      else {
        newValue = v;
      }
      var msg = infos != null ? infos.fileName + ':' + infos.lineNumber + ': ' : '';
      Sys.stderr().writeString('${msg}${newValue}\n');
    }
}
#end


#if ! (flash || js)

private static function GetHelp() : String {
    return Sys.executablePath()+"  modus  trnsOption  transport  protocol\n"
    +"Options:\n"
    +"  modus:       client, server   (default: client)\n"
    +"  trnsOption:  framed, buffered (default: none)\n"
    +"  transport:   socket, http     (default: socket)\n"
    +"  protocol:    binary, json     (default: binary)\n"
    +"\n"
    +"All arguments are optional.\n";
}


private static function ParseArgs() : Void {
    var step = 0;
    for (arg in Sys.args()) {

        // server|client
        switch(step) {
        case 0:
            ++step;
            if ( arg == "client")
                server = false;
            else if ( arg == "server")
                server = true;
            else
                throw "First argument must be 'server' or 'client'";

        case 1:
            if ( arg == "framed") {
                framed = true;
            } else if ( arg == "buffered") {
                buffered = true;
            } else if ( arg == "socket") {
                trns = socket;
                ++step;
            } else if ( arg == "http") {
                trns = http;
                ++step;
            } else {
                throw "Unknown transport "+arg;
            }

        case 2:
            if ( arg == "binary") {
                prot = binary;
                ++step;
            } else if ( arg == "json") {
                prot = json;
                ++step;
            } else {
                throw "Unknown protocol "+arg;
            }

        default:
            throw "Unexpected argument "+arg;
        }

        if ( framed && buffered)
        {
            trace("WN: framed supersedes buffered");
        }

    }
}

#end

private static function ClientSetup() : Calculator {
     trace("Client configuration:");

    // endpoint transport
    var transport : TTransport;
    switch(trns)
    {
    case socket:
         trace('- socket transport $targetHost:$targetPort');
        transport = new TSocket( targetHost, targetPort);
    case http:
        var uri = 'http://${targetHost}:${targetPort}';
        trace('- HTTP transport $uri');
        transport = new THttpClient(uri);
    default:
        throw "Unhandled transport";
    }


    // optinal layered transport
    if ( framed) {
        trace("- framed transport");
        transport = new TFramedTransport(transport);
    } else if ( buffered) {
        trace("- buffered transport");
        transport = new TBufferedTransport(transport);
    }


    // protocol
    var protocol : TProtocol;
    switch(prot)
    {
    case binary:
         trace("- binary protocol");
         protocol = new TBinaryProtocol( transport);
    case json:
         trace("- JSON protocol");
         protocol = new TJSONProtocol( transport);
    default:
        throw "Unhandled protocol";
    }


    // put everything together
    transport.open();
    return new CalculatorImpl(protocol,protocol);
}


private static function RunClient() : Void {
    var client = ClientSetup();

    try {
          client.ping();
        trace("ping() successful");
    } catch(error : TException) {
        trace('ping() failed: $error');
    } catch(error : Dynamic) {
        trace('ping() failed: $error');
    }

    try {
        var sum = client.add( 1, 1);
        trace('1+1=$sum');
    } catch(error : TException) {
        trace('add() failed: $error');
    } catch(error : Dynamic) {
        trace('add() failed: $error');
    }


    var work = new tutorial.Work();
    work.op = tutorial.Operation.DIVIDE;
    work.num1 = 1;
    work.num2 = 0;
    try {
        var quotient = client.calculate( 1, work);
        trace('Whoa we can divide by 0! Result = $quotient');
    } catch(error : TException) {
        trace('calculate() failed: $error');
    } catch(error : Dynamic) {
        trace('calculate() failed: $error');
    }

    work.op = tutorial.Operation.SUBTRACT;
    work.num1 = 15;
    work.num2 = 10;
    try {
        var diff = client.calculate( 1, work);
        trace('15-10=$diff');
    } catch(error : TException) {
        trace('calculate() failed: $error');
    } catch(error : Dynamic) {
        trace('calculate() failed: $error');
    }


    try {
        var log : SharedStruct = client.getStruct( 1);
        var logval = log.value;
        trace('Check log: $logval');
    } catch(error : TException) {
        trace('getStruct() failed: $error');
    } catch(error : Dynamic) {
        trace('getStruct() failed: $error');
    }
}


private static function ServerSetup() : TServer {
     trace("Server configuration:");

    // endpoint transport
    var transport : TServerTransport = null;
    switch(trns)
    {
    case socket:
        #if (flash || js)
        throw 'current platform does not support socket servers';
        #else
         trace('- socket transport port $targetPort');
        transport = new TServerSocket( targetPort);
        #end
    case http:
        #if !phpwebserver
          throw "HTTP server not implemented yet";
          //trace("- http transport");
          //transport = new THttpClient( targetHost);
        #else
          trace("- http transport");
          transport = new TWrappingServerTransport(
                  new TStreamTransport(
                    new TFileStream("php://input", Read),
                    new TFileStream("php://output", Append)
                    )
                  );

        #end
    default:
        throw "Unhandled transport";
    }

    // optional: layered transport
    var transfactory : TTransportFactory = null;
    if ( framed) {
        trace("- framed transport");
        transfactory = new TFramedTransportFactory();
    } else if ( buffered) {
        trace("- buffered transport");
        transfactory = new TBufferedTransportFactory();
    }

Were done with the client, but need some more for the server: A service handler implementaion.

Server

As the name suggests, the service handler implements the Thrift service on the server side. The code to achieve this is as follows:

package;

import haxe.ds.IntMap;

import org.apache.thrift.; import org.apache.thrift.protocol.; import org.apache.thrift.transport.; import org.apache.thrift.server.; import org.apache.thrift.meta_data.*;

import tutorial.; import shared.;

class CalculatorHandler implements Calculator {

private var log = new IntMap<SharedStruct>();

public function new() {
}

public function ping() : Void {
    trace("ping()");
}


public function add( num1 : haxe.Int32, num2 : haxe.Int32) : haxe.Int32 {
    trace('add( $num1, $num2)');
    return num1 + num2;
}

public function calculate( logid : haxe.Int32, work : Work) : haxe.Int32  {
    trace('calculate( $logid, '+work.op+","+work.num1+","+work.num2+")");

    var val : haxe.Int32 = 0;
    switch (work.op)
    {
        case Operation.ADD:
            val = work.num1 + work.num2;

        case Operation.SUBTRACT:
            val = work.num1 - work.num2;

        case Operation.MULTIPLY:
            val = work.num1 * work.num2;

        case Operation.DIVIDE:
            if (work.num2 == 0)
            {
                var io = new InvalidOperation();
                io.whatOp = work.op;
                io.why = "Cannot divide by 0";
                throw io;
            }
            val = Std.int( work.num1 / work.num2);

        default:
            var io = new InvalidOperation();
            io.whatOp = work.op;
            io.why = "Unknown operation";
            throw io;
    }

    var entry = new SharedStruct();
    entry.key = logid;
    entry.value = '$val';
    log.set(logid, entry);

    return val;
}

public function getStruct( key : haxe.Int32) : SharedStruct {
    trace('getStruct($key)');
    return log.get(key);
}

// oneway method,  no args
public function zip() : Void {
    trace("zip()");
}

}

Additional Information

Similar to Thrift, Haxe supports different compiler targets. Depending on the target, some features may or may not be supported. For example, if you plan to use Flash or JavaScript targets, there is currently no way to pass command line arguments to the program.