jsonrpc2

package module
v10.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 2, 2019 License: MIT Imports: 8 Imported by: 0

README

jsonrpc2/v9

GoDoc Go Report Card Coverage Status Build Status

Package jsonrpc2 is a complete and strictly conforming implementation of the JSON-RPC 2.0 protocol for both clients and servers.

It strives to conform to the official specification: https://www.jsonrpc.org

Getting started

Please read the official godoc documentation for the most up to date documentation.

Client

Clients use the provided types, optionally along with their own custom data types for making Requests and parsing Responses. The Request and Response types are defined so that they can accept any valid types for "id", "params", and "result".

Clients can use the Request, Response, and Error types with the json and http packages to make HTTP JSON-RPC 2.0 calls and parse their responses.

reqBytes, _ := json.Marshal(jsonrpc2.NewRequest("subtract", 0, []int{5, 1}))
httpResp, _ := http.Post("www.example.com", "application/json",
        bytes.NewReader(reqBytes))
respBytes, _ := ioutil.ReadAll(httpResp.Body)
response := jsonrpc2.Response{Result: &MyCustomResultType{}}
json.Unmarshal(respBytes, &response)
Server

Servers define their own MethodFuncs and associate them with a method name in a MethodMap. Passing the MethodMap to HTTPRequestHandler() will return a corresponding http.Handler which can be used with an http.Server. The http.Handler handles both batch and single requests, catches all protocol errors, and recovers from any panics or invalid return values from the user provided MethodFunc. MethodFuncs only need to catch errors related to their function such as InvalidParams or any user defined errors for the RPC method.

func versionMethod(params json.RawMessage) interface{} {
	if params != nil {
		return jsonrpc2.NewInvalidParamsError("no params accepted")
	}
	return "0.0.0"
}
var methods = jsonrpc2.MethodMap{"version": versionMethod}
func StartServer() {
        http.ListenAndServe(":8080", jsonrpc2.HTTPRequestHandler(methods))
}

Documentation

Overview

Package jsonrpc2 is a complete and strictly conforming implementation of the JSON-RPC 2.0 protocol for both clients and servers.

https://www.jsonrpc.org.

Client

Clients use the provided types, optionally along with their own custom data types for making Requests and parsing Responses. The Request and Response types are defined so that they can accept any valid types for "id", "params", and "result".

Clients can use the Request, Response, and Error types with the json and http packages to make HTTP JSON-RPC 2.0 calls and parse their responses.

reqBytes, _ := json.Marshal(jsonrpc2.NewRequest("subtract", 0, []int{5, 1}))
httpResp, _ := http.Post("www.example.com", "application/json",
        bytes.NewReader(reqBytes))
respBytes, _ := ioutil.ReadAll(httpResp.Body)
response := jsonrpc2.Response{Result: &MyCustomResultType{}}
json.Unmarshal(respBytes, &response)

Server

Servers define their own MethodFuncs and associate them with a method name in a MethodMap. Passing the MethodMap to HTTPRequestHandler() will return a corresponding http.Handler which can be used with an http.Server. The http.Handler handles both batch and single requests, catches all protocol errors, and recovers from any panics or invalid return values from the user provided MethodFunc. MethodFuncs only need to catch errors related to their function such as Invalid Params or any user defined errors for the RPC method.

func versionMethod(params json.RawMessage) interface{} {
	if params != nil {
		return jsonrpc2.NewInvalidParamsError("no params accepted")
	}
	return "0.0.0"
}
var methods = jsonrpc2.MethodMap{"version": versionMethod}
func StartServer() {
        http.ListenAndServe(":8080", jsonrpc2.HTTPRequestHandler(methods))
}
Example

This example makes all of the calls from the examples in the JSON-RPC 2.0 specification and prints them in a similar format.

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"

	jrpc "github.com/AdamSLevy/jsonrpc2/v10"
)

var endpoint = "http://localhost:18888"

