Qore Programming Language

  • Increase font size
  • Default font size
  • Decrease font size

XmlRpcHandler example

E-mail Print PDF
Defines the XmlRpcHandler class for the HttpServer example class.
Processes XML-RPC requests by converting the arguments to Qore data and calling the associated function or call reference and returns the response as an XML-RPC response
# -*- mode: qore; indent-tabs-mode: nil -*-
#! @file XmlRpcHandler.qc XML-RPC handler class definition

# Copyright (C) 2006 - 2010 David Nichols
# to be registered as a handler to the Qore HTTP server

#! XML-RPC Handler implementation; to be registered as a request handler in the HttpServer class
class XmlRpcHandler inherits public AbstractHttpRequestHandler {
    #! version of the implementation
    const Version = "0.3.4";

    #! internal methods (for introspection)
    const InternalMethods = (("function" : "help",
			      "help"     : "shows a list of XML-RPC methods registered with this handler",
			      "text"     : "help",
			      "logopt"   : 2
			      ),
			     ("function" : "listMethods",
			      "help"     : "lists XML-RPC method names registered with this handler",
			      "text"     : "system.listMethods",
			      "logopt"   : 2
			       ));

    private {
        list $.methods = ();
        hash $.mi = hash();
        int $.loglevel;
    }

    #! creates the handler with the given method list
    constructor(AbstractAuthenticator $auth, list $methods) : AbstractHttpRequestHandler($auth) {
	# add internal methods
	foreach my hash $im in (XmlRpcHandler::InternalMethods)
	    $.addMethodInternal($im + ( "internal" : True ));

	foreach my hash $m in ($methods) {
	    if (!exists $m.name)
		throw "XML-RPC-CONSTRUCTOR-ERROR", sprintf("expecting 'name' key in method hash (%n)", $m);
	    if (!exists $m.function)
		throw "XML-RPC-CONSTRUCTOR-ERROR", sprintf("expecting 'function' key in method hash (%n)", $m);

	    if (!exists $m.text)
		throw "XML-RPC-CONSTRUCTOR-ERROR", sprintf("expecting 'text' key in method hash (%n)", $m);
	    delete $m.internal;
	    $.addMethodInternal($m);
	}
    }

    #! adds a method to the handler dynamically
    addMethod(string $name, any $func, string $text, string $help, int $logopt, any $cmark) {
	if (!exists $func)
	    throw "XML-RPC-SERVER-ADD-METHOD-PARAMETER-ERROR", "expecting function name and text name as hash key in argument";

	$.addMethodInternal(( "name"     : $name,
			      "function" : $func,
			      "text"     : $text,
			      "help"     : $help,
			      "logopt"   : $logopt,
			      "cmark"    : $cmark ));
    }

    private addMethodInternal(hash $h) {
	# check for duplicate in method index
	my any $i = $.mi.($h.text);
	if (!exists $i)
	    $i = elements $.methods;

	if (!exists $h.name)
	    $h.name = sprintf("^%s\$", $h.text);
	$.methods[$i] = $h;
    }

    private hash help() {
	my hash $h;
	foreach my hash $m in ($.methods) {
	    $h.($m.text).description = $m.help;
	    if (exists $m.params)
		$h.($m.text).params  = $m.params;
	}
	return $h;
    }

    private list listMethods() {
	my list $l;
	foreach my hash $m in ($.methods)
	    $l += $m.text;
	return $l;
    }

    private log(hash $context, string $str) {
	my string $msg = "XML-RPC ";
	if (exists $context.user)
	    $msg += sprintf("user %s ", $context.user);
	$msg += sprintf("from %s: ", $context.source);
	$msg += vsprintf($str, $argv);

	call_function_args($context.logfunc, $msg);
    }

