jrpc2

package module
v0.12.1-0...-2ab8468 Latest Latest
Warning

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

Go to latest
Published: Apr 5, 2021 License: BSD-3-Clause Imports: 17 Imported by: 0

README

jrpc2

GoDoc Go Report Card

This repository provides Go package that implements a JSON-RPC 2.0 client and server. There is also a working example in the Go playground.

Packages

  • Package jrpc2 implements the base client and server.

  • Package channel defines the communication channel abstraction used by the server & client.

  • Package code defines standard error codes as defined by the JSON-RPC 2.0 protocol.

  • Package handler defines support for adapting functions to service methods.

  • Package jctx implements an encoder and decoder for request context values, allowing context metadata to be propagated through JSON-RPC requests.

  • Package jhttp allows clients and servers to use HTTP as a transport.

  • Package metrics defines a server metrics collector.

  • Package server provides support for running a server to handle multiple connections, and an in-memory implementation for testing.

Implementation Notes

The following describes some of the implementation choices made by this module.

Batch requests and error reporting

The JSON-RPC 2.0 spec is ambiguous about the semantics of batch requests. Specifically, the definition of notifications says:

A Notification is a Request object without an "id" member. ... The Server MUST NOT reply to a Notification, including those that are within a batch request.

Notifications are not confirmable by definition, since they do not have a Response object to be returned. As such, the Client would not be aware of any errors (like e.g. "Invalid params", "Internal error").

This conflicts with the definition of batch requests, which asserts:

A Response object SHOULD exist for each Request object, except that there SHOULD NOT be any Response objects for notifications. ... The Response objects being returned from a batch call MAY be returned in any order within the Array. ... If the batch rpc call itself fails to be recognized as an valid JSON or as an Array with at least one value, the response from the Server MUST be a single Response object.

and includes examples that contain request values with no ID (which are, perforce, notifications) and report errors back to the client. Since order may not be relied upon, and there are no IDs, the client cannot correctly match such responses back to their originating requests.

This implementation resolves the conflict in favour of the notification rules. Specifically:

  • If a batch is empty or not valid JSON, the server reports error -32700 (Invalid JSON) as a single error Response object.

  • Otherwise, parse or validation errors resulting from any batch member without an ID are mapped to error objects with a null ID, in the same position in the reply as the corresponding request. Preservation of order is not required by the specification, but it ensures the server has stable behaviour.

Because a server is allowed to reorder the results, a client should not depend on this implementation detail.

Non-standard server push

The specification defines client and server as follows:

The Client is defined as the origin of Request objects and the handler of Response objects. The Server is defined as the origin of Response objects and the handler of Request objects.

Although a client may also be a server, and vice versa, the specification does not require them to do so. The server notification support defined in the jrpc2 package is thus "non-standard" in that it allows the server to act as a client, and the client as a server, in the narrow context of "push" notifications and server callbacks. Otherwise the feature is not special: Requests sent by *jrpc2.Server.Notify and *jrpc2.Server.Callbackare standard Request objects.

Documentation

Overview

Package jrpc2 implements a server and a client for the JSON-RPC 2.0 protocol defined by http://www.jsonrpc.org/specification.

Servers

The *Server type implements a JSON-RPC server. A server communicates with a client over a channel.Channel, and dispatches client requests to user-defined method handlers. Handlers satisfy the jrpc2.Handler interface by exporting a Handle method with this signature:

Handle(ctx Context.Context, req *jrpc2.Request) (interface{}, error)

The handler package helps adapt existing functions to this interface. A server finds the handler for a request by looking up its method name in a jrpc2.Assigner provided when the server is set up.

For example, suppose we would like to export the following Add function as a JSON-RPC method:

// Add returns the sum of a slice of integers.
func Add(ctx context.Context, values []int) int {
   sum := 0
   for _, v := range values {
      sum += v
   }
   return sum
}

To convert Add to a jrpc2.Handler, call handler.New, which uses reflection to lift its argument into the jrpc2.Handler interface:

h := handler.New(Add)  // h is a jrpc2.Handler that invokes Add

We will advertise this function under the name "Add". For static assignments we can use a handler.Map, which finds methods by looking them up in a Go map:

assigner := handler.Map{
   "Add": handler.New(Add),
}

Equipped with an Assigner we can now construct a Server:

srv := jrpc2.NewServer(assigner, nil)  // nil for default options

To serve requests, we need a channel.Channel. Implementations of the Channel interface handle the framing, transmission, and receipt of JSON messages. The channel package provides several common framing disciplines and functions to wrap them around various input and output streams. For this example, we'll use a channel that delimits messages by newlines, and communicates on os.Stdin and os.Stdout:

ch := channel.Line(os.Stdin, os.Stdout)
srv.Start(ch)