// Functions for making requests and printing the Requests and Responses.
func post(b []byte) []byte {
	httpResp, _ := http.Post(endpoint, "", bytes.NewReader(b))
	respBytes, _ := ioutil.ReadAll(httpResp.Body)
	return respBytes
}
func postNewRequest(method string, id, params interface{}) {
	postRequest(jrpc.NewRequest(method, id, params))
}
func postRequest(request interface{}) {
	fmt.Println(request)
	reqBytes, _ := json.Marshal(request)
	respBytes := post(reqBytes)
	parseResponse(respBytes)
}
func parseResponse(respBytes []byte) {
	var response interface{}
	if len(respBytes) == 0 {
		return
	} else if string(respBytes[0]) == "[" {
		response = &jrpc.BatchResponse{}
	} else {
		response = &jrpc.Response{}
	}
	json.Unmarshal(respBytes, response)
	fmt.Println(response)
	fmt.Println()
}
func postBytes(req string) {
	fmt.Println("-->", req)
	respBytes := post([]byte(req))
	parseResponse(respBytes)
}

// The RPC methods called in the JSON-RPC 2.0 specification examples.
func subtract(params json.RawMessage) interface{} {
	// Parse either a params array of numbers or named numbers params.
	var a []float64
	if err := json.Unmarshal(params, &a); err == nil {
		if len(a) != 2 {
			return jrpc.NewInvalidParamsError("Invalid number of array params")
		}
		return a[0] - a[1]
	}
	var p struct {
		Subtrahend *float64
		Minuend    *float64
	}
	if err := json.Unmarshal(params, &p); err != nil ||
		p.Subtrahend == nil || p.Minuend == nil {
		return jrpc.NewInvalidParamsError(`Required fields "subtrahend" and ` +
			`"minuend" must be valid numbers.`)
	}
	return *p.Minuend - *p.Subtrahend
}
func sum(params json.RawMessage) interface{} {
	var p []float64
	if err := json.Unmarshal(params, &p); err != nil {
		return jrpc.NewInvalidParamsError(err)
	}
	sum := float64(0)
	for _, x := range p {
		sum += x
	}
	return sum
}
func notifyHello(_ json.RawMessage) interface{} {
	return ""
}
func getData(_ json.RawMessage) interface{} {
	return []interface{}{"hello", 5}
}

// This example makes all of the calls from the examples in the JSON-RPC 2.0
// specification and prints them in a similar format.
func main() {
	// Start the server.
	go func() {
		// Register RPC methods.
		methods := jrpc.MethodMap{
			"subtract":     subtract,
			"sum":          sum,
			"notify_hello": notifyHello,
			"get_data":     getData,
		}
		handler := jrpc.HTTPRequestHandler(methods)
		jrpc.DebugMethodFunc = true
		http.ListenAndServe(":18888", handler)
	}()

	// Make requests.
	fmt.Println("Syntax:")
	fmt.Println("--> data sent to Server")
	fmt.Println("<-- data sent to Client")
	fmt.Println("")

	fmt.Println("rpc call with positional parameters:")
	postNewRequest("subtract", 1, []int{42, 23})
	postNewRequest("subtract", 2, []int{23, 42})

	fmt.Println("rpc call with named parameters:")
	postNewRequest("subtract", 3, map[string]int{"subtrahend": 23, "minuend": 42})
	postNewRequest("subtract", 4, map[string]int{"minuend": 42, "subtrahend": 23})

	fmt.Println("a Notification:")
	postNewRequest("update", nil, []int{1, 2, 3, 4, 5})
	postNewRequest("foobar", nil, nil)
	fmt.Println()

	fmt.Println("rpc call of non-existent method:")
	postNewRequest("foobar", "1", nil)

	fmt.Println("rpc call with invalid JSON:")
	postBytes(`{"jsonrpc":"2.0","method":"foobar,"params":"bar","baz]`)

	fmt.Println("rpc call with invalid Request object:")
	postBytes(`{"jsonrpc":"2.0","method":1,"params":"bar"}`)

	fmt.Println("rpc call Batch, invalid JSON:")
	postBytes(
		`[
  {"jsonrpc":"2.0","method":"sum","params":[1,2,4],"id":"1"},
  {"jsonrpc":"2.0","method"
]`)

	fmt.Println("rpc call with an empty Array:")
	postBytes(`[]`)

	fmt.Println("rpc call with an invalid Batch (but not empty):")
	postBytes(`[1]`)

	fmt.Println("rpc call with invalid Batch:")
	postBytes(`[1,2,3]`)

	fmt.Println("rpc call Batch:")
	postBytes(`[
  {"jsonrpc":"2.0","method":"sum","params":[1,2,4],"id":"1"},
  {"jsonrpc":"2.0","method":"notify_hello","params":[7]},
  {"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":"2"},
  {"foo":"boo"},
  {"jsonrpc":"2.0","method":"foo.get","params":{"name":"myself"},"id":"5"},
  {"jsonrpc":"2.0","method":"get_data","id":"9"}
]`)
	fmt.Println("rpc call Batch (all notifications):")
	postRequest(jrpc.BatchRequest{
		jrpc.NewRequest("notify_sum", nil, []int{1, 2, 4}),
		jrpc.NewRequest("notify_hello", nil, []int{7}),
	})
	fmt.Println("<-- //Nothing is returned for all notification batches")

}
Output:

