More Go RPC, using gorilla/rpc/json

Following on from an earlier post about Go and RPC, this time we're looking at gorilla/rpc and gorilla/rpc/json. One weakness of the prior example in the post linked above was it required both parts, client and server to be written in Go, a tight coupling not immediately apparent to someone learning either Go or RPC. Enter JSON...

Using JSON allows us to send a request as JSON and receive a response as JSON thus removing a tight coupling that we had previously.

Setup the project

OK let's get started. You're going to need a project structure much like below, obviously the modules files are optional if you're a GOPATH user and you don't strictly need a readme but you should by force of habit include one in your projects.

gorilla-jsonrpc-demo
├── go.mod
├── go.sum
├── projects.json
├── readme.md
└── server
    └── main.go

go get github.com/gorilla/rpc
go get -u github.com/gorilla/mux

The json data file

Here I've created a dummy data file with some sample projects structures in it. Extremely trivial to avoid complicating the demo.

[
    {
        "name": "go/golang",
        "author": "google",
        "stars": "88200",
        "forks": "12900"
    },
    {
        "name": "chi",
        "author": "go-chi",
        "stars": "9800",
        "forks": "665"
    },
    {
        "name": "go-me",
        "author": "me",
        "stars": "1",
        "forks": "0"
    },
    {
        "name": "go-you",
        "author": "you",
        "stars": "100",
        "forks": "10"
    },
    {
        "name": "go-them",
        "author": "them",
        "stars": "2000",
        "forks": "112"
    }
]

You can adapt your file, structures to suit your case or just to play around with more complex structures.

Implementing the RPC server

We're going to make a couple of changes to the previous server we wrote and although the changes are slight they make a world difference.

package main

import (
    jsonparse "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "path/filepath"

    "github.com/gorilla/mux"
    "github.com/gorilla/rpc"
    "github.com/gorilla/rpc/json"
)

type Args struct {
    Name string
}

type Repo struct {
    Name   string `json:"name,omitempty"`
    Author string `json:"author,omitempty"`
    Forks  string `json:"forks,omitempty"`
    Stars  string `json:"stars,omitempty"`
}

type JSONServer struct{}

func (t *JSONServer) GiveRepoDetails(r *http.Request, args *Args, reply *Repo) error {
    var repos []Repo

    // get the datafile
    absPath, _ := filepath.Abs("projects.json")

    raw, readerr := ioutil.ReadFile(absPath)
    if readerr != nil {
        log.Println("error:", readerr)
        os.Exit(1)
    }

    // unmarshal the raw data from the file into our array for the repos
    marshalerr := jsonparse.Unmarshal(raw, &repos)
    if marshalerr != nil {
        log.Println("error:", marshalerr)
        os.Exit(1)
    }

    for _, repo := range repos {
        if repo.Name == args.Name {
            // we found a hit
            fmt.Println(repo)
            *reply = repo
            break
        }
    }
    return nil
}

func main() {

    port := ":8080"

    s := rpc.NewServer()                                 // Create a new RPC server
    s.RegisterCodec(json.NewCodec(), "application/json") // Register the type of data requested as JSON
    s.RegisterService(new(JSONServer), "")               // Register the service by creating a new JSON server

    r := mux.NewRouter()
    r.Handle("/rpc", s)
    log.Fatal(http.ListenAndServe(port, r))

}

Things to note:

  • we've had to rename the standard encoding/json to jsonparse to avoid clashing with the gorilla/rpc json library in our code.
  • we're using a gorilla mux
  • we are registering a codec for application/json
  • we register the service
  • in the service we're unmarshaling a file into an array
  • our results are sent back in JSON format.

Testing our server

So, how do we run this? Where is the client application this time? Of course we could create one but we can just as easily demo it with a fairly simple curl request.

curl -X POST \
   http://localhost:8080/rpc \
   -H 'cache-control: no-cache' \
   -H 'content-type: application/json' \
   -d '{
   "method": "JSONServer.GiveRepoDetails",
   "params": [{
   "name": "go-me"
   }],"id": "1"}'

Things to note about the curl request:

  • To get a response you need an ID, I too learned this the hard way after way too much time thinking where is my response? Why is there no output? Thanks to this post on SO I resolved my issue by adding the "id": "1" section.

I hope this has been simple enough for you get an easy takeaway from, about why and how you might use the gorilla libs to help you knock up an RPC json solution.

Best wishes.

16