jsonrpc2: github.com/AdamSLevy/jsonrpc2 Index | Examples | Files

package jsonrpc2

import "github.com/AdamSLevy/jsonrpc2"

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.InvalidParams("no params accepted")
	}
	return "0.0.0"
}
var methods = jsonrpc2.MethodMap{"version": versionMethod}
func StartServer() {
        http.ListenAndServe(":8080", jsonrpc2.HTTPRequestHandler(methods))
}

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

Code:

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")

}

Index

Examples

Package Files

client.go doc.go error.go errorcode.go handler.go methods.go request.go response.go

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.

func HTTPRequestHandler Uses

func HTTPRequestHandler(methods MethodMap) http.HandlerFunc

HTTPRequestHandler returns an http.HandlerFunc for the given methods.

type BatchRequest Uses

type BatchRequest []Request

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

func (BatchRequest) String Uses

func (br BatchRequest) String() string

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

type BatchResponse Uses

type BatchResponse []Response

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

func (BatchResponse) String Uses

func (br BatchResponse) String() string

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

type Client Uses

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 Uses

func NewClient(doer RequestDoer) *Client

NewClient returns a newly initialized Client. If doer is nil, then &http.Client{} is used.

func (*Client) Request Uses

func (c *Client) Request(url, method string, params, result interface{}) error

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 Uses

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 Uses

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.

func NewError Uses

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

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

func (Error) Error Uses

func (e Error) Error() string

Error implements the error interface.

type ErrorCode Uses

type ErrorCode int

ErrorCode represent the int JSON RPC 2.0 error code.

func (ErrorCode) IsReserved Uses

func (c ErrorCode) IsReserved() bool

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

type Logger Uses

type Logger interface {
    Println(...interface{})
}

Logger allows custom log types to be used with the Client when Client.DebugRequest is true.

type MethodFunc Uses

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.

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

Code:

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

type MethodMap Uses

type MethodMap map[string]MethodFunc

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

type Request Uses

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.

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

Code:

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)

func NewRequest Uses

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 Uses

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 Uses

func (r Request) String() string

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

type RequestDoer Uses

type RequestDoer interface {
    Do(req *http.Request) (*http.Response, error)
}

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 Uses

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 Uses

func (r Response) IsValid() bool

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 Uses

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 Uses

func (r Response) String() string

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

Package jsonrpc2 imports 9 packages (graph). Updated 2019-09-13. Refresh now. Tools for package owners.