    private hash callMethod(hash $context, any $params) {
	my string $method = $context.method;
	# find method function
	my hash $found;
	foreach my hash $m in ($.methods) {
	    if (regex($method, $m.name)) {
		$found = $m;
		break;
	    }
	}

	if (!exists $found)
	    throw "XML-RPC-SERVER-UNKNOWN-METHOD", sprintf("unknown method %n", $method);

        # add context marker, if any
        $context.cmark = $found.cmark;
        $context.function = $found.function;

        if (($found.logopt & HttpServer::LP_LEVELMASK) <= $.loglevel && exists $context.logfunc) {
            my string $msg = $method;
            # add arguments to log message
            if ($found.logopt & HttpServer::LP_LOGPARAMS) {
                $msg += sprintf("(");
                my int $i = 0;
                foreach my any $arg in ($params) {
                    if (inlist($i++, $found.maskargs))
                        $msg += "<masked>, ";
                    else if (type($arg) == Type::Hash && elements $arg) {
                        $msg += "(";
                        foreach my string $k in (keys $arg) {
                            if ($k == $found.maskkey)
                                $msg += sprintf("%s=<masked>, ", $k);
                            else
                                $msg += sprintf("%s=%n, ", $k, $arg.$k);
                        }
                        splice $msg, -2, 2;
                        $msg += "), ";
                    }
                    else
                        $msg += sprintf("%n, ", $arg);
                }
                # remove the last two characters from the string if any were added
                if ($i)
                    splice $msg, -2, 2;
                $msg += ")";
            }

            $.log($context, $msg);
        }
        #printf("DEBUG: about to call function '%s' (method=%s params=%n)\n", $found.function, $method, $params);

        if (type($params) == Type::List)
            unshift $params, $context;
        else if (exists $params)
            $params = ($context, $params);
        else
            $params = $context;

        my any $rv;
        if ($found.internal)
            $rv = callObjectMethodArgs($self, $found.function, $params);
        else
            $rv = call_function_args($found.function, $params);

	my hash $h;

        $h.body = makeXMLRPCResponseString($rv);

        return $h;
    }

    #! method called by HttpServer to handle an XML-RPC request
    hash handleRequest(hash $context, hash $hdr, *data $body, reference $close) {
	#printf("xmlrpc handler context=%n hdr=%n body=%n\n", $context, $hdr, $body);

	my hash $xmlrpc;

	if ($hdr.method == "GET") {
	    my string $path = substr($hdr.path, index($hdr.path, "/") + 1);

	    if (!strlen($path))
		return ( "code" : 501,
			 "desc" : "invalid HTTP GET: no path/XML-RPC method name given" );

	    if (index($path, ".") == -1)
		$path = "omq.system." + $path;
	    $xmlrpc.methodName = $path;
	}
	else {
	    if ($hdr.method != "POST")
		return ( "code" : 501,
			 "body" : sprintf("don't know how to handle method %n", $hdr.method) );

	    if ($hdr."content-type" != "text/xml")
		return ( "code" : 501,
			 "body" : sprintf("don't know how to handle content-type %n", $hdr."content-type") );

	    try {
		$xmlrpc = parseXMLRPCCall($body);
	    }
	    catch (hash $ex) {
		$.log($context, "error parsing XML-RPC string: %s", $body);
		return ( "code"   : 200,
			 "errlog" : sprintf("%s: %s", $ex.err, $ex.desc),
			 "hdr"    : ( "Content-Type" : "text/xml" ),
			 "body"   : makeXMLRPCFaultResponseString(0, $ex.err) );
	    }
	}
	try {
	    $context.method = $xmlrpc.methodName;
	    my any $rh = $.callMethod($context, $xmlrpc.params);
	    #printf("method=%s args=%n\nans=%N\n", $xmlrpc.methodName, $xmlrpc.params, $rh);flush();
	    return ( "code" : 200, "hdr" : ( "Content-Type" : "text/xml" ) ) + $rh;
	}
	catch (hash $ex) {
            my string $str = sprintf("%s: %s", $ex.err, $ex.desc);

	    return ( "code"   : 200,
		     "errlog" : $str,
		     "hdr"    : ( "Content-Type" : "text/xml" ),
		     "body"   : makeXMLRPCFaultResponseString($ex.arg, $str) );
	}
    }
}
Last Updated on Saturday, 25 December 2010 19:57