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.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 ¶
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
const Version = "2.0"
Version is the valid version string for the "jsonrpc" field required in all JSON RPC 2.0 objects.
Variables ¶
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
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 NewInvalidParamsError ¶
func NewInvalidParamsError(data interface{}) Error
NewInvalidParamsError returns an InvalidParams Error with the given data.
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 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 ¶
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 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 ¶
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 ¶
MarshalJSON outputs a JSON RPC Response object with the "jsonrpc" field populated to Version ("2.0").