Once started, the running server handles incoming requests until the channel closes, or until it is stopped explicitly by calling srv.Stop(). To wait for the server to finish, call:

err := srv.Wait()

This will report the error that led to the server exiting. The code for this example is availabe from cmd/examples/adder/adder.go:

$ go run cmd/examples/adder/adder.go

Interact with the server by sending JSON-RPC requests on stdin, such as for example:

{"jsonrpc":"2.0", "id":1, "method":"Add", "params":[1, 3, 5, 7]}

Clients

The *Client type implements a JSON-RPC client. A client communicates with a server over a channel.Channel, and is safe for concurrent use by multiple goroutines. It supports batched requests and may have arbitrarily many pending requests in flight simultaneously.

To create a client we need a channel:

import "net"

conn, err := net.Dial("tcp", "localhost:8080")
...
ch := channel.RawJSON(conn, conn)
cli := jrpc2.NewClient(ch, nil)  // nil for default options

To send a single RPC, use the Call method:

rsp, err := cli.Call(ctx, "Add", []int{1, 3, 5, 7})

Call blocks until the response is received. Any error returned by the server, including cancellation or deadline exceeded, has concrete type *jrpc2.Error.

To issue a batch of requests, use the Batch method:

rsps, err := cli.Batch(ctx, []jrpc2.Spec{
   {Method: "Math.Add", Params: []int{1, 2, 3}},
   {Method: "Math.Mul", Params: []int{4, 5, 6}},
   {Method: "Math.Max", Params: []int{-1, 5, 3, 0, 1}},
})

Batch blocks until all the responses are received. An error from the Batch call reflects an error in sending the request: The caller must check each response separately for errors from the server. Responses are returned in the same order as the Spec values, save that notifications are omitted.

To decode the result from a successful response use its UnmarshalResult method:

var result int
if err := rsp.UnmarshalResult(&result); err != nil {
   log.Fatalln("UnmarshalResult:", err)
}

To close a client and discard all its pending work, call cli.Close().

Notifications

The JSON-RPC protocol also supports notifications. Notifications differ from calls in that they are one-way: The client sends them to the server, but the server does not reply.

Use the Notify method of a jrpc2.Client to send notifications:

err := cli.Notify(ctx, "Alert", handler.Obj{
   "message": "A fire is burning!",
})

A notification is complete once it has been sent.

On server, notifications are handled identically to ordinary requests, except that the return value is discarded once the handler returns. If a handler does not want to do anything for a notification, it can query the request:

if req.IsNotification() {
   return 0, nil  // ignore notifications
}

Cancellation

The *jrpc2.Client and *jrpc2.Server types support a non-standard cancellation protocol, consisting of a notification method "rpc.cancel" taking an array of request IDs to be cancelled. The server cancels the context of each method handler whose ID is named.

When the context associated with a client request is cancelled, the client sends an "rpc.cancel" notification to the server for that request's ID. The "rpc.cancel" method is automatically handled (unless disabled) by the *jrpc2.Server implementation from this package.

Services with Multiple Methods

The example above shows a server with one method using handler.New. To simplify exporting multiple methods, the handler.NewService function applies handler.New to all the relevant exported methods of a concrete value, returning a handler.Map for those methods:

type math struct{}

func (math) Add(ctx context.Context, vals ...int) int { ... }
func (math) Mul(ctx context.Context, vals []int) int { ... }

assigner := handler.NewService(math{})

This assigner maps the name "Add" to the Add method, and the name "Mul" to the Mul method, of the math value.

This may be further combined with the handler.ServiceMap type to allow different services to work together:

type status struct{}

func (status) Get(context.Context) (string, error) {
   return "all is well", nil
}

assigner := handler.ServiceMap{
   "Math":   handler.NewService(math{}),
   "Status": handler.NewService(status{}),
}

This assigner dispatches "Math.Add" and "Math.Mul" to the math value's methods, and "Status.Get" to the status value's method. A ServiceMap splits the method name on the first period ("."), and you may nest ServiceMaps more deeply if you require a more complex hierarchy.

Concurrency

A Server issues requests to handlers concurrently, up to the Concurrency limit given in its ServerOptions. Two requests (either calls or notifications) are concurrent if they arrive as part of the same batch. In addition, two calls are concurrent if the time intervals between the arrival of the request objects and delivery of the response objects overlap.

The server may issue concurrent requests to their handlers in any order. Otherwise, requests are processed in order of arrival. Notifications, in particular, can only be concurrent with other notifications in the same batch. This ensures a client that sends a notification can be sure its notification was fully processed before any subsequent calls are issued.

These rules imply that the client cannot rely on the order of evaluation for calls that overlap: If the caller needs to ensure that call A completes before call B starts, it must wait for A to return before invoking B.

Non-Standard Extension Methods

