Documentation ¶
Overview ¶
Package jsonrpc2 is a complete and strictly conforming implementation of the JSON-RPC 2.0 protocol for both clients and servers.
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.InvalidParams("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/v12" ) 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.InvalidParams("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.InvalidParams(`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.InvalidParams(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","data":{"method":"foobar"}},"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","data":"json: cannot unmarshal number into Go struct field Request.method of type string"},"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","data":"empty batch request"},"id":null} rpc call with an invalid Batch (but not empty): --> [1] <-- [ {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request","data":"json: cannot unmarshal number into Go value of type jsonrpc2.Request"},"id":null} ] rpc call with invalid Batch: --> [1,2,3] <-- [ {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request","data":"json: cannot unmarshal number into Go value of type jsonrpc2.Request"},"id":null}, {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request","data":"json: cannot unmarshal number into Go value of type jsonrpc2.Request"},"id":null}, {"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request","data":"json: cannot unmarshal number into Go value of type jsonrpc2.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","data":"json: unknown field \"foo\""},"id":null}, {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found","data":{"method":"foo.get"}},"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 ¶
const ( // MinReservedErrorCode is the minimum reserved error code. Method // defined errors may be less than this value. MinReservedErrorCode ErrorCode = -32768 // ParseErrorCode is returned to the client when invalid JSON was // received by the server. An error occurred on the server while // parsing the JSON text. ParseErrorCode ErrorCode = -32700 ParseErrorMessage = "Parse error" // InvalidRequestCode is returned to the client when the JSON sent is // not a valid Request object. InvalidRequestCode ErrorCode = -32600 InvalidRequestMessage = "Invalid Request" // MethodNotFoundCode is returned to the client when the method does // not exist / is not available. MethodNotFoundCode ErrorCode = -32601 MethodNotFoundMessage = "Method not found" // InvalidParamsCode is returned to the client if a method is called // with invalid method parameter(s). MethodFuncs are responsible for // detecting and returning this error. InvalidParamsCode ErrorCode = -32602 InvalidParamsMessage = "Invalid params" // InternalErrorCode is returned to the client if an internal error // occurs such as a MethodFunc panic. InternalErrorCode ErrorCode = -32603 InternalErrorMessage = "Internal error" // MaxReservedErrorCode is the maximum reserved error code. Method // defined errors may be greater than this value. MaxReservedErrorCode ErrorCode = -32000 )
Official JSON-RPC 2.0 Spec Error Codes and Messages
const Version = "2.0"
Version is the valid version string for the "jsonrpc" field required in all JSON RPC 2.0 objects.
Variables ¶
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 Client ¶
type Client struct { RequestDoer DebugRequest bool Log Logger BasicAuth bool User string Password string Header http.Header }
Client embeds http.Client and provides a convenient way to make JSON-RPC requests.
func NewClient ¶
func NewClient(doer RequestDoer) *Client
NewClient returns a newly initialized Client. If doer is nil, then &http.Client{} is used.
func (*Client) Request ¶
Request uses c to make a JSON-RPC 2.0 Request to url with the given method and params, and then parses the Response using the provided result for Response.Result. Thus, result must be a pointer in order for json.Unmarshal to populate it. If Request returns nil, then the request and RPC method call were successful and result will be populated, if applicable. If the request is successful but the RPC method returns an Error Response, then Request will return the Error, which can be checked for by attempting a type assertion on the returned error.
Request uses a pseudorandom uint32 for the Request.ID.
Requests will have the "Content-Type":"application/json" header added.
Any populated c.Header will then be added to the http.Request, so you may override the "Content-Type" header with your own.
If c.BasicAuth is true then http.Request.SetBasicAuth(c.User, c.Password) will be called. This will override the same header in c.Header.
If c.DebugRequest is true then the Request and Response will be printed to stdout.
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 InvalidParams ¶
func InvalidParams(data interface{}) *Error
InvalidParams returns a pointer to a new Error initialized with the InvalidParamsCode and InvalidParamsMessage and the user provided data. MethodFuncs are responsible for detecting and returning this error.
type ErrorCode ¶
type ErrorCode int
ErrorCode represent the int JSON RPC 2.0 error code.
func (ErrorCode) IsReserved ¶
IsReserved returns true if c is within the reserved error code range: [LowestReservedErrorCode, HighestReservedErrorCode].
type Logger ¶
type Logger interface {
Println(...interface{})
}
Logger allows custom log types to be used with the Client when Client.DebugRequest is true.
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. If the underlying type of the returned interface{} is Error or *Error, then an Error Response will be returned to the client. If the underlying type of the returned interface{} is any other generic error, than an Internal Error 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 or *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 ¶
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 ¶
MarshalJSON outputs a JSON RPC Request object with the "jsonrpc" field populated to Version ("2.0").
type RequestDoer ¶
RequestDoer is implemented by *http.Client and many other Client types are easily adapted to match this interface. This allows a custom HTTP Client type to be used with Client.
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 (Response) IsValid ¶
IsValid returns true if JSONRPC is equal to the Version ("2.0") and one of Response.Result or Response.Error is not nil, but not both.
func (Response) MarshalJSON ¶
MarshalJSON outputs a JSON RPC Response object with the "jsonrpc" field populated to Version ("2.0").