/* 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. The channel package exports functions to 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 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 processes requests 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. 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. */ package jrpc2 // Version is the version string for the JSON-RPC protocol understood by this // implementation, defined at http://www.jsonrpc.org/specification. const Version = "2.0"