Providing XML-RPC Services

[ Start > PikeDevel > HowTo > Providing XML-RPC Services ] [ Edit this Page | Show Page Versions | Show Raw Source ]


Often, while writing applications, it's desirable to make functionality available to external clients. Numerous options exist, however http://www.xmlrpc.org is a relatively popular, cross-platform method for calling remote functionality. Pike includes a ready-to-go XML-RPC client, however, a little more work is required to provide XML-RPC services to clients. It's something I've needed often enough that I've developed some standard code that I always employ and it's worked quite well for me.

XML-RPC consists of procedure calls and responses encoded in a standard XML format. These messages are sent over standard HTTP connections, which is convenient, as we can use a lot of what we've learned from our earlier Simple Web Server experiments. We'll extend that embedded web server to make methods in an object available via XML-RPC.

Getting Started

We like to get up and running quickly, so let's reuse some existing code. We'll take the code from the Simple Web Server article, and extend it by overriding handle_request(). The replacement function simply calls the parse() function, and traps any errors. By doing this, we can make sure that the remote client always receives a response, even if it's not proper XML-RPC:

void handle_request(Protocols.HTTP.Server.Request request) { mapping response = ([]);

if(catch(response = parse(request))) { response = (["error": 500, "type": "text/html", "data": "an unknown error has occurred."]); }

request->response_and_finish(response); }

Handling XML-RPC

Once we're guaranteed to respond to a request with something, we can turn our attention to decoding the request, calling the desired method and returning a response. Conveniently, Pike includes methods for decoding and encoding calls and fault messages. These messages are contained in the body of the request, which is separated from the HTTP header section by two Carriage Return - Line Feed combinations. So, we make sure that the incoming request contains this delimiter, and then send everything following it to the call decoder:

    mapping m;
    int off = search(request->raw, "rnrn");

if(off<=0) error("invalid request format.&#110;");

object X;

if(catch(X=Protocols.XMLRPC.decode_call(request->raw[(off+4) ..]))) { error("Error decoding the XML-RPC Call. Are you not speaking XML-RPC?&#110;"); }

Once we've determined that we have a valid request, it's time to see if we have the method requested, and if so, we call it:

 if(this && this[X->method_name] &&
        functionp(this[X->method_name]))
    {

mixed response; mixed err=catch( response=call_function(this[X->method_name], @(X->params + ({request}) ))); if(err) { m=([ "data": Protocols.XMLRPC.encode_response_fault(1, err[0]), "type": "text/xml" ]); } else m=([ "data": Protocols.XMLRPC.encode_response( ({response}) ), "type": "text/xml" ]); } else { m=([ "data": Protocols.XMLRPC.encode_response_fault(1, "Method " + X->method_name + " not present.&#110;"), "type": "text/xml" ]); } return (m);

In this example, we make any public method in the current object callable remotely. Obviously, that's not such a good idea, but for the purposes of our example, it works just fine. It's important to note that XML-RPC doesn't have formal specifications for defining method names or parameters, so you'll need to make sure that your application does checking as appropriate.

The finished server

Here's our final XML-RPC server, included for good measure:

#!/usr/local/bin/pike

constant default_port = 8908;

Protocols.HTTP.Server.Port port;

int main(int argc, array(string) argv) { int my_port = default_port; if(argc>1) my_port=(int)argv[1];

port = Protocols.HTTP.Server.Port(handle_request, my_port); return -1; }

void handle_request(Protocols.HTTP.Server.Request request) { write(sprintf("Got request: %O&#110;", request));

mapping response = ([]); if(catch(response = parse(request))) { response = (["error": 500, "type": "text/html", "data": "an unknown error has occurred."]); }

request->response_and_finish(response); }

mixed parse(object request) { mapping m; int off = search(request->raw, "&#114;&#110;&#114;&#110;");

if(off<=0) error("invalid request format.&#110;");

object X;

if(catch(X=Protocols.XMLRPC.decode_call(request->raw[(off+4) ..]))) { error("Error decoding the XMLRPC Call. Are you not speaking XMLRPC?&#110;"); }

if(this && this[X->method_name] && functionp(this[X->method_name])) { mixed response; mixed err=catch( response=call_function(this[X->method_name], @(X->params + ({request}) ))); if(err) { m=([ "data": Protocols.XMLRPC.encode_response_fault(1, err[0]), "type": "text/xml" ]); } else m=([ "data": Protocols.XMLRPC.encode_response( ({response}) ), "type": "text/xml" ]); } else { m=([ "data": Protocols.XMLRPC.encode_response_fault(1, "Method " + X->method_name + " not present.&#110;"), "type": "text/xml" ]); } return (m);

}


Powered by PikeWiki2

 
gotpike.org | Copyright © 2004 - 2009 | Pike is a trademark of Department of Computer and Information Science, Linköping University