https://github.com/brehaut/necessary-evil.git
git clone 'https://github.com/brehaut/necessary-evil.git'
(ql:quickload :brehaut.necessary-evil)
necessary-evil is an implementation of XML-RPC built on top of the ring http library for Clojure. XML-RPC is a bit nasty, but it is the basis of a number of other standards such as certain blogging APIs and Ping Back.
necessary-evil
will only work with Java 6+, and Clojure 1.2.1+
(require '[necessary-evil.core :as xml-rpc])
Making a client request is very simple:
(xml-rpc/call "http://example.com/rpc" :hello "World")
This will either return a clojure data structure or, if there is a fault, a necessary-evil.methodresponse.Fault
record. For the example above, you might expect something like "Hello, World!"
to be returned. See xml-rpc mappings below for details of how xml-rpc data is converted to clojure data and vice versa.
Call accepts the arguments to the remote method as varags after the method name.
Here is a simple hello world request handler:
(use '[ring.adapter.jetty :only [run-jetty]])
(def handler (xml-rpc/end-point
{:hello (fn hello
([] (hello "World"))
([name] (str "Hello, " name "!")))}))
(run-jetty handler {:port 3000 :join? false})
Methods are :keyword iFn pairs in the method map passed to end-point.
As XML-RPC requests are always HTTP POST requests, necessary-evil implements a very bare bones GET handler that returns a comma separated list of method names.
The handler generated by end-point should work properly with any other ring handler, and should play nice with middleware or any other ring library such as compojure or moustache.
The following is a trivial example to attach an xml-rpc endpoint into a hello world compojure application:
(require '[necessary-evil.core :as xmlrpc])
(use '[ring.adapter.jetty :only [run-jetty]])
(use '[compojure.core :only [defroutes GET ANY]])
(def ep (xmlrpc/end-point
{:hello (fn [n] (str "Hello, " n "!"))}))
(defroutes handler
(GET "/hello" [] "Hello!")
(ANY "/xml" [] ep))
(run-jetty #'handler {:port 3000 :join? false})
In this application /
is a 404, /hello
returns “Hello!”, and /xml
is the xmlrpc handler.
This snippet implements the same server as the one above for Compojure:
(require '[necessary-evil.core :as xmlrpc])
(use '[ring.adapter.jetty :only [run-jetty]])
(use '[net.cgrand.moustache :only [app]])
(def ep (xmlrpc/end-point
{:hello (fn [n] (str "Hello, " n "!"))}))
(def handler
(app ["hello"] {:get "Hello!"}
["xml"] ep))
(run-jetty #'handler {:port 3000 :join? false})
Just as in the compojure example above, /
is a 404, /hello
returns “Hello!”, and /xml
is the xmlrpc handler.
These tables describes the mapping of clojure datastructures and types to XML-RPC types. Note that as of version 2.0.0 these are no longer symmetric operations.
XML-RPC → Clojure | |
---|---|
XML-RPC Element | Clojure or Java type |
array | clojure.lang.IPersistentVector |
base64 | byte-array |
boolean | java.lang.Boolean |
dateTime.iso8601 | org.joda.time.DateTime |
double | java.lang.Double |
i4 | java.lang.Integer |
int | java.lang.Integer |
struct | clojure.lang.IPersistantMap — clojure.lang.Keyword keys |
no element | java.lang.String |
Clojure → XML-RPC | |
---|---|
Clojure or Java type | XML-RPC Element |
byte-array | base64 |
clojure.lang.IPersistantMap — clojure.lang.Keyword keys | struct |
clojure.lang.Sequential | array |
java.lang.Boolean | boolean |
java.lang.Double | double |
java.lang.Integer | int |
java.lang.Long | int – Longs that are greater than Integer/MAX_VALUE will cause an exception to be thrown. |
java.lang.String | string |
java.util.Date | dateTime.iso8601 |
org.joda.time.DateTime | dateTime.iso8601 |
Note: nil
is conspicuously absent from the list of types; this is because the spec for xml-rpc itself does not include any canonical representation.
It is possible to extend the support to additional data types trivially. All the details of parsing and unparsing the various value types is handled in the necessary-evil.value
namespace with the multimethod parse-value
and the protocol ValueTypeElem
. Simple implement the appropriate pair for each of these in your own code.
Keep in mind that if you specify a mapping from a new Clojure type to an existing xmlrpc type that the mapping will be asymmetric. If you add additional xmlrpc types keep in mind that the xmlrpc implementation at the other end will also need to know how to serialize and deserialize the type.
The following the main API functions that you will use as a consumer of the library.
necessary-evil.core/end-point
— Defines a Ring handler that acts as an XML-RPC end-point. See above for examples.necessary-evil.core/call
— Calls an XML-RPC function at a given end point.necessary-evil.core/call*
— Calls an XML-RPC function at a given end point. Allows for more control than necessary-evil.core/call
.necessary-evil.fault/fault
— Creates a new fault, use this if you need to return an error condition to the caller.necessary-evil.fault/fault?
— Predicate that tests a value for being a Fault record.necessary-evil.fault/attempt-all
— A comprehension form to make it easier to work with potentially Fault returning functions. For more detail on this macro see my Error Monads and Error Monads Revisited blog posts.Despite the big jump in version numbers relatively small changes have occured.
ValueTypeElem
protocol:clojure.lang.PersistanceVector
, any
clojure.lang.Sequential
implementor will be serialized to an array; this includes lists and
lazy sequences.Long
s are now serialized as Integer
s (and must not exceed Integer.MAX_VALUE
as the xmlrpc spec only allows for 4 byte signed ints).java.util.Date
objects are now serialized to timecall*
function added to necessary-evil.core
to add more fine grained control to the http request.
call
now uses call*
under the hood. The major difference is that call*
takes the remote functions arguments as a sequence, and has keyword options for the configurable things.Note that the serialization and deserialization processes are now asymmetric: For example in a round trip a list will return as vector, Java dates will return as Joda time dates and longs as ints.
necessary-evil.value/allow-nils
form.clj-time
fault
handling code is now in its own namespace: necessary-evil.fault
necessary-evil.fault
now includes attempt-all
macro to stream line
writing code that may generate faults in multiple stages.Laurent Petit has a recipe for making Make a SSL certificate visible to your app. This may be relevant if you wish to secure your api with HTTPS and are not running your application behind another web server.
Thanks to the following people for their feedback and assistance:
clj-xmlrpc
code).Copyright (C) 2010, 2011 Andrew Brehaut
Distributed under the Eclipse Public License, the same as Clojure.