README

Golang JSON-RPC 2.0 HTTP Server

GoDoc

This library is an HTTP server implementation of the JSON-RPC 2.0 Specification. The library is fully spec compliant with support for named and positional arguments and batch requests.

Installation
go get github.com/bitwurx/jrpc2
Quickstart
package main

import (
    "encoding/json"
    "errors"
    "os"

    "github.com/bitwurx/jrpc2"
)

// This struct is used for unmarshaling the method params
type AddParams struct {
    X *float64 `json:"x"`
    Y *float64 `json:"y"`
}

// Each params struct must implement the FromPositional method.
// This method will be passed an array of interfaces if positional parameters
// are passed in the rpc call
func (ap *AddParams) FromPositional(params []interface{}) error {
    if len(params) != 2 {
        return errors.New("exactly two integers are required")
    }

    x := params[0].(float64)
    y := params[1].(float64)
    ap.X = &x
    ap.Y = &y

    return nil
}

// Each method should match the prototype <fn(json.RawMessage) (inteface{}, *ErrorObject)>
func Add(params json.RawMessage) (interface{}, *jrpc2.ErrorObject) {
    p := new(AddParams)

    // ParseParams is a helper function that automatically invokes the FromPositional
    // method on the params instance if required
    if err := jrpc2.ParseParams(params, p); err != nil {
        return nil, err
    }

    if p.X == nil || p.Y == nil {
        return nil, &jrpc2.ErrorObject{
            Code:    jrpc2.InvalidParamsCode,
            Message: jrpc2.InvalidParamsMsg,
            Data:    "exactly two integers are required",
        }
    }

    return *p.X + *p.Y, nil
}

func main() {
    // create a new server instance
    s := jrpc2.NewServer(":8888", "/api/v1/rpc", nil)

    // register the add method
    s.Register("add", jrpc2.Method{Method: Add})

    // register the subtract method to proxy another rpc server
    // s.Register("add", jrpc2.Method{Url: "http://localhost:9999/api/v1/rpc"})

    // start the server instance
    s.Start()
}

When defining your own registered methods with the rpc server it is important to consider both named and positional parameters per the specification.

While named arguments are more straightforward, this library aims to be fully spec compliant, therefore positional parameters must be handled accordingly.

The ParseParams helper function should be used to ensure positional parameters are automatically resolved by the params struct's FromPositional handler method. The spec states by-position: params MUST be an Array, containing the values in the Server expected order., so handling positional argument by direct subscript reference, where positional arguments are valid, should be considered safe.

Multiplexing Server

The jrpc2 Server only supports a single method handler. This may not be suitable for versioned rpc APIs or any other implementation that requires more than a single rpc route. The multiplexing server was added to support this use case.

Example:

package main

import (
    "encoding/json"
    "github.com/bitwurx/jrpc2"
)

type AddV1Params struct {
    X int `json:x`
    Y int `json:y`
}

func (p *AddV1Params) FromPositional(params []interface{}) error {
    p.X = int(params[0].(float64))
    p.Y = int(params[1].(float64))
    return nil
}

func AddV1(params json.RawMessage) (interface{}, *jrpc2.ErrorObject) {
    p := new(AddV1Params)
    if err := jrpc2.ParseParams(params, p); err != nil {
        return nil, err
    }
    return p.X + p.Y, nil
}

type AddV2Params struct {
    Args []float64 `json:args`
}

func (p *AddV2Params) FromPositional(params []interface{}) error {
    p.Args = params[0].([]float64)
    return nil
}

func AddV2(params json.RawMessage) (interface{}, *jrpc2.ErrorObject) {
    p := new(AddV2Params)
    if err := jrpc2.ParseParams(params, p); err != nil {
        return nil, err
    }
    return p.Args[0] + p.Args[1], nil
}

