While working on one of my smaller projects I came across a need to make XML-RPC requests. After checking my calendar and ensuring that I did not go back to ‘98, I’ve set of in my search.

I was not very surprised that there was hardly any information or projects that would support XML-RPC in Go, especially given how ancient XML-RPC actually is.

My search results did reveal a few options, but they were no longer maintained and had somewhat quirky APIs. This got me thinking: XML-RPC in it’s nutshell is very simple, and it should be fairly easy to make a library to support my needs (and it’s a fun project).

And so I set off to create yet another http router XML-RPC client library for Go.

Design

For this client library, I had a few requirements for how I would want this library to work:

  1. It should provide a Client with a simple interface to make RPC calls.
  2. It should support handling all data structures that XML-RPC spec supports.
  3. Responses should be decodable to native Go data-types.
  4. It should be possible to use pointers in both method arguments and method responses.

Results of my tinkering around is alexejk.io/go-xmlrpc library ( Github).

Usage

To use this library, one must naturally add it to the project:

go get -u alexejk.io/go-xmlrpc

Then we can initialize the client with NewClient(endpoint string) (*Client, error) and make RPC calls with Call(method string, args interface{}, reply interface{}) error as shown in example below.

package main

import(
    "fmt"

    "alexejk.io/go-xmlrpc"
)

func main() {
    client, _ := xmlrpc.NewClient("https://bugzilla.mozilla.org/xmlrpc.cgi")

    result := &struct {
        Bugzilla struct {
            Version string
        }
    }{}

    _ = client.Call("Bugzilla.version", nil, result)
    fmt.Printf("Version: %s\n", result.Bugzilla.Version)
}

The small code above will make an XML-RPC call to Mozilla’s Bugzilla API and request an RPC method Bugzilla.version. As this method accepts no arguments, second paramter to Call is nil.

Server response is decoded into result struct. Not unlike how xml.Unmarshal works. If there are any mismatches between response XML and provided data type, library will return an appropriate error such as type 'SomeType' cannot be assigned a value of type 'string'.

Behind the scenes

I’ve decided to base the Client API on rpc.Client from net/rpc. This ensured a nice and familiar API.

I’ve taken inspiration from both net/rpc/jsonrpc as well as encoding/json and encoding/xml packages when writing encoder & decoder for the wire format. This means quite a bit of reflection is used behind the scenes. However, given the nature of this package and what it makes possible to do - I do not see this as a drawback.

What’s next

Overall I’ve had a lot of fun writing this library and it helped me get to know reflect package much better.

I’m using this library myself in another project (will post about later) and am quite satisfied with how it works. It’s probably still rough around the edges here and there - so any PRs are more than welcome.

Thanks for reading!