This is a Scala library to talk to servers and clients via XML-RPC, originally created to connect to my university servers. This implementation is compliant with the specification.
Chances are, that if you are reading this, you already know what XML-RPC is. But, for the sake of completeness, here it is the definition from the specification:
XML-RPC is a Remote Procedure Calling protocol that works over the Internet. An XML-RPC message is an HTTP-POST request. The body of the request is in XML. A procedure executes on the server and the value it returns is also formatted in XML.
Despite that more powerful and modern rpc protocols are used nowadays, I have written this to support connection to older servers.
This library uses Play WS to connect to any HTTP server. It would be easy to change that if one wants to use another library like Dispatch. You are free to fork this project and make the necesssary changes in Xmlrpc.scala, located in the main package.
Thanks to Scalaz, it offers good feedback in case of any failure with the help of Validation[T]. Validation is applicative, this means that all the errors in the process of deserialization will be accumulated.
Using Shapeless, we solve the problem of writing boilerplate code for any arity of case classes and tuples. If you are more interested in a library for serialization using Shapeless, you can check PicoPickle, an extensible, more powerful library entirely written in Shapeless.
This project cross-compiles to Scala 2.11 and 2.12.
In order to add it to your project, write the following in your build.sbt:
libraryDependencies ++= Seq("com.github.jvican" %% "xmlrpc" % "1.2.1")
It solves the problem of serializing and deserializing types in a fancy way. Moreover, it does so simpler than other libraries, using the power of type classes and implicits. This technique was proposed by David McIver in sbinary and it's very powerful, being used broadly in json and xml libraries.
A tiny example using case classes. Tuples, Option[T] and roughly any standard type can be used to read and write XML-RPC messages (if you want for some type in particular, please let me know). This example only shows the serialization and deserialization but not the use of invokeMethod. If you are implementing a server, you may use only this feature to reply the client.
import xmlrpc.protocol.XmlrpcProtocol._
// As you see, no boilerplate code is needed
case class Subject(code: Int, title: String)
case class Student(name: String, age: Int, currentGrade: Double, favorite: Subject)
val history = Subject(1, "Contemporary History")
val charles = Student("Charles de Gaulle", 42, 7.2, history)
// The only restriction is to explicitly mark the type
writeXmlRequest[Student]("addStudent", Some(charles))
// This is a confirmation from the server
case class Confirmation(message: String)
// Make sure you always mark the return type
readXmlResponse[Confirmation](<methodResponse>
<params>
<param>
<value><string>{"Ok"}</string></value>
</param>
</params>
</methodResponse>)
First, import the library:
import xmlrpc.protocol.XmlrpcProtocol._
import xmlrpc.Xmlrpc._
If you don't have an Actor System in scope or you don't have an environment created for WS Client, you must set it up:
implicit val system = ActorSystem()
implicit val ma = ActorMaterializer()
implicit val ws_client = StandaloneAhcWSClient()
implicit val timeout = Timeout(5 seconds)
import system.dispatcher
Now, we set up the XML-RPC server and invoke any method:
implicit val testServer = XmlrpcServer("http://betty.userland.com/RPC2")
val response: XmlrpcResponse[Int] = invokeMethod[String, Int]("methodName", "Hello World!")
You don't have to explicit the type of the response. If you want to have a Future[Int], you can access the attribute underlying of the response. XmlrpcResponse is a wrapper useful when we want to invoke several methods, because it allow us to use for-comprehensions and chain them.
At this moment, it's not possible to serialize and deserialize Seq[Any]. For example, you cannot serialize List("a", 1)
. In case you need this, it's better to use case classes if the appearances of these types are cyclic, e.g. List("a", 1, "a", 1)
. When I have more time, I would include this functionality in the library.