README
¶
Golang JSON-RPC 2.0 HTTP Server
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.
Documentation
¶
Index ¶
- func NewResponse(result interface{}, errObj *ErrorObject, id interface{}, nl bool) []byte
- type Batch
- type ErrorCode
- type ErrorMsg
- type ErrorObject
- type Method
- type MethodWithContext
- type MuxHandler
- type MuxServer
- type Params
- type RegisterRPCParams
- type RequestObject
- type ResponseObject
- type Server
- func (s *Server) Call(ctx context.Context, name interface{}, params json.RawMessage) (interface{}, *ErrorObject)
- func (s *Server) HandleBatch(w http.ResponseWriter, reqs []*RequestObject)
- func (s *Server) HandleRequest(w http.ResponseWriter, req *RequestObject)
- func (s *Server) ParseRequest(w http.ResponseWriter, r *http.Request) *ErrorObject
- func (s *Server) Register(name string, method Method)
- func (s *Server) RegisterRPC(ctx context.Context, params json.RawMessage) (interface{}, *ErrorObject)
- func (s *Server) RegisterWithContext(name string, method MethodWithContext)
- func (s *Server) Start()
- func (s *Server) StartTLS(certFile, keyFile string)
- func (s *Server) StartTLSWithMiddleware(certFile, keyFile string, m func(next http.HandlerFunc) http.HandlerFunc)
- func (s *Server) StartWithMiddleware(m func(next http.HandlerFunc) http.HandlerFunc)
- func (s *Server) ValidateRequest(req *RequestObject) *ErrorObject
Constants ¶
Variables ¶
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 ¶
AddResponse inserts the response into the batch responses.
func (*Batch) MakeResponse ¶
MakeResponse creates a bytes encoded representation of a response object.
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 ¶
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.
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 (*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 ¶
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 ¶
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.