func main() {
    v1 := jrpc2.NewMuxHandler()
    v1.Register("add", jrpc2.Method{Method: AddV1})
    v2 := jrpc2.NewMuxHandler()
    v2.Register("add", jrpc2.Method{Method: AddV2})
    s := jrpc2.NewMuxServer(":8080", nil)
    s.AddHandler("/rpc/v1", v1)
    s.AddHandler("/rpc/v2", v2)
    s.Start()
}

The mux server api is designed to mimic the single server api as closely as possible. The key difference is the addition of the mux handler which handles method registration. Once methods are registered to the handler, the handler is added to the mux server. The mux server can then be started with the Start() method exactly like the single server.

Each registered handler isolates all registered methods so duplicating method names between handlers is fully supported.

Warning: Mixing single and multiplexing servers can result in unexpected behavior and is not recommended.

Proxy Server

The jrpc2 HTTP server is capable of proxying another jrpc2 HTTP server's requests out of the box. The jrpc2.register method allows rpc registration of a method. Registration requires a method name and a url of the server to proxy.

The following request is an example of method registration:

{"jsonrpc": "2.0", "method": "jrpc2.register", "params": ["subtract", "http://localhost:8080/api/v1/rpc"]}

Methods can also be explicitly registered using the server's Register method:

s.Register("add", jrpc2.Method{Url: "http://localhost:8080/api/v1/rpc"})

Running Tests

This library contains a set of api tests to verify spec compliance. The provided tests are a subset of the Section 7 Examples here.

go test ./... -v
License
Copyright (c) 2017 Jared Patrick <jared.patrick@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Expand ▾ Collapse ▴

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewResponse

func NewResponse(result interface{}, errObj *ErrorObject, id interface{}, nl bool) []byte

    NewResponse creates a bytes encoded representation of a response. Both result and error response objects can be created. The nl flag specifies if the response should be newline terminated.

    Types

    type Batch

    type Batch struct {
    	// Responses contains the byte representations of a batch of responses.
    	Responses [][]byte
    }

      Batch is a wrapper around multiple response objects.

      func (*Batch) AddResponse

      func (b *Batch) AddResponse(resp []byte)

        AddResponse inserts the response into the batch responses.

        func (*Batch) MakeResponse

        func (b *Batch) MakeResponse() []byte

          MakeResponse creates a bytes encoded representation of a response object.

          type ErrorCode

          type ErrorCode int

            ErrorCode is a json rpc 2.0 error code.

            const (
            	ParseErrorCode     ErrorCode = -32700
            	InvalidRequestCode ErrorCode = -32600
            	MethodNotFoundCode ErrorCode = -32601
            	InvalidParamsCode  ErrorCode = -32602
            	InternalErrorCode  ErrorCode = -32603
            	MethodExistsCode   ErrorCode = -32000
            	URLSchemeErrorCode ErrorCode = -32001
            )

              Error codes

              type ErrorMsg

              type ErrorMsg string

                ErrorMsg is a json rpc 2.0 error message.

                const (
                	ParseErrorMsg     ErrorMsg = "Parse error"
                	InvalidRequestMsg ErrorMsg = "Invalid Request"
                	MethodNotFoundMsg ErrorMsg = "Method not found"
                	InvalidParamsMsg  ErrorMsg = "Invalid params"
                	InternalErrorMsg  ErrorMsg = "Internal error"
                	ServerErrorMsg    ErrorMsg = "Server error"
                	MethodExistsMsg   ErrorMsg = "Method exists"
                	URLSchemeErrorMsg ErrorMsg = "URL scheme error"
                )

                  Error message

                  type ErrorObject

                  type ErrorObject struct {
                  	// Code indicates the error type that occurred.
                  	// Message provides a short description of the error.
                  	// Data is a primitive or structured value that contains additional information.
                  	// about the error.
                  	Code    ErrorCode   `json:"code"`
                  	Message ErrorMsg    `json:"message"`
                  	Data    interface{} `json:"data,omitempty"`
                  }

                    ErrorObject represents a response error object.

                    func ParseParams

                    func ParseParams(params json.RawMessage, p Params) *ErrorObject

                      ParseParams processes the params data structure from the request. Named parameters will be umarshaled into the provided Params inteface. Positional arguments will be passed to Params interface's FromPositional method for extraction.

                      type Method

                      type Method struct {
                      	// Url is the url of the server that handles the method.
                      	// Method is the callable function
                      	Url    string
                      	Method func(params json.RawMessage) (interface{}, *ErrorObject)
                      }

                        Method represents an rpc method.

                        type MethodWithContext

                        type MethodWithContext struct {
                        	// Url is the url of the server that handles the method.
                        	// Method is the callable function
                        	Url    string
                        	Method func(ctx context.Context, params json.RawMessage) (interface{}, *ErrorObject)
                        }

                          MethodWithContext represents an rpc method with a context.

                          type MuxHandler

                          type MuxHandler struct {
                          	Methods map[string]MethodWithContext
                          }

                            MuxHandler is a method dispatcher that handles request at a designated route.

                            func NewMuxHandler

                            func NewMuxHandler() *MuxHandler

                              NewMuxHandler creates a new mux handler instance.

                              func (*MuxHandler) Register

                              func (h *MuxHandler) Register(name string, method Method)

                                Register adds the method to the handler methods.

                                func (*MuxHandler) RegisterWithContext

                                func (h *MuxHandler) RegisterWithContext(name string, method MethodWithContext)

                                  RegisterWithContext adds the method to the handler methods.

                                  type MuxServer

                                  type MuxServer struct {
                                  	Host     string
                                  	Headers  map[string]string
                                  	Handlers map[string]*MuxHandler
                                  }

                                    MuxServer is a json rpc 2 server that handles multiple requests.

                                    func NewMuxServer

                                    func NewMuxServer(host string, headers map[string]string) *MuxServer

                                      NewMuxServer creates a new mux handler instance.

                                      func (*MuxServer) AddHandler

                                      func (s *MuxServer) AddHandler(route string, handler *MuxHandler)

                                        AddHandler add the handler to the mux handlers.

                                        func (*MuxServer) Start

                                        func (s *MuxServer) Start()

                                          Start Starts binds all server rpcHandlers to their handler routes and starts the http server.

                                          func (*MuxServer) StartTLS

                                          func (s *MuxServer) StartTLS(certFile, keyFile string)

                                            Start Starts binds all server rpcHandlers to their handler routes and starts the https server.

                                            type Params

                                            type Params interface {
                                            	FromPositional([]interface{}) error
                                            }

                                              Params defines methods for processing request parameters.

                                              type RegisterRPCParams

                                              type RegisterRPCParams struct {
                                              	// Name is the the name of the method being registered.
                                              	// Url is the url of the server that handles the method.
                                              	Name *string
                                              	Url  *string
                                              }

                                                RegisterRPCParams is a paramater spec for the RegisterRPC method.

                                                func (*RegisterRPCParams) FromPositional

                                                func (rp *RegisterRPCParams) FromPositional(params []interface{}) error

                                                  FromPositional extracts the positional name and url parameters from a list of parameters.

                                                  type RequestObject

                                                  type RequestObject struct {
                                                  	// Jsonrpc specifies the version of the JSON-RPC protocol.
                                                  	// Must be exactly "2.0".
                                                  	// Method contains the name of the method to be invoked.
                                                  	// Params is a structured value that holds the parameter values to be used during
                                                  	// the invocation of the method.
                                                  	// Id is a unique identifier established by the client.
                                                  	Jsonrpc string          `json:"jsonrpc"`
                                                  	Method  interface{}     `json:"method"`
                                                  	Params  json.RawMessage `json:"params"`
                                                  	Id      interface{}     `json:"id"`
                                                  	// contains filtered or unexported fields
                                                  }

                                                    RequestObject represents a request object

                                                    type ResponseObject

                                                    type ResponseObject struct {
                                                    	// Jsonrpc specifies the version of the JSON-RPC protocol.
                                                    	// Must be exactly "2.0".
                                                    	// Error contains the error object if an error occurred while processing the request.
                                                    	// Result contains the result of the called method.
                                                    	// Id contains the client established request id or null.
                                                    	Jsonrpc string       `json:"jsonrpc"`
                                                    	Error   *ErrorObject `json:"error,omitempty"`
                                                    	Result  interface{}  `json:"result,omitempty"`
                                                    	Id      interface{}  `json:"id"`
                                                    }

                                                      ResponseObject represents a response object.

                                                      type Server

                                                      type Server struct {
                                                      	// Host is the host:port of the server.
                                                      	// Route is the path to the rpc api.
                                                      	// Methods contains the mapping of registered methods.
                                                      	// Headers contains response headers.
                                                      	Host    string
                                                      	Route   string
                                                      	Methods map[string]MethodWithContext
                                                      	Headers map[string]string
                                                      }

                                                        Server represents a jsonrpc 2.0 capable web server.

                                                        func NewServer

                                                        func NewServer(host, route string, headers map[string]string) *Server

                                                          NewServer creates a new server instance.

                                                          func (*Server) Call

                                                          func (s *Server) Call(ctx context.Context, name interface{}, params json.RawMessage) (interface{}, *ErrorObject)

                                                            Call invokes the named method with the provided parameters. If a method from the server Methods has a Method member will be called locally. If a method from the server Methods has a Url member it will be called by proxy.

                                                            func (*Server) HandleBatch

                                                            func (s *Server) HandleBatch(w http.ResponseWriter, reqs []*RequestObject)

                                                              HandleBatch validates, calls, and returns the results of a batch of rpc client requests. Batch methods are called in individual goroutines and collected in a single response.

                                                              func (*Server) HandleRequest

                                                              func (s *Server) HandleRequest(w http.ResponseWriter, req *RequestObject)

                                                                HandleRequest validates, calls, and returns the result of a single rpc client request.

                                                                func (*Server) ParseRequest

                                                                func (s *Server) ParseRequest(w http.ResponseWriter, r *http.Request) *ErrorObject

                                                                  ParseRequest parses the json request body and unpacks into one or more. RequestObjects for single or batch processing.

                                                                  func (*Server) Register

                                                                  func (s *Server) Register(name string, method Method)

                                                                    Register maps the provided method to the given name for later method calls.

                                                                    func (*Server) RegisterRPC

                                                                    func (s *Server) RegisterRPC(ctx context.Context, params json.RawMessage) (interface{}, *ErrorObject)

                                                                      RegisterRPC accepts a method name and server url to register a proxy rpc method. A method name can be only be registered once.

                                                                      func (*Server) RegisterWithContext

                                                                      func (s *Server) RegisterWithContext(name string, method MethodWithContext)

                                                                      func (*Server) Start

                                                                      func (s *Server) Start()

                                                                        Start binds the rpcHandler to the server route and starts the http server.

                                                                        func (*Server) StartTLS

                                                                        func (s *Server) StartTLS(certFile, keyFile string)

                                                                          Start binds the rpcHandler to the server route and starts the https server.

                                                                          func (*Server) StartTLSWithMiddleware

                                                                          func (s *Server) StartTLSWithMiddleware(certFile, keyFile string, m func(next http.HandlerFunc) http.HandlerFunc)

                                                                            StartWithMiddleware binds the rpcHandler, with its middleware to the server route and starts the https server.

                                                                            func (*Server) StartWithMiddleware

                                                                            func (s *Server) StartWithMiddleware(m func(next http.HandlerFunc) http.HandlerFunc)

                                                                              StartWithMiddleware binds the rpcHandler, with its middleware to the server route and starts the http server.

                                                                              func (*Server) ValidateRequest

                                                                              func (s *Server) ValidateRequest(req *RequestObject) *ErrorObject

                                                                                ValidateRequest validates that the request json contains valid values.

                                                                                Source Files