jsonrpc2

package module
v0.1.21 Latest Latest
Warning

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

Go to latest
Published: Jun 15, 2020 License: MPL-2.0 Imports: 12 Imported by: 4

README

JSON-RPC 2 Go supporting library

Documentation license donate Download

The library aims to bring JSON-RPC 2.0 support to Go. Goals:

  • Type safe by code-generation
  • Reasonable good performance
  • Clean, extendable and easy-to use interface
  • Protocol-agnostic solution with adapters for common-cases (HTTP, TCP, etc...)

Installation

  • (recommended) look at releases page and download
  • build from source go get -v github.com/reddec/jsonrpc2/cmd/...
  • From bintray repository for most debian-based distribution (trusty, xenial, bionic, buster, wheezy):
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 379CE192D401AB61
echo "deb https://dl.bintray.com/reddec/debian {distribution} main" | sudo tee -a /etc/apt/sources.list
sudo apt install jsonrpc2

Build requirements

  • go 1.13+

Usage as library

Please see package documentation

Usage as CLI for type-safe generation

Usage:
  jsonrpc2-gen [OPTIONS]

Generate tiny wrapper for JSON-RPC router
Author: Baryshnikov Aleksandr <dev@baryshnikov.net>
Version: dev

Application Options:
  -i, --file=                                File to scan [$GOFILE]
  -I, --interface=                           Interface to wrap [$INTERFACE]
      --namespace=                           Custom namespace for functions. If not defined - interface name will be used [$NAMESPACE]
  -w, --wrapper=                             Wrapper function name. If not defined - Register<interface> name will be used [$WRAPPER]
  -o, --output=                              Generated output destination (- means STDOUT) (default: -) [$OUTPUT]
  -p, --package=                             Package name (can be override by output dir) (default: events) [$PACKAGE]
  -d, --doc=                                 Generate markdown documentation [$DOC]
  -c, --case=[keep|camel|pascal|snake|kebab] Method name case style (default: keep) [$CASE]
      --url=                                 URL for examples in documentation (default: https://example.com/api) [$URL]
  -C, --interceptor                          add interceptor for each method [$INTERCEPTOR]

Help Options:
  -h, --help                                 Show this help message

Example

Assume you have an interface file (user.go) like this:

package abc

// General user profile access
type User interface {
	// Get user profile
	Profile(token string) (*Profile, error)
}

Just invoke jsonrpc2-gen -i user.go -o user_gen.go -I User -p abc

You will get user_gen.go file like that:

// Code generated by jsonrpc2-gen. DO NOT EDIT.
//go:generate jsonrpc2-gen -i user.go -o user_gen.go -I User -p abc
package abc

import (
	"encoding/json"
	jsonrpc2 "github.com/reddec/jsonrpc2"
)

func RegisterUser(router *jsonrpc2.Router, wrap User) []string {
	router.RegisterFunc("User.Profile", func(params json.RawMessage, positional bool) (interface{}, error) {
		var args struct {
			Arg0 string `json:"token"`
		}
		var err error
		if positional {
			err = jsonrpc2.UnmarshalArray(params, &args.Arg0)
		} else {
			err = json.Unmarshal(params, &args)
		}
		if err != nil {
			return nil, err
		}
		return wrap.Profile(args.Arg0)
	})

	return []string{"User.Profile"}
}
Generate documentation

Add -doc user.md to generate documentations as described bellow. It will be generated and saved to the provided file (user.md)

# User

General user profile access


## User.Profile

Get user profile

* Method: `User.Profile`
* Returns: `*Profile`
* Arguments:

| Position | Name | Type |
|----------|------|------|
| 0 | token | `string` |

KTOR (kotlin) generator

  • Supports time/Time
  • (TBD) time/Duration

Gradle requirements


def ktor_version = '1.3.1'

repositories {
    mavenCentral()
}

dependencies {
    implementation("io.ktor:ktor-client-cio:$ktor_version") // <-- you can choose another
    implementation("io.ktor:ktor-client-gson:$ktor_version")
}

Documentation

Overview

JSON-RPC 2.0 supporting library

Main object - Router doesn't need any kind of initialization. Just use `var router Router`.

There are two ways how to register service: dynamical and static.

Static

This is recommended way. jsonrpc2-gen tool will generate type-safe wrapper with positional and named arguments support.

Tool can be installed by `go get -v github.com/reddec/jsonrpc2/cmd/...` or by other method (see README.md)

For example:

Assume you have an interface file (`user.go`) like this:

package abc

type User interface {
	Profile(token string) (*Profile, error)
}

Just invoke `jsonrpc2-gen -i user.go -o user_gen.go -I User -p abc`

You will get `user_gen.go` file like that:

// Code generated by jsonrpc2-gen. DO NOT EDIT.
//go:generate jsonrpc2-gen -i user.go -o user_gen.go -I User -p abc
package abc

import (
	"encoding/json"
	jsonrpc2 "github.com/reddec/jsonrpc2"
)

func RegisterUser(router *jsonrpc2.Router, wrap User) []string {
	router.RegisterFunc("User.Profile", func(params json.RawMessage, positional bool) (interface{}, error) {
		var args struct {
			Arg0 string `json:"token"`
		}
		var err error
		if positional {
			err = jsonrpc2.UnmarshalArray(params, &args.Arg0)
		} else {
			err = json.Unmarshal(params, &args)
		}
		if err != nil {
			return nil, err
		}
		return wrap.Profile(args.Arg0)
	})

	return []string{"User.Profile"}
}

Dynamic

By using RegisterPositionalOnly or RegisterNamedOnly. This two functions are heavily relying on reflection so don't use in a high-load environment.

HTTP expose

Helper `Handler` can expose JSON-RPC over HTTP with supported methods POST, PUT, PATCH. For other methods server will return MethodNotAllowed (405)

Index

Examples

Constants

View Source
const (
	Version        = "2.0"
	AppError       = -30000
	ParseError     = -32700
	InvalidRequest = -32600
	MethodNotFound = -32601
	InvalidParams  = -32602
	InternalError  = -32603
)

Variables

This section is empty.

Functions

func Function

func Function(handler interface{}) (*callableWrapper, error)

Wrap function as JSON-RPC method for usage in router

This kind of wrapper support only positional arguments

func Handler

func Handler(router *Router) http.HandlerFunc

Expose JSON-RPC route over HTTP Rest (POST) and web sockets (GET)

Example
var router Router
router.RegisterPositionalOnly("sum", func(ctx context.Context, a, b int) (int, error) {
	return a + b, nil
})
http.ListenAndServe(":8080", Handler(&router))
Output:

func HandlerContext added in v0.1.18

func HandlerContext(ctx context.Context, router *Router) http.HandlerFunc

func HandlerRest added in v0.1.9

func HandlerRest(router *Router) http.HandlerFunc

Expose JSON-RPC router as HTTP handler where one requests is one execution. Supported methods: POST, PUT, PATCH Expose JSON-RPC router as HTTP handler where one requests is one execution. Supported methods: POST, PUT, PATCH

func HandlerRestContext added in v0.1.18

func HandlerRestContext(ctx context.Context, router *Router) http.HandlerFunc

func HandlerWS added in v0.1.9

func HandlerWS(router *Router) http.HandlerFunc

Process requests over web socket (all requests are processing in parallel in a separate go-routine)

func HandlerWSContext added in v0.1.18

func HandlerWSContext(ctx context.Context, router *Router) http.HandlerFunc

func RPCLike

func RPCLike(handler interface{}) (*rpcLikeCallable, error)

Expose function handler where first argument is pointer to structure and returns are payload with error.

This kind of wrapper support only named arguments

func ToArray

func ToArray(params json.RawMessage, expected int) ([]json.RawMessage, error)

func UnmarshalArray

func UnmarshalArray(params json.RawMessage, args ...interface{}) error

Types

type Error

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

JSON-RPC 2.0 standard error object

func (*Error) Error added in v0.1.2

func (e *Error) Error() string

type GlobalInterceptorContext added in v0.1.3

type GlobalInterceptorContext struct {
	Requests []*Request
	Context  context.Context
	IsBatch  bool
	// contains filtered or unexported fields
}

func (*GlobalInterceptorContext) Next added in v0.1.3

func (gic *GlobalInterceptorContext) Next() (responses []*Response, isBatch bool)

type GlobalInterceptorFunc added in v0.1.3

type GlobalInterceptorFunc func(gic *GlobalInterceptorContext) (responses []*Response, isBatch bool)

func MaxBatch added in v0.1.3

func MaxBatch(num int) GlobalInterceptorFunc

Interceptor that limiting maximum number of requests in a batch. If batch size is bigger - InternalError will be returned with description. Only one error response without ID will be generated regardless of batch size

type Method

type Method interface {
	JsonCall(ctx context.Context, params json.RawMessage, positional bool) (interface{}, error)
}

Method handler (for low-level implementation). Should support params as object or as array (positional=true).

Returned data should be JSON serializable and not nil for success

type MethodFunc

type MethodFunc func(ctx context.Context, params json.RawMessage, positional bool) (interface{}, error)

func (MethodFunc) JsonCall

func (m MethodFunc) JsonCall(ctx context.Context, params json.RawMessage, positional bool) (interface{}, error)

type MethodInterceptorContext added in v0.1.3

type MethodInterceptorContext struct {
	Request      *Request
	IsPositional bool
	Context      context.Context
	// contains filtered or unexported fields
}

Context handling request per method

func (*MethodInterceptorContext) Next added in v0.1.3

func (ic *MethodInterceptorContext) Next() (interface{}, error)

Call next interceptor or final method

type MethodInterceptorFunc added in v0.1.2

type MethodInterceptorFunc func(ic *MethodInterceptorContext) (interface{}, error)

Interceptor for each method that will be called

type Request

type Request struct {
	// always 2.0 (will refuse if not)
	Version string `json:"jsonrpc"`
	// case-sensitive method name
	Method string `json:"method"`
	// any kind of valid JSON as ID (more relaxed comparing to for spec)
	ID json.RawMessage `json:"id"`
	// array (for positional) or object (for named) of arguments
	Params json.RawMessage `json:"params"`
}

Standard JSON-RPC 2.0 request messages

func (*Request) IsNotification

func (rq *Request) IsNotification() bool

Check request is notification (null ID)

func (*Request) IsValid

func (rq *Request) IsValid() bool

Base checks against specification

type Response

type Response struct {
	// always 2.0
	Version string `json:"jsonrpc"`
	// any kind of valid JSON as ID (more relaxed comparing to for spec) copied from request
	ID json.RawMessage `json:"id"`
	// result if exists
	Result interface{} `json:"result,omitempty"`
	// error if exists
	Error *Error `json:"error,omitempty"`
}

JSON-RPC 2.0 standard response object

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router for JSON-RPC requests.

Supports batching.

func (*Router) Intercept added in v0.1.3

func (caller *Router) Intercept(handler GlobalInterceptorFunc) *Router

Add interceptor for handling batch before lookup for methods and execution

func (*Router) InterceptMethods added in v0.1.2

func (caller *Router) InterceptMethods(handler MethodInterceptorFunc) *Router

Add interceptor for handling all methods invoke. Called in a same thread as method

func (*Router) Invoke

func (caller *Router) Invoke(stream io.Reader) (responses []*Response, isBatch bool)

Invoke exposed method using request from stream (as a batch or single) with background context

func (*Router) InvokeContext added in v0.1.18

func (caller *Router) InvokeContext(ctx context.Context, stream io.Reader) (responses []*Response, isBatch bool)

Invoke exposed method using request from stream (as a batch or single) with custom context

func (*Router) Register

func (caller *Router) Register(method string, handler Method) *Router

Register method to router to expose over JSON-RPC interface

func (*Router) RegisterFunc

func (caller *Router) RegisterFunc(method string, handlerFunc MethodFunc) *Router

Register function as method to expose over JSON-RPC

func (*Router) RegisterNamedOnly

func (caller *Router) RegisterNamedOnly(method string, handler interface{}) error

Register function as exposed method. Function handler must have first argument is pointer to structure and must return payload and error.

This kind of wrapper supports only named arguments.

Example
type Args struct {
	A int `json:"a"`
	B int `json:"b"`
}
router := &Router{}
err := router.RegisterNamedOnly("sum", func(ctx context.Context, params *Args) (int, error) {
	return params.A + params.B, nil
})
if err != nil {
	panic(err)
}
Output:

func (*Router) RegisterPositionalOnly

func (caller *Router) RegisterPositionalOnly(method string, handler interface{}) error

Register function as exposed method. Handler must return two values, last of them - error.

For such methods only positional arguments supported.

Example
router := &Router{}
err := router.RegisterPositionalOnly("sum", func(ctx context.Context, a, b int) (int, error) {
	return a + b, nil
})
if err != nil {
	panic(err)
}
Output:

Directories

Path Synopsis
cmd
jsonrpc2-gen/internal
Package internal generated by go-bindata.// sources: template.gotemplate python.gotemplate js.gotemplate ts.gotemplate method_doc.gotemplate ktor.gotemplate
Package internal generated by go-bindata.// sources: template.gotemplate python.gotemplate js.gotemplate ts.gotemplate method_doc.gotemplate ktor.gotemplate

Jump to

Keyboard shortcuts

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