Syntax:
--> data sent to Server
<-- data sent to Client

rpc call with positional parameters:
--> {"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":1}
<-- {"jsonrpc":"2.0","result":19,"id":1}

--> {"jsonrpc":"2.0","method":"subtract","params":[23,42],"id":2}
<-- {"jsonrpc":"2.0","result":-19,"id":2}

rpc call with named parameters:
--> {"jsonrpc":"2.0","method":"subtract","params":{"minuend":42,"subtrahend":23},"id":3}
<-- {"jsonrpc":"2.0","result":19,"id":3}

--> {"jsonrpc":"2.0","method":"subtract","params":{"minuend":42,"subtrahend":23},"id":4}
<-- {"jsonrpc":"2.0","result":19,"id":4}

a Notification:
--> {"jsonrpc":"2.0","method":"update","params":[1,2,3,4,5]}
--> {"jsonrpc":"2.0","method":"foobar"}

rpc call of non-existent method:
--> {"jsonrpc":"2.0","method":"foobar","id":"1"}
<-- {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":"1"}

rpc call with invalid JSON:
--> {"jsonrpc":"2.0","method":"foobar,"params":"bar","baz]
<-- {"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}

rpc call with invalid Request object:
--> {"jsonrpc":"2.0","method":1,"params":"bar"}
<-- {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":null}

rpc call Batch, invalid JSON:
--> [
  {"jsonrpc":"2.0","method":"sum","params":[1,2,4],"id":"1"},
  {"jsonrpc":"2.0","method"
]
<-- {"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}

rpc call with an empty Array:
--> []
<-- {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":null}

rpc call with an invalid Batch (but not empty):
--> [1]
<-- [
  {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":null}
]

rpc call with invalid Batch:
--> [1,2,3]
<-- [
  {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":null},
  {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":null},
  {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":null}
]

rpc call Batch:
--> [
  {"jsonrpc":"2.0","method":"sum","params":[1,2,4],"id":"1"},
  {"jsonrpc":"2.0","method":"notify_hello","params":[7]},
  {"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":"2"},
  {"foo":"boo"},
  {"jsonrpc":"2.0","method":"foo.get","params":{"name":"myself"},"id":"5"},
  {"jsonrpc":"2.0","method":"get_data","id":"9"}
]
<-- [
  {"jsonrpc":"2.0","result":7,"id":"1"},
  {"jsonrpc":"2.0","result":19,"id":"2"},
  {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":null},
  {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":"5"},
  {"jsonrpc":"2.0","result":["hello",5],"id":"9"}
]

rpc call Batch (all notifications):
--> [
  {"jsonrpc":"2.0","method":"notify_sum","params":[1,2,4]},
  {"jsonrpc":"2.0","method":"notify_hello","params":[7]}
]
<-- //Nothing is returned for all notification batches

Index

Examples

Constants

View Source
const (
	LowestReservedErrorCode  ErrorCode = -32768
	ParseErrorCode           ErrorCode = -32700
	InvalidRequestCode       ErrorCode = -32600
	MethodNotFoundCode       ErrorCode = -32601
	InvalidParamsCode        ErrorCode = -32602
	InternalErrorCode        ErrorCode = -32603
	HighestReservedErrorCode ErrorCode = -32000

	ParseErrorMessage     = "Parse error"
	InvalidRequestMessage = "Invalid Request"
	MethodNotFoundMessage = "Method not found"
	InvalidParamsMessage  = "Invalid params"
	InternalErrorMessage  = "Internal error"
)

Official JSON-RPC 2.0 Spec Error Codes and Messages

View Source
const Version = "2.0"

Version is the valid version string for the "jsonrpc" field required in all JSON RPC 2.0 objects.

Variables

View Source
var (
	// ParseError is returned to the client if a JSON is not well formed.
	ParseError = NewError(ParseErrorCode, ParseErrorMessage, nil)
	// InvalidRequest is returned to the client if a request does not
	// conform to JSON-RPC 2.0 spec
	InvalidRequest = NewError(InvalidRequestCode, InvalidRequestMessage, nil)
	// MethodNotFound is returned to the client if a method is called that
	// has not been registered with RegisterMethod()
	MethodNotFound = NewError(MethodNotFoundCode, MethodNotFoundMessage, nil)
	// InvalidParams is returned to the client if a method is called with
	// an invalid "params" object. A method's function is responsible for
	// detecting and returning this error.
	InvalidParams = NewError(InvalidParamsCode, InvalidParamsMessage, nil)
	// InternalError is returned to the client if a method function returns
	// an invalid response object.
	InternalError = NewError(InternalErrorCode, InternalErrorMessage, nil)
)

Official JSON-RPC 2.0 Errors

View Source
var DebugMethodFunc = false

DebugMethodFunc controls whether additional debug information will be printed to stdout in the event of an InternalError when a MethodFunc is called. This can be helpful when troubleshooting MethodFuncs.

Functions

func HTTPRequestHandler

func HTTPRequestHandler(methods MethodMap) http.HandlerFunc

HTTPRequestHandler returns an http.HandlerFunc for the given methods.

Types

type BatchRequest

type BatchRequest []Request

BatchRequest is a type that implements fmt.Stringer for a slice of Requests.

func (BatchRequest) String

func (br BatchRequest) String() string

String returns a string of the JSON array with "--> " prefixed to represent a BatchRequest object.

type BatchResponse

type BatchResponse []Response

BatchResponse is a type that implements fmt.Stringer for a slice of Responses.

func (BatchResponse) String

func (br BatchResponse) String() string

String returns a string of the JSON array with "<-- " prefixed to represent a BatchResponse object.

type Error

type Error struct {
	Code    ErrorCode   `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

Error represents a JSON-RPC 2.0 Error object, which is used in the Response object.

func NewError

func NewError(code ErrorCode, message string, data interface{}) Error

NewError returns an Error with the given code, message, and data.

func NewInvalidParamsError

func NewInvalidParamsError(data interface{}) Error

NewInvalidParamsError returns an InvalidParams Error with the given data.

func (Error) Error

func (e Error) Error() string

Error implements the error interface.

type ErrorCode

type ErrorCode int

ErrorCode represent the int JSON RPC 2.0 error code.

func (ErrorCode) IsReserved

func (c ErrorCode) IsReserved() bool

IsReserved returns true if c is within the reserved error code range: [LowestReservedErrorCode, HighestReservedErrorCode].

type MethodFunc

type MethodFunc func(params json.RawMessage) interface{}

MethodFunc is the function signature used for RPC methods. The raw JSON of the params of a valid Request is passed to the MethodFunc for further application specific unmarshaling and validation. When a MethodFunc is invoked by the handler, the params json.RawMessage, if not nil, is guaranteed to be valid JSON representing a structured JSON type. If the "params" field was omitted or was null, then nil is passed to the MethodFunc.

A valid MethodFunc must return a not-nil interface{} that will not cause an error when passed to json.Marshal and will not marshal to null. If the underlying type of the returned interface{} is Error, then an Error Response will be returned to the client. Any return value that is not an Error will be used as the "result" field.

If the MethodFunc returns an Error, then the Error must either use the InvalidParamsCode, or it must use an Error.Code that is outside of the reserved error code range. See ErrorCode.IsReserved() for more information.

An invalid MethodFunc will result in an Internal Error to be returned to the client without revealing any information about the error. If you are getting InternalErrors from your MethodFunc, set DebugMethodFunc to true for additional debug output about the cause of the Internal Error.

Example (Panic)

Any panic will return InternalError to the user if the call was a request and not a Notification.

var _ jsonrpc2.MethodFunc = func(params json.RawMessage) interface{} {
	panic("don't worry, jsonrpc2 will recover you and return an internal error")
}
Output:

type MethodMap

type MethodMap map[string]MethodFunc

MethodMap associates method names with MethodFuncs and is passed to HTTPRequestHandler() to generate a corresponding http.HandlerFunc.

type Request

type Request struct {
	JSONRPC string      `json:"jsonrpc"`
	Method  string      `json:"method"`
	Params  interface{} `json:"params,omitempty"`
	ID      interface{} `json:"id,omitempty"`
}

Request represents a JSON-RPC 2.0 Request or Notification object.

Valid Requests must use a numeric or string type for the ID, and a structured type such as a slice, array, map, or struct for the Params.

Example

Use the http and json packages to send a Request object.

reqBytes, _ := json.Marshal(jsonrpc2.NewRequest("subtract", 0, []int{5, 1}))
httpResp, _ := http.Post("http://localhost:8888", "application/json", bytes.NewReader(reqBytes))
respBytes, _ := ioutil.ReadAll(httpResp.Body)
response := jsonrpc2.Response{}
json.Unmarshal(respBytes, &response)
Output:

func NewRequest

func NewRequest(method string, id, params interface{}) Request

NewRequest returns a new Request with the given method, id, and params. If nil id is provided, it is by definition a Notification object and will not receive a response.

func (Request) MarshalJSON

func (r Request) MarshalJSON() ([]byte, error)

MarshalJSON outputs a JSON RPC Request object with the "jsonrpc" field populated to Version ("2.0").

func (Request) String

func (r Request) String() string

String returns a JSON string with "--> " prefixed to represent a Request object.

type Response

type Response struct {
	JSONRPC string      `json:"jsonrpc"`
	Result  interface{} `json:"result,omitempty"`
	Error   *Error      `json:"error,omitempty"`
	ID      interface{} `json:"id"`
}

Response represents a JSON-RPC 2.0 Response object.

func NewResponse

func NewResponse(result interface{}) Response

NewResponse returns a Response with the given result as the Response.Result.

func (Response) IsValid

func (r Response) IsValid() bool

IsValid returns true if JSONRPC is equal to the Version ("2.0") and either Response.Result or Response.Error is not nil.

func (Response) MarshalJSON

func (r Response) MarshalJSON() ([]byte, error)

MarshalJSON outputs a JSON RPC Response object with the "jsonrpc" field populated to Version ("2.0").

func (Response) String

func (r Response) String() string

String returns a string of the JSON with "<-- " prefixed to represent a Response object.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL