Now that you have seen that it truly is easy to create an OpenSRF service, we can take a look at what is going on under the covers to make all of this work for you.
One of the core innovations of OpenSRF was to use the Extensible Messaging and Presence Protocol (XMPP, more colloquially known as Jabber) as the messaging bus that ties OpenSRF services together across servers. XMPP is an "XML protocol for near-real-time messaging, presence, and request-response services" (http://www.ietf.org/rfc/rfc3920.txt) that OpenSRF relies on to handle most of the complexity of networked communications. OpenSRF achieves a measure of security for its services through the use of public and private XMPP domains; all OpenSRF services automatically register themselves with the private XMPP domain, but only those services that register themselves with the public XMPP domain can be invoked from public OpenSRF clients.
In a minimal OpenSRF deployment, two XMPP users named "router" connect to the XMPP server, with one connected to the private XMPP domain and one connected to the public XMPP domain. Similarly, two XMPP users named "opensrf" connect to the XMPP server via the private and public XMPP domains. When an OpenSRF service is started, it uses the "opensrf" XMPP user to advertise its availability with the corresponding router on that XMPP domain; the XMPP server automatically assigns a Jabber ID (JID) based on the client hostname to each service’s listener process and each connected drone process waiting to carry out requests. When an OpenSRF router receives a request to invoke a method on a given service, it connects the requester to the next available listener in the list of registered listeners for that service.
The opensrf and router user names, passwords, and domain names, along with the
list of services that should be public, are contained in the opensrf_core.xml
configuration file.
OpenSRF was an early adopter of JavaScript Object Notation (JSON). While XMPP is an XML protocol, the Evergreen developers recognized that the compactness of the JSON format offered a significant reduction in bandwidth for the volume of messages that would be generated in an application of that size. In addition, the ability of languages such as JavaScript, Perl, and Python to generate native objects with minimal parsing offered an attractive advantage over invoking an XML parser for every message. Instead, the body of the XMPP message is a simple JSON structure. For a simple request, like the following example that simply reverses a string, it looks like a significant overhead: but we get the advantages of locale support and tracing the request from the requester through the listener and responder (drone).
A request for opensrf.simple-text.reverse("foobar"):
<message from='router@private.localhost/opensrf.simple-text' to='opensrf@private.localhost/opensrf.simple-text_listener_at_localhost_6275' router_from='opensrf@private.localhost/_karmic_126678.3719_6288' router_to='' router_class='' router_command='' osrf_xid='' > <thread>1266781414.366573.12667814146288</thread> <body> [ {"__c":"osrfMessage","__p": {"threadTrace":"1","locale":"en-US","type":"REQUEST","payload": {"__c":"osrfMethod","__p": {"method":"opensrf.simple-text.reverse","params":["foobar"]} } } } ] </body> </message>
A response from opensrf.simple-text.reverse("foobar").
<message from='opensrf@private.localhost/opensrf.simple-text_drone_at_localhost_6285' to='opensrf@private.localhost/_karmic_126678.3719_6288' router_command='' router_class='' osrf_xid='' > <thread>1266781414.366573.12667814146288</thread> <body> [ {"__c":"osrfMessage","__p": {"threadTrace":"1","payload": {"__c":"osrfResult","__p": {"status":"OK","content":"raboof","statusCode":200} } ,"type":"RESULT","locale":"en-US"} }, {"__c":"osrfMessage","__p": {"threadTrace":"1","payload": {"__c":"osrfConnectStatus","__p": {"status":"Request Complete","statusCode":205} },"type":"STATUS","locale":"en-US"} } ] </body> </message>
The content of the <body>
element of the OpenSRF request and result should
look familiar; they match the structure of the OpenSRF over HTTP examples that we previously dissected.
Let’s explore the call to __PACKAGE__->register_method()
; most of the elements
of the hash are optional, and for the sake of brevity we omitted them in the
previous example. As we have seen in the results of the introspection call, a
verbose registration method call is recommended to better enable the internal
documentation. So, for the sake of completeness here, is the set of elements
that you should pass to __PACKAGE__->register_method()
:
method
: the name of the procedure in this module that is being registered as an OpenSRF method
api_name
: the invocable name of the OpenSRF method; by convention, the OpenSRF service name is used as the prefix
api_level
: (optional) can be used for versioning the methods to allow the use of a deprecated API, but in practical use is always 1
argc
: (optional) the minimal number of arguments that the method expects
stream
: (optional) if this argument is set to any value, then the method supports returning multiple values from a single call to subsequent requests, and OpenSRF automatically creates a corresponding method with ".atomic" appended to its name that returns the complete set of results in a single request; streaming methods are useful if you are returning hundreds of records and want to act on the results as they return
signature
: (optional) a hash describing the method’s purpose, arguments, and return value
desc
: a description of the method’s purpose
params
: an array of hashes, each of which describes one of the method arguments
name
: the name of the argument
desc
: a description of the argument’s purpose
type
: the data type of the argument: for example, string, integer, boolean, number, array, or hash
return
: a hash describing the return value of the method
desc
: a description of the return value
type
: the data type of the return value: for example, string, integer, boolean, number, array, or hash