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 jrpc2.NewHandler function 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 have defined the following Add function, and would like to export it as a JSON-RPC method:
// Add returns the sum of a slice of integers. func Add(ctx context.Context, values []int) (int, error) { sum := 0 for _, v := range values { sum += v } return sum, nil }
To convert Add to a jrpc2.Handler, call the jrpc2.NewHandler function, which uses reflection to lift its argument into the jrpc2.Handler interface:
h := jrpc2.NewHandler(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 jrpc2.MapAssigner, which finds methods by looking them up in a Go map:
assigner := jrpc2.MapAssigner{ "Add": jrpc2.NewHandler(Add), }
Equipped with an Assigner we can now construct a Server:
srv := jrpc2.NewServer(assigner, nil) // nil for default options
To serve requests, we will next need a channel.Channel. The channel package exports functions that can adapt 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 will handle 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. A working implementation of this example can found in examples/adder/adder.go:
$ go run examples/adder/adder.go
You can interact with this server on the command line.
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 establish a client we first 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})
This 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 all at once, use the Batch method:
rsps, err := cli.Batch(ctx, []jrpc2.Spec{ {"Math.Add", []int{1, 2, 3}}, {"Math.Mul", []int{4, 5, 6}}, {"Math.Max", []int{-1, 5, 3, 0, 1}}, })
The Batch method waits until all the responses are received. The caller must check each response separately for errors. The responses will be 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 shut down a client and discard all its pending work, call cli.Close().
Notifications ¶
The JSON-RPC protocol also supports a kind of request called a notification. Notifications differ from ordinary calls in that they are one-way: The client sends them to the server, but the server does not reply.
A jrpc2.Client supports sending notifications as follows:
type alert struct { M string `json:"message"` } err := cli.Notify(ctx, "Alert", alert{M: "a fire is burning!"})
Unlike ordinary requests, there are no pending calls for notifications; the notification is complete once it has been sent.
On the server side, notifications are identical to ordinary requests, save that their 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 *Client and *Server types support a nonstandard cancellation protocol, that consists of a notification method "rpc.cancel" taking an array of request IDs to be cancelled. Upon receiving this notification, the server will cancel the context of each method handler whose ID is named.
When the context associated with a client request is cancelled, the client will send an "rpc.cancel" notification to the server for that request's ID:
ctx, cancel := context.WithCancel(ctx) p, err := cli.Call(ctx, "MethodName", params) ... cancel() rsp := p.Wait()
The "rpc.cancel" method is automatically handled by the *Server implementation from this package.
Services with Multiple Methods ¶
The examples above show a server with only one method using NewHandler; you will often want to expose more than one. The NewService function supports this by applying NewHandler to all the exported methods of a concrete value to produce a MapAssigner for those methods:
type math struct{} func (math) Add(ctx context.Context, vals ...int) (int, error) { ... } func (math) Mul(ctx context.Context, vals []int) (int, error) { ... } assigner := jrpc2.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 ServiceMapper type to allow different services to work together:
type status struct{} func (status) Get(context.Context) (string, error) { return "all is well", nil } assigner := jrpc2.ServiceMapper{ "Math": jrpc2.NewService(math{}), "Status": jrpc2.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 ServiceMapper splits the method name on the first period ("."), and you may nest ServiceMappers more deeply if you require a more complex hierarchy.
See the "caller" package for a convenient way to generate client call wrappers.
Non-Standard Custom Methods ¶
By default a jrpc2.Server exports the following built-in non-standard custom methods:
rpc.serverInfo (null) Returns a jrpc2.ServerInfo value giving server metrics. rpc.cancel ([]int) [notification only] Request cancellation of the specified in-flight request IDs.
The methods marked "notification only" work only as notifications, and will report an error if called as ordinary methods. These methods are enabled by default, but may be disabled by setting the DisableBuiltin server option to true when constructing the server.
Index ¶
- Constants
- Variables
- func DataErrorf(code code.Code, v interface{}, msg string, args ...interface{}) error
- func Errorf(code code.Code, msg string, args ...interface{}) error
- func ServerMetrics(ctx context.Context) *metrics.M
- func ServerPush(ctx context.Context, method string, params interface{}) error
- type Assigner
- type Client
- func (c *Client) Batch(ctx context.Context, specs []Spec) ([]*Response, error)
- func (c *Client) Call(ctx context.Context, method string, params interface{}) (*Response, error)
- func (c *Client) CallResult(ctx context.Context, method string, params, result interface{}) error
- func (c *Client) Close() error
- func (c *Client) Notify(ctx context.Context, method string, params interface{}) error
- type ClientOptions
- type Error
- type Handler
- type MapAssigner
- type Request
- type Response
- type Server
- type ServerInfo
- type ServerOptions
- type ServiceMapper
- type Spec
Examples ¶
Constants ¶
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 ¶
var ErrNoData = errors.New("no data to unmarshal")
ErrNoData indicates that there are no data to unmarshal.
var ErrNotifyUnsupported = errors.New("server notifications are not enabled")
ErrNotifyUnsupported is returned by ServerPush if server notifications are not enabled in the specified context.
Functions ¶
func DataErrorf ¶
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 ¶
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 ServerMetrics ¶
ServerMetrics returns the server metrics collector associated with the given context, or nil if ctx doees not have a collector attached. The context passed to a handler by *jrpc2.Server will include this value.
func ServerPush ¶ added in v0.0.18
ServerPush posts a server notification to the client. If ctx does not contain a server notifier, this reports ErrNotifyUnsupported. The context passed to the handler by *jrpc2.Server will support notiications if the server was constructed with the AllowPush option set true.
Types ¶
type Assigner ¶
type Assigner interface { // Assign returns the handler for the named method, or nil. Assign(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 ¶
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 in each response.
Example ¶
package main import ( "context" "fmt" "log" "bitbucket.org/creachadair/jrpc2" "bitbucket.org/creachadair/jrpc2/channel" ) var ( ctx = context.Background() srv, cli = channel.Pipe(channel.Line) c = jrpc2.NewClient(cli, nil) ) type Msg struct { Text string `json:"msg"` } func main() { // var c = jrpc2.NewClient(cli, nil) rsps, err := c.Batch(ctx, []jrpc2.Spec{ {Method: "Hello"}, {Method: "Log", Params: Msg{"Sing it!"}, Notify: true}, }) if err != nil { log.Fatalf("Batch: %v", err) } // There should be only one reply in this case, since we sent 1 // notification and 1 request. if len(rsps) != 1 { log.Fatalf("Wait: got %d responses, wanted 1", len(rsps)) } fmt.Printf("len(rsps)=%d\n", len(rsps)) // Decode the result from the request. var msg string if err := rsps[0].UnmarshalResult(&msg); err != nil { log.Fatalf("Invalid result: %v", err) } fmt.Printf("rsps[0]=%s\n", msg) }
Output: Log: Sing it! len(rsps)=1 rsps[0]=Hello, world!
func (*Client) Call ¶
Call initiates a single request and blocks until the response returns. If err != nil then rsp == nil. Errors from the server have concrete type *jrpc2.Error.
rsp, err := c.Call(ctx, method, params) if err != nil { if e, ok := err.(*jrpc2.Error); ok { log.Fatalf("Error from server: %v", err) } else { log.Fatalf("Call failed: %v", err) } } handleValidResponse(rsp)
Example ¶
package main import ( "context" "fmt" "log" "bitbucket.org/creachadair/jrpc2" "bitbucket.org/creachadair/jrpc2/channel" ) var ( ctx = context.Background() srv, cli = channel.Pipe(channel.Line) c = jrpc2.NewClient(cli, nil) ) func main() { // var c = jrpc2.NewClient(cli, nil) rsp, err := c.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 ¶ added in v0.0.18
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" "bitbucket.org/creachadair/jrpc2" "bitbucket.org/creachadair/jrpc2/channel" ) var ( ctx = context.Background() srv, cli = channel.Pipe(channel.Line) c = jrpc2.NewClient(cli, nil) ) func main() { // var c = jrpc2.NewClient(cli, nil) var msg string if err := c.CallResult(ctx, "Hello", nil, &msg); err != nil { log.Fatalf("CallResult: %v", err) } fmt.Println(msg) }
Output: Hello, world!
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 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, 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) }
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) UnmarshalData ¶
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.
type Handler ¶ added in v0.0.21
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 two extra // values that the handler may extract. // // To obtain a server metrics value, write: // // sm := jrpc2.ServerMetrics(ctx) // // To obtain the inbound request message, write: // // req := jrpc2.InboundRequest(ctx) // // The inbound request is the same value passed to the Handle method -- the // latter is primarily useful in handlers generated by jrpc2.NewHandler, // which do not receive this value directly. Handle(context.Context, *Request) (interface{}, error) }
A Handler handles a single request.
func NewHandler ¶ added in v0.0.21
func NewHandler(fn interface{}) Handler
NewHandler adapts a function to a jrpc2.Handler. The concrete value of fn must be a function with one of the following type signatures:
func(context.Context) (Y, error) func(context.Context, X) error func(context.Context, X) (Y, error) func(context.Context, ...X) (Y, error) func(context.Context, *jrpc2.Request) (Y, error) func(context.Context, *jrpc2.Request) (interface{}, error)
for JSON-marshalable types X and Y. NewHandler will panic if the type of its argument does not have one of these forms. The resulting method will handle encoding and decoding of JSON and report appropriate errors.
Functions adapted by in this way can obtain the *jrpc2.Request value using the jrpc2.InboundRequest helper on the context value supplied by the server.
type MapAssigner ¶
A MapAssigner is a trivial implementation of the Assigner interface that looks up method names in a map of static Handlers.
func NewService ¶
func NewService(obj interface{}) MapAssigner
NewService adapts the methods of a value to a map from method names to Handler implementations as constructed by NewHandler. It will panic if obj has no exported methods with a suitable signature.
func (MapAssigner) Assign ¶
func (m MapAssigner) Assign(method string) Handler
Assign implements part of the Assigner interface.
func (MapAssigner) Names ¶
func (m MapAssigner) Names() []string
Names implements part of the Assigner interface.
type Request ¶
type Request struct {
// contains filtered or unexported fields
}
A Request is a request message from a client to a server.
func InboundRequest ¶
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.Call the request value returned by InboundRequest will be the same value as was passed explicitly.
func (*Request) HasParams ¶ added in v0.0.12
HasParams reports whether the request has non-empty parameters.
func (*Request) IsNotification ¶
IsNotification reports whether the request is a notification, and thus does not require a value response.
func (*Request) UnmarshalParams ¶
UnmarshalParams decodes the parameters into v.
type Response ¶
type Response struct {
// contains filtered or unexported fields
}
A Response is a response message from a server to a client.
func (*Response) UnmarshalResult ¶
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.
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" "fmt" "strings" "bitbucket.org/creachadair/jrpc2" "bitbucket.org/creachadair/jrpc2/channel" ) var ( s *jrpc2.Server srv, cli = channel.Pipe(channel.Line) ) type Msg struct { Text string `json:"msg"` } func main() { // Construct a new server with a single method "Hello". s = jrpc2.NewServer(jrpc2.MapAssigner{ "Hello": jrpc2.NewHandler(func(ctx context.Context) (string, error) { return "Hello, world!", nil }), "Log": jrpc2.NewHandler(func(ctx context.Context, msg Msg) (bool, error) { fmt.Println("Log:", msg.Text) return true, nil }), }, nil).Start(srv) // 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: Hello Log
func (*Server) Push ¶ added in v0.0.18
Push posts a 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 without sending anything.
func (*Server) ServerInfo ¶
func (s *Server) ServerInfo() *ServerInfo
ServerInfo returns an atomic snapshot of the current server info for s.
func (*Server) Start ¶
Start enables processing of requests from c. This function will panic if the server is already running.
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]string `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.
type ServerOptions ¶
type ServerOptions struct { // If not nil, send debug logs here. Logger *log.Logger // Instructs the server to tolerate requests that do not include the // required "jsonrpc" version marker. AllowV1 bool // Instructs the server to allow server notifications, a non-standard // extension to the JSON-RPC protocol. If AllowPush is false, the Push // method of the server will report an error when 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 concurrent goroutines to execute // when processing requests. A value less than 1 uses runtime.NumCPU(). Concurrency int // If set, this function is called with the 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, json.RawMessage) (context.Context, json.RawMessage, 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 ServiceMapper ¶
A ServiceMapper combines multiple assigners into one, permitting a server to export multiple services under different names.
Example:
m := jrpc2.ServiceMapper{ "Foo": jrpc2.NewService(fooService), // methods Foo.A, Foo.B, etc. "Bar": jrpc2.NewService(barService), // methods Bar.A, Bar.B, etc. }
func (ServiceMapper) Assign ¶
func (m ServiceMapper) Assign(method string) Handler
Assign splits the inbound method name as Service.Method, and passes the Method portion to the corresponding Service assigner. If method does not have the form Service.Method, or if Service is not set in m, the lookup fails and returns nil.
func (ServiceMapper) Names ¶
func (m ServiceMapper) Names() []string
Names reports the composed names of all the methods in the service, each having the form Service.Method.
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
Package caller provides a function to construct JRPC2 client call wrappers.
|
Package caller provides a function to construct JRPC2 client call wrappers. |
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 bitbucket.org/creachadair/jrpc2/channel package.
|
Package chanutil exports helper functions for working with channels and framing defined by the bitbucket.org/creachadair/jrpc2/channel package. |
Package code defines error code values used by the jrpc2 package.
|
Package code defines error code values used by the jrpc2 package. |
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. |
client
Program client demonstrates how to set up a JSON-RPC 2.0 client using the bitbucket.org/creachadair/jrpc2 package.
|
Program client demonstrates how to set up a JSON-RPC 2.0 client using the bitbucket.org/creachadair/jrpc2 package. |
jcl
Program jcl is a client program for the demonstration shell-server defined in jsh.go.
|
Program jcl is a client program for the demonstration shell-server defined in jsh.go. |
jsh
Program jsh exposes a trivial command-shell functionality via JSON-RPC for demonstration purposes.
|
Program jsh exposes a trivial command-shell functionality via JSON-RPC for demonstration purposes. |
server
Program server demonstrates how to set up a JSON-RPC 2.0 server using the bitbucket.org/creachadair/jrpc2 package.
|
Program server demonstrates how to set up a JSON-RPC 2.0 server using the bitbucket.org/creachadair/jrpc2 package. |
Program jcall issues RPC calls to a JSON-RPC server.
|
Program jcall issues RPC calls to a JSON-RPC server. |
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. |
Program jproxy is a reverse proxy JSON-RPC server that bridges and multiplexes client requests to a server that communicates over a pipe.
|
Program jproxy is a reverse proxy JSON-RPC server that bridges and multiplexes client requests to a server that communicates over a pipe. |
Package metrics defines a concurrently-accessible metrics collector.
|
Package metrics defines a concurrently-accessible metrics collector. |
Package proxy implements a transparent JSON-RPC proxy that dispatches to a jrpc2.Client.
|
Package proxy implements a transparent JSON-RPC proxy that dispatches to a jrpc2.Client. |
Package server provides support routines for running jrpc2 servers.
|
Package server provides support routines for running jrpc2 servers. |