By default, a *jrpc2.Server exports the following built-in non-standard extension methods:

rpc.serverInfo(null) ⇒ jrpc2.ServerInfo
Returns a jrpc2.ServerInfo value giving server metrics.

rpc.cancel([]int)  [notification]
Request cancellation of the specified in-flight request IDs.

The rpc.cancel method works only as a notification, and will report an error if called as an ordinary method.

These extension methods are enabled by default, but may be disabled by setting the DisableBuiltin server option to true when constructing the server.

Server Push

The AllowPush option in jrpc2.ServerOptions allows a server to "push" requests back to the client. This is a non-standard extension of JSON-RPC used by some applications such as the Language Server Protocol (LSP). If this feature is enabled, the server's Notify and Callback methods send requests back to the client. Otherwise, those methods will report an error:

if err := s.Notify(ctx, "methodName", params); err == jrpc2.ErrPushUnsupported {
  // server push is not enabled
}
if rsp, err := s.Callback(ctx, "methodName", params); err == jrpc2.ErrPushUnsupported {
  // server push is not enabled
}

A method handler may use jrpc2.PushNotify and jrpc2.PushCall functions to access these methods from its context.

On the client side, the OnNotify and OnCallback options in jrpc2.ClientOptions provide hooks to which any server requests are delivered, if they are set.

Index

Examples

Constants

View Source
const Version = "2.0"

Version is the version string for the JSON-RPC protocol understood by this implementation, defined at http://www.jsonrpc.org/specification.

Variables

View Source
var ErrConnClosed = errors.New("client connection is closed")

ErrConnClosed is returned by a server's push-to-client methods if they are called after the client connection is closed.

View Source
var ErrInvalidVersion = Errorf(code.InvalidRequest, "incorrect version marker")

ErrInvalidVersion is returned by ParseRequests if one or more of the requests in the input has a missing or invalid version marker.

View Source
var ErrNoData = errors.New("no data to unmarshal")

ErrNoData indicates that there are no data to unmarshal.

View Source
var ErrPushUnsupported = errors.New("server push is not enabled")

ErrPushUnsupported is returned by PushNotify and PushCall if server pushes are not enabled in the specified context.

Functions

func CancelRequest

func CancelRequest(ctx context.Context, id string)

CancelRequest requests the server associated with ctx to cancel the pending or in-flight request with the specified ID. If no request exists with that ID, this is a no-op without error. This function is for use by handlers, and will panic for a non-handler context.

func DataErrorf

func DataErrorf(code code.Code, v interface{}, msg string, args ...interface{}) error

DataErrorf returns an error value of concrete type *Error having the specified code, error data, and formatted message string. If v == nil this behaves identically to Errorf(code, msg, args...).

func Errorf

func Errorf(code code.Code, msg string, args ...interface{}) error

Errorf returns an error value of concrete type *Error having the specified code and formatted message string. It is shorthand for DataErrorf(code, nil, msg, args...)

func Network

func Network(s string) string

Network guesses a network type for the specified address. The assignment of a network type uses the following heuristics:

If s does not have the form [host]:port, the network is assigned as "unix". The network "unix" is also assigned if port == "", port contains characters other than ASCII letters, digits, and "-", or if host contains a "/".

Otherwise, the network is assigned as "tcp". Note that this function does not verify whether the address is lexically valid.

func PushNotify

func PushNotify(ctx context.Context, method string, params interface{}) error

PushNotify posts a server notification to the client. If the server does not have push enabled (via the AllowPush option), it reports ErrPushUnsupported. This function is for use by handlers, and will panic for a non-handler context.

func ServerMetrics

func ServerMetrics(ctx context.Context) *metrics.M

ServerMetrics returns the server metrics collector. If the server does not have a metrics collector, it returns nil, which is ready for use but discards all posted metrics. This function is for use by handlers, and will panic for a non-handler context.

func StrictFields

func StrictFields(v interface{}) interface{}

StrictFields wraps a value v to implement the DisallowUnknownFields method, requiring unknown fields to be rejected when unmarshaling from JSON.

For example:

