Barrister RPC bindings for the Go language
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
conform
example
idl2go
iris
retinaws
test
.gitignore
.travis.yml
README.md
barrister.go
barrister_test.go
conform_test.go
convert.go
generate.go
pull_and_build.sh
stdlibhttp.go
test.sh

README.md

Barrister for Go

This project contains Go bindings for the Barrister RPC system.

build status

For information on how to write a Barrister IDL, please visit:

http://barrister.bitmechanic.com/

Installation

# Install the barrister translator (IDL -> JSON)
# you need to be root (or use sudo)
pip install barrister

# Install barrister-go
go get github.com/coopernurse/barrister-go
go install github.com/coopernurse/barrister-go/idl2go

Run tests

To run the included unit tests:

go get github.com/couchbaselabs/go.assert
go test

Run example

HTTP transport

# Generate Go code from calc.idl
cd $GOPATH/src/github.com/coopernurse/barrister-go/example
barrister calc.idl | $GOPATH/bin/idl2go -i -p calc

# Compile and run server in background
go run server.go &

# Compile and run client
go run client.go

Iris transport

Iris is a messaging system that maps very cleanly to JSON-RPC request/response semantics. Iris provides a clusterable message relay that eliminates the need to manually wire endpoints together, which in a micro service architecture is quite helpful.

Barrister Iris support is very preliminary, but you can run a simple example by:

# Fetch Iris Go client
go get gopkg.in/project-iris/iris-go.v1

# Generate Go code from calc.idl
cd $GOPATH/src/github.com/coopernurse/barrister-go/example
barrister calc.idl | $GOPATH/bin/idl2go -i -p calc

# Compile and run server in background
go run iris-calc-server.go &

# Compile and run client
go run iris-calc-client.go

API documentation

http://godoc.org/github.com/coopernurse/barrister-go

idl2go usage

idl2go generates a .go file based on the IDL JSON. If the IDL contains namespaced enums or structs, the namespaced elements will be written to separate .go files.

The IDL JSON file is embedded in the generated .go file, so it is not needed at runtime.

Usage info: idl2go -h

Examples:

# Loads auth.json and generates ./auth/auth.go
idl2go -p auth auth.json

# Reads IDL JSON from STDIN and generates /tmp/designsvc/designsvc.go
idl2go -p designsvc -i -d /tmp

Writing clients

To write a Barrister client in Go:

  • Run idl2go to generate a Go package based on the Barrister IDL .json file
  • Use the generated Proxy struct in your program

Example:

package main

import (
	"fmt"
	"github.com/coopernurse/barrister-go"
	"github.com/coopernurse/barrister-go/example/calc"
)

// Accepts an URL to the endpoint of the Calculator server
// and returns a proxy that implements the Calculator interface
func NewCalculatorProxy(url string) calc.Calculator {
    // HttpTransport is provided, but you could write your
    // own transport (e.g. websockets, zeromq, etc)
	trans := &barrister.HttpTransport{Url: url}

	client := barrister.NewRemoteClient(trans, true)

	// calc.NewCalculatorProxy() is provided by the idl2go
	// generated calc.go file
	return calc.NewCalculatorProxy(client)
}

func main() {
    // create the proxy
	calc := NewCalculatorProxy("http://localhost:9233")

    // call methods on the proxy as if they were local functions
	res, err := calc.Add(51, 22.3)
	if err == nil {
		fmt.Println("Success!  51+22.3 =", res)
	} else {
		fmt.Println("ERROR! ", err)
	}

}

Writing servers

To write a Barrister server in Go:

  • Run idl2go to generate a Go package based (same step as writing a client)
  • Write structs that implement the interfaces in the IDL
  • Expose the service via HTTP (or other transport)

See example/server.go for a basic example.

Thread safety

By default interface implementations (aka "services") must be thread safe. A single shared instance will be used for all RPC requests.

If your struct implements barrister.Cloneable then CloneForReq() will be called per request, and your service interface methods can safely mutate state on the service struct for the lifespan of a single method invocation.

Security / Transport Headers

Often security is implemented via transport headers (e.g. HTTP Auth headers or session cookies). Cloneable.CloneForReq() is passed a map of the transport headers for the request. Consequently, you can copy this map to your struct and use these headers in the body of your methods to implement whatever rules you like. For example:

type MySecureService struct {
	userId   string
}

// implements Cloneable interface
func (s MySecureService) CloneForReq(headers Headers) interface{} {
	userId := ""
	cookie := barrister.GetFirst(headers.Request, "MySecureCookie")

	if cookie != "" {
		// decode cookie somehow, perhaps returning empty string on failure
        userId = decodeUserId(cookie)
	}

	return MySecureService{userId}
}

// fictitious method from the IDL
func (s MySecureService) SaveSecretStuff(req SaveReq) (SaveResp, error) {
	err := s.authorize()
	if err != nil {
	    return SaveResp{}, err
	}

	// do real work

	return SaveResp{}, nil
}

func (s MySecureService) authorize() error {
	if s.userId == "" {
        return &barrister.JsonRpcError{Code: 1000, Message: "Unauthorized"}
	}

    // OK
	return nil
}

Filters

Filters may be added to the Server instance. Filter are separate from interface implementations and run on all requests handled by the server, regardless of method.

Filters may handle the request and terminate request processing. They may also perform some passive action and allow request processing to continue normally.

Example from example/server.go

type LogFilter struct{}

func (f LogFilter) PreInvoke(r *barrister.RequestResponse) bool {
	fmt.Println("LogFilter: PreInvoke of method:", r.Method)

	// returning true continues request processing normally
	return true
}

func (f LogFilter) PostInvoke(r *barrister.RequestResponse) bool {
	fmt.Println("LogFilter: PostInvoke of method:", r.Method)
	return true
}

func main() {
	idl := barrister.MustParseIdlJson([]byte(calc.IdlJsonRaw))

	// create barrister.Server instance
	svr := calc.NewJSONServer(idl, true, CalculatorImpl{})

	// register filter
	svr.AddFilter(LogFilter{})

	http.Handle("/", &svr)
	fmt.Println("Starting Calculator server on localhost:9233")
	err := http.ListenAndServe(":9233", nil)
	if err != nil {
		panic(err)
	}
}