var obj RequestType
err := req.UnmarshalParams(jrpc2.StrictFields(&obj))`

Types

type Assigner

type Assigner interface {
	// Assign returns the handler for the named method, or nil.
	Assign(ctx context.Context, method string) Handler

	// Names returns a slice of all known method names for the assigner.  The
	// resulting slice is ordered lexicographically and contains no duplicates.
	Names() []string
}

An Assigner assigns a Handler to handle the specified method name, or nil if no method is available to handle the request.

type Client

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

A Client is a JSON-RPC 2.0 client. The client sends requests and receives responses on a channel.Channel provided by the caller.

func NewClient

func NewClient(ch channel.Channel, opts *ClientOptions) *Client

NewClient returns a new client that communicates with the server via ch.

func (*Client) Batch

func (c *Client) Batch(ctx context.Context, specs []Spec) ([]*Response, error)

Batch initiates a batch of concurrent requests, and blocks until all the responses return. The responses are returned in the same order as the original specs, omitting notifications.

Any error returned is from sending the batch; the caller must check each response for errors from the server.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Elegant996/jrpc2"
	"github.com/Elegant996/jrpc2/channel"
)

var (
	ctx      = context.Background()
	sch, cch = channel.Direct()
	cli      = jrpc2.NewClient(cch, nil)
)

type Msg struct {
	Text string `json:"msg"`
}

func main() {
	// var cli = jrpc2.NewClient(cch, nil)
	rsps, err := cli.Batch(ctx, []jrpc2.Spec{
		{Method: "Hello"},
		{Method: "Log", Params: Msg{"Sing it!"}, Notify: true},
	})
	if err != nil {
		log.Fatalf("Batch: %v", err)
	}

	fmt.Printf("len(rsps) = %d\n", len(rsps))
	for i, rsp := range rsps {
		var msg string
		if err := rsp.UnmarshalResult(&msg); err != nil {
			log.Fatalf("Invalid result: %v", err)
		}
		fmt.Printf("Response #%d: %s\n", i+1, msg)
	}
}
Output:

Log: Sing it!
len(rsps) = 1
Response #1: Hello, world!

func (*Client) Call

func (c *Client) Call(ctx context.Context, method string, params interface{}) (*Response, error)

Call initiates a single request and blocks until the response returns. A successful call reports a nil error and a non-nil response. Errors from the server have concrete type *jrpc2.Error.

rsp, err := c.Call(ctx, method, params)
if e, ok := err.(*jrpc2.Error); ok {
   log.Fatalf("Error from server: %v", err)
} else if err != nil {
   log.Fatalf("Call failed: %v", err)
}
handleValidResponse(rsp)
Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Elegant996/jrpc2"
	"github.com/Elegant996/jrpc2/channel"
)

var (
	ctx      = context.Background()
	sch, cch = channel.Direct()
	cli      = jrpc2.NewClient(cch, nil)
)

func main() {
	// var cli = jrpc2.NewClient(cch, nil)
	rsp, err := cli.Call(ctx, "Hello", nil)
	if err != nil {
		log.Fatalf("Call: %v", err)
	}
	var msg string
	if err := rsp.UnmarshalResult(&msg); err != nil {
		log.Fatalf("Decoding result: %v", err)
	}
	fmt.Println(msg)
}
Output:

Hello, world!

func (*Client) CallResult

func (c *Client) CallResult(ctx context.Context, method string, params, result interface{}) error

CallResult invokes Call with the given method and params. If it succeeds, the result is decoded into result. This is a convenient shorthand for Call followed by UnmarshalResult. It will panic if result == nil.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Elegant996/jrpc2"
	"github.com/Elegant996/jrpc2/channel"
)

var (
	ctx      = context.Background()
	sch, cch = channel.Direct()
	cli      = jrpc2.NewClient(cch, nil)
)

func main() {
	// var cli = jrpc2.NewClient(cch, nil)
	var msg string
	if err := cli.CallResult(ctx, "Hello", nil, &msg); err != nil {
		log.Fatalf("CallResult: %v", err)
	}
	fmt.Println(msg)
}
Output:

Hello, world!

func (*Client) Close

func (c *Client) Close() error

Close shuts down the client, abandoning any pending in-flight requests.

func (*Client) Notify

func (c *Client) Notify(ctx context.Context, method string, params interface{}) error

Notify transmits a notification to the specified method and parameters. It blocks until the notification has been sent.

type ClientOptions

type ClientOptions struct {
	// If not nil, send debug logs here.
	Logger *log.Logger

	// Instructs the client to tolerate responses that do not include the
	// required "jsonrpc" version marker.
	AllowV1 bool

	// Instructs the client not to send rpc.cancel notifications to the server
	// when the context for an in-flight request terminates.
	DisableCancel bool

	// If set, this function is called with the context, method name, and
	// encoded request parameters before the request is sent to the server.
	// Its return value replaces the request parameters. This allows the client
	// to send context metadata along with the request. If unset, the parameters
	// are unchanged.
	EncodeContext func(context.Context, string, json.RawMessage) (json.RawMessage, error)

	// If set, this function is called if a notification is received from the
	// server. If unset, server notifications are logged and discarded.  At
	// most one invocation of the callback will be active at a time.
	// Server notifications are a non-standard extension of JSON-RPC.
	OnNotify func(*Request)

	// If set, this function is called if a request is received from the server.
	// If unset, server requests are logged and discarded. At most one
	// invocation of this callback will be active at a time.
	// Server callbacks are a non-standard extension of JSON-RPC.
	//
	// If a callback handler panics, the client will recover the panic and
	// report a system error back to the server describing the error.
	OnCallback func(context.Context, *Request) (interface{}, error)

	// If set, this function is called when the context for a request terminates.
	// The function receives the client and the response that was cancelled.
	// The hook can obtain the ID and error value from rsp.
	//
	// Setting this option disables the default rpc.cancel handling (as DisableCancel).
	// Note that the hook does not receive the client context, which has already
	// ended by the time the hook is called.
	OnCancel func(cli *Client, rsp *Response)
}

ClientOptions control the behaviour of a client created by NewClient. A nil *ClientOptions provides sensible defaults.

type Error

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

Error is the concrete type of errors returned from RPC calls.

func (Error) Code

func (e Error) Code() code.Code

Code returns the error code value associated with e.

func (Error) Error

func (e Error) Error() string

Error renders e to a human-readable string for the error interface.

func (Error) HasData

func (e Error) HasData() bool

HasData reports whether e has error data to unmarshal.

func (Error) MarshalJSON

func (e Error) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface for Error values.

func (Error) Message

func (e Error) Message() string

Message returns the message string associated with e.

func (Error) UnmarshalData

func (e Error) UnmarshalData(v interface{}) error

UnmarshalData decodes the error data associated with e into v. It returns ErrNoData without modifying v if there was no data message attached to e.

func (*Error) UnmarshalJSON

func (e *Error) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface for Error values.

type Handler

type Handler interface {
	// Handle invokes the method with the specified request. The response value
	// must be JSON-marshalable or nil. In case of error, the handler can
	// return a value of type *jrpc2.Error to control the response code sent
	// back to the caller; otherwise the server will wrap the resulting value.
	//
	// The context passed to the handler by a *jrpc2.Server includes three
	// special values that the handler may extract.
	//
	// To obtain the server instance running the handler, write:
	//
	//    srv := jrpc2.ServerFromContext(ctx)
	//
	// To obtain a server metrics value (see metrics.M), write:
	//
	//    sm := jrpc2.ServerMetrics(ctx)
	//
	// To obtain the inbound request message, write:
	//
	//    req := jrpc2.InboundRequest(ctx)
	//
	// The latter is primarily useful for handlers generated by handler.New,
	// which do not receive the request directly. For a handler that implements
	// the Handle method directly, req is the same value passed as a parameter
	// to Handle.
	Handle(context.Context, *Request) (interface{}, error)
}

A Handler handles a single request.

type RPCLogger

type RPCLogger interface {
	// Called for each request received prior to invoking its handler.
	LogRequest(ctx context.Context, req *Request)

	// Called for each response produced by a handler, immediately prior to
	// sending it back to the client. The inbound request can be recovered from
	// the context using jrpc2.InboundRequest.
	LogResponse(ctx context.Context, rsp *Response)
}

An RPCLogger receives callbacks from a server to record the receipt of requests and the delivery of responses. These callbacks are invoked synchronously with the processing of the request.

type Request

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

A Request is a request message from a client to a server.

func InboundRequest

func InboundRequest(ctx context.Context) *Request

InboundRequest returns the inbound request associated with the given context, or nil if ctx does not have an inbound request. The context passed to the handler by *jrpc2.Server will include this value.

This is mainly useful to wrapped server methods that do not have the request as an explicit parameter; for direct implementations of Handler.Handle the request value returned by InboundRequest will be the same value as was passed explicitly.

func ParseRequests

func ParseRequests(msg []byte) ([]*Request, error)

ParseRequests parses a single request or a batch of requests from JSON. The result parameters are either nil or have concrete type json.RawMessage.

If any of the requests is missing or has an invalid JSON-RPC version, it returns ErrInvalidVersion along with the parsed results. Otherwise, no validation apart from basic structure is performed on the results.

func (*Request) HasParams

func (r *Request) HasParams() bool

HasParams reports whether the request has non-empty parameters.

func (*Request) ID

func (r *Request) ID() string

ID returns the request identifier for r, or "" if r is a notification.

func (*Request) IsNotification

func (r *Request) IsNotification() bool

IsNotification reports whether the request is a notification, and thus does not require a value response.

func (*Request) Method

func (r *Request) Method() string

Method reports the method name for the request.

func (*Request) ParamString

func (r *Request) ParamString() string

ParamString returns the encoded request parameters of r as a string. If r has no parameters, it returns "".

func (*Request) UnmarshalParams

func (r *Request) UnmarshalParams(v interface{}) error

UnmarshalParams decodes the request parameters of r into v. If r has empty parameters, it returns nil without modifying v. If r is invalid it returns an InvalidParams error.

By default, unknown object keys are ignored when unmarshaling into a v of struct type. This can be overridden either by giving the type of v a custom implementation of json.Unmarshaler, or implementing a DisallowUnknownFields method. The jrpc2.StrictFields helper function adapts existing values to this interface.

If v has type *json.RawMessage, decoding cannot fail.

Example
package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/Elegant996/jrpc2"
	"github.com/Elegant996/jrpc2/code"
)

func main() {
	const msg = `{"jsonrpc":"2.0", "id":101, "method":"M", "params":{"a":1, "b":2, "c":3}}`

	reqs, err := jrpc2.ParseRequests([]byte(msg))
	if err != nil {
		log.Fatalf("ParseRequests: %v", err)
	}

	var t, u struct {
		A int `json:"a"`
		B int `json:"b"`
	}

	// By default, unmarshaling ignores unknown fields (here, "c").
	if err := reqs[0].UnmarshalParams(&t); err != nil {
		log.Fatalf("UnmarshalParams: %v", err)
	}

	// Solution 1: Use the jrpc2.StrictFields helper.
	err = reqs[0].UnmarshalParams(jrpc2.StrictFields(&t))
	if code.FromError(err) != code.InvalidParams {
		log.Fatalf("UnmarshalParams strict: %v", err)
	}
	fmt.Printf("t.A=%d, t.B=%d\n", t.A, t.B)

	// Solution 2: Unmarshal as json.RawMessage and decode separately.
	var tmp json.RawMessage
	reqs[0].UnmarshalParams(&tmp) // cannot fail
	if err := json.Unmarshal(tmp, &u); err != nil {
		log.Fatalf("Unmarshal: %v", err)
	}
	fmt.Printf("u.A=%d, u.B=%d\n", u.A, u.B)

}
Output:

t.A=1, t.B=2
u.A=1, u.B=2

type Response

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

A Response is a response message from a server to a client.

func PushCall

func PushCall(ctx context.Context, method string, params interface{}) (*Response, error)

PushCall posts a server call to the client. If the server does not have push enabled (via the AllowPush option), it reports ErrPushUnsupported. This function is for use by handlers, and will panic for a non-handler context.

A successful callback reports a nil error and a non-nil response. Errors reported by the client have concrete type *jrpc2.Error.

func (*Response) Error

func (r *Response) Error() *Error

Error returns a non-nil *Error if the response contains an error.

func (*Response) ID

func (r *Response) ID() string

ID returns the request identifier for r.

func (*Response) MarshalJSON

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

MarshalJSON converts the response to equivalent JSON.

func (*Response) ResultString

func (r *Response) ResultString() string

ResultString returns the encoded result message of r as a string. If r has no result, for example if r is an error response, it returns "".

func (*Response) SetID

func (r *Response) SetID(id string)

SetID sets the request identifier for r. This is for use in proxies.

func (*Response) UnmarshalResult

func (r *Response) UnmarshalResult(v interface{}) error

UnmarshalResult decodes the result message into v. If the request failed, UnmarshalResult returns the *Error value that would also be returned by r.Error(), and v is unmodified.

By default, unknown object keys are ignored when unmarshaling into a v of struct type. This can be overridden either by giving the type of v a custom implementation of json.Unmarshaler, or implementing a DisallowUnknownFields method. The jrpc2.StrictFields helper function adapts existing values to this interface.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Elegant996/jrpc2"
	"github.com/Elegant996/jrpc2/channel"
	"github.com/Elegant996/jrpc2/handler"
)

var (
	ctx      = context.Background()
	sch, cch = channel.Direct()
	cli      = jrpc2.NewClient(cch, nil)
)

func main() {
	// var cli = jrpc2.NewClient(cch, nil)
	rsp, err := cli.Call(ctx, "Echo", []string{"alpha", "oscar", "kilo"})
	if err != nil {
		log.Fatalf("Call: %v", err)
	}
	var r1, r3 string

	// Note the nil, which tells the decoder to skip that argument.
	if err := rsp.UnmarshalResult(&handler.Args{&r1, nil, &r3}); err != nil {
		log.Fatalf("Decoding result: %v", err)
	}
	fmt.Println(r1, r3)
}
Output:

alpha kilo

type Server

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

A Server is a JSON-RPC 2.0 server. The server receives requests and sends responses on a channel.Channel provided by the caller, and dispatches requests to user-defined Handlers.

func NewServer

func NewServer(mux Assigner, opts *ServerOptions) *Server

NewServer returns a new unstarted server that will dispatch incoming JSON-RPC requests according to mux. To start serving, call Start.

N.B. It is only safe to modify mux after the server has been started if mux itself is safe for concurrent use by multiple goroutines.

This function will panic if mux == nil.

Example
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/Elegant996/jrpc2"
	"github.com/Elegant996/jrpc2/channel"
	"github.com/Elegant996/jrpc2/handler"
)

var (
	s *jrpc2.Server

	sch, cch = channel.Direct()
)

type Msg struct {
	Text string `json:"msg"`
}

func main() {
	// Construct a new server with methods "Hello" and "Log".
	s = jrpc2.NewServer(handler.Map{
		"Hello": handler.New(func(ctx context.Context) string {
			return "Hello, world!"
		}),
		"Echo": handler.New(func(_ context.Context, args []json.RawMessage) []json.RawMessage {
			return args
		}),
		"Log": handler.New(func(ctx context.Context, msg Msg) (bool, error) {
			fmt.Println("Log:", msg.Text)
			return true, nil
		}),
	}, nil).Start(sch)

	// We can query the server for its current status information, including a
	// list of its methods.
	si := s.ServerInfo()

	fmt.Println(strings.Join(si.Methods, "\n"))
}
Output:

Echo
Hello
Log

func ServerFromContext

func ServerFromContext(ctx context.Context) *Server

ServerFromContext returns the server associated with the given context. This will be populated on the context passed to request handlers. This function is for use by handlers, and will panic for a non-handler context.

It is safe to retain the server and invoke its methods beyond the lifetime of the context from which it was extracted; however, a handler must not block on the Wait or WaitStatus methods of the server, as the server will deadlock waiting for the handler to return.

func (*Server) Callback

func (s *Server) Callback(ctx context.Context, method string, params interface{}) (*Response, error)

Callback posts a single server-side call to the client. It blocks until a reply is received or the client connection terminates. A successful callback reports a nil error and a non-nil response. Errors returned by the client have concrete type *jrpc2.Error.

This is a non-standard extension of JSON-RPC, and may not be supported by all clients. Unless s was constructed with the AllowPush option set true, this method will always report an error (ErrPushUnsupported) without sending anything. If Callback is called after the client connection is closed, it returns ErrConnClosed.

func (*Server) CancelRequest

func (s *Server) CancelRequest(id string)

CancelRequest instructs s to cancel the pending or in-flight request with the specified ID. If no request exists with that ID, this is a no-op.

func (*Server) Notify

func (s *Server) Notify(ctx context.Context, method string, params interface{}) error

Notify posts a single server-side notification to the client.

This is a non-standard extension of JSON-RPC, and may not be supported by all clients. Unless s was constructed with the AllowPush option set true, this method will always report an error (ErrPushUnsupported) without sending anything. If Notify is called after the client connection is closed, it returns ErrConnClosed.

func (*Server) ServerInfo

func (s *Server) ServerInfo() *ServerInfo

ServerInfo returns an atomic snapshot of the current server info for s.

func (*Server) Start

func (s *Server) Start(c channel.Channel) *Server

Start enables processing of requests from c. This function will panic if the server is already running.

func (*Server) Stop

func (s *Server) Stop()

Stop shuts down the server. It is safe to call this method multiple times or from concurrent goroutines; it will only take effect once.

func (*Server) Wait

func (s *Server) Wait() error

Wait blocks until the server terminates and returns the resulting error. It is equivalent to s.WaitStatus().Err.

func (*Server) WaitStatus

func (s *Server) WaitStatus() ServerStatus

WaitStatus blocks until the server terminates, and returns the resulting status. After WaitStatus returns, whether or not there was an error, it is safe to call s.Start again to restart the server with a fresh channel.

type ServerInfo

type ServerInfo struct {
	// The list of method names exported by this server.
	Methods []string `json:"methods,omitempty"`

	// Whether this server understands context wrappers.
	UsesContext bool `json:"usesContext"`

	// Metric values defined by the evaluation of methods.
	Counter  map[string]int64       `json:"counters,omitempty"`
	MaxValue map[string]int64       `json:"maxValue,omitempty"`
	Label    map[string]interface{} `json:"labels,omitempty"`

	// When the server started.
	StartTime time.Time `json:"startTime,omitempty"`
}

ServerInfo is the concrete type of responses from the rpc.serverInfo method.

func RPCServerInfo

func RPCServerInfo(ctx context.Context, cli *Client) (result *ServerInfo, err error)

RPCServerInfo calls the built-in rpc.serverInfo method exported by servers. It is a convenience wrapper for an invocation of cli.CallResult.

type ServerOptions

type ServerOptions struct {
	// If not nil, send debug logs here.
	Logger *log.Logger

	// If not nil, the methods of this value are called to log each request
	// received and each response or error returned.
	RPCLog RPCLogger

	// Instructs the server to tolerate requests that do not include the
	// required "jsonrpc" version marker.
	AllowV1 bool

	// Instructs the server to allow server callbacks and notifications, a
	// non-standard extension to the JSON-RPC protocol. If AllowPush is false,
	// the Notify and Callback methods of the server report errors if called.
	AllowPush bool

	// Instructs the server to disable the built-in rpc.* handler methods.
	//
	// By default, a server reserves all rpc.* methods, even if the given
	// assigner maps them. When this option is true, rpc.* methods are passed
	// along to the given assigner.
	DisableBuiltin bool

	// Allows up to the specified number of goroutines to execute concurrently
	// in request handlers. A value less than 1 uses runtime.NumCPU().  Note
	// that this setting does not constrain order of issue.
	Concurrency int

	// If set, this function is called with the method name and encoded request
	// parameters received from the client, before they are delivered to the
	// handler. Its return value replaces the context and argument values. This
	// allows the server to decode context metadata sent by the client.
	// If unset, ctx and params are used as given.
	DecodeContext func(context.Context, string, json.RawMessage) (context.Context, json.RawMessage, error)

	// If set, this function is called with the context and the client request
	// to be delivered to the handler. If CheckRequest reports a non-nil error,
	// the request fails with that error without invoking the handler.
	CheckRequest func(ctx context.Context, req *Request) error

	// If set, use this value to record server metrics. All servers created
	// from the same options will share the same metrics collector.  If none is
	// set, an empty collector will be created for each new server.
	Metrics *metrics.M

	// If nonzero this value as the server start time; otherwise, use the
	// current time when Start is called.
	StartTime time.Time
}

ServerOptions control the behaviour of a server created by NewServer. A nil *ServerOptions provides sensible defaults.

type ServerStatus

type ServerStatus struct {
	Err error // the error that caused the server to stop (nil on success)
	// contains filtered or unexported fields
}

ServerStatus describes the status of a stopped server.

func (ServerStatus) Closed

func (s ServerStatus) Closed() bool

Closed reports whether the server exited due to a channel close.

func (ServerStatus) Stopped

func (s ServerStatus) Stopped() bool

Stopped reports whether the server exited due to Stop being called.

func (ServerStatus) Success

func (s ServerStatus) Success() bool

Success reports whether the server exited without error.

type Spec

type Spec struct {
	Method string
	Params interface{}
	Notify bool
}

A Spec combines a method name and parameter value. If the Notify field is true, the spec is sent as a notification instead of a request.

Directories

Path Synopsis
Package channel defines a communications channel that can encode/transmit and decode/receive data records with a configurable framing discipline, and provides some simple framing implementations.
Package channel defines a communications channel that can encode/transmit and decode/receive data records with a configurable framing discipline, and provides some simple framing implementations.
chanutil
Package chanutil exports helper functions for working with channels and framing defined by the github.com/Elegant996/jrpc2/channel package.
Package chanutil exports helper functions for working with channels and framing defined by the github.com/Elegant996/jrpc2/channel package.
cmd
examples/adder
Program adder demonstrates a trivial JSON-RPC server that communicates over the process's stdin and stdout.
Program adder demonstrates a trivial JSON-RPC server that communicates over the process's stdin and stdout.
examples/client
Program client demonstrates how to set up a JSON-RPC 2.0 client using the github.com/Elegant996/jrpc2 package.
Program client demonstrates how to set up a JSON-RPC 2.0 client using the github.com/Elegant996/jrpc2 package.
examples/http
Program http demonstrates how to set up a JSON-RPC 2.0 server using the github.com/Elegant996/jrpc2 package with an HTTP transport.
Program http demonstrates how to set up a JSON-RPC 2.0 server using the github.com/Elegant996/jrpc2 package with an HTTP transport.
examples/server
Program server demonstrates how to set up a JSON-RPC 2.0 server using the github.com/Elegant996/jrpc2 package.
Program server demonstrates how to set up a JSON-RPC 2.0 server using the github.com/Elegant996/jrpc2 package.
jcall
Program jcall issues RPC calls to a JSON-RPC server.
Program jcall issues RPC calls to a JSON-RPC server.
Package code defines error code values used by the jrpc2 package.
Package code defines error code values used by the jrpc2 package.
Package handler provides implementations of the jrpc2.Assigner interface, and support for adapting functions to the jrpc2.Handler interface.
Package handler provides implementations of the jrpc2.Assigner interface, and support for adapting functions to the jrpc2.Handler interface.
Package jctx implements an encoder and decoder for request context values, allowing context metadata to be propagated through JSON-RPC.
Package jctx implements an encoder and decoder for request context values, allowing context metadata to be propagated through JSON-RPC.
Package jhttp implements a bridge from HTTP to JSON-RPC.
Package jhttp implements a bridge from HTTP to JSON-RPC.
Package metrics defines a concurrently-accessible metrics collector.
Package metrics defines a concurrently-accessible metrics collector.
Package server provides support routines for running jrpc2 servers.
Package server provides support routines for running jrpc2 servers.

Jump to

Keyboard shortcuts

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