Documentation
¶
Overview ¶
Package jsonrpc implements the JSON-RPC 2.0 specification with pluggable transports.
The package is transport-agnostic at its core: a Server dispatches decoded requests to registered handlers, and the same dispatch logic backs the HTTP handler (Server.ServeHTTP) and the newline-delimited stream loop (Server.ServeStream, suitable for stdio). A Client talks to a remote endpoint over any Transport — HTTP (HTTPTransport), an arbitrary byte stream (StreamTransport), or a spawned subprocess (NewProcessTransport). When both ends must act as client and server on a single stream, a Peer combines the two (NewPeer, or NewProcessPeer for a subprocess).
The wire format follows JSON-RPC 2.0 exactly: request/notification framing, batch arrays, notification (no id) semantics, and the standard error codes. Request ids are preserved as raw JSON so their original shape (number, string or null) round-trips into the response unchanged, and request params are kept as encoding/json.RawMessage so handlers control their own decoding (preserving integer precision, custom number handling, etc.).
Index ¶
- Constants
- Variables
- func IsNotification(ctx context.Context) bool
- func Unmarshal(params json.RawMessage, v any) error
- type BatchCall
- type BatchResult
- type Client
- type Error
- func Errorf(code int, format string, args ...any) *Error
- func InternalError(message string) *Error
- func InvalidParams(message string) *Error
- func InvalidRequest(message string) *Error
- func MethodNotFound(message string) *Error
- func NewError(code int, message string, data any) *Error
- func ParseError(message string) *Error
- func ServerError(message string) *Error
- type HTTPOption
- type HTTPTransport
- type Handler
- type Option
- type Peer
- func (p *Peer) Call(ctx context.Context, req *Request) (*Response, error)
- func (p *Peer) CallBatch(ctx context.Context, reqs []*Request) ([]*Response, error)
- func (p *Peer) Client() *Client
- func (p *Peer) Close() error
- func (p *Peer) Done() <-chan struct{}
- func (p *Peer) Err() error
- func (p *Peer) Handle(method string, h Handler) *Server
- func (p *Peer) Notify(ctx context.Context, req *Request) error
- func (p *Peer) Serve() error
- func (p *Peer) Server() *Server
- func (p *Peer) Unregister(method string) bool
- func (p *Peer) Wait()
- type PeerOption
- type ProcessOption
- type Request
- type Response
- type Server
- func (s *Server) Handle(method string, h Handler) *Server
- func (s *Server) HandleMessage(ctx context.Context, raw []byte) ([]byte, bool)
- func (s *Server) HandleRequest(ctx context.Context, req *Request) *Response
- func (s *Server) Methods() []string
- func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Server) ServeStream(ctx context.Context, in io.Reader, out io.Writer) error
- func (s *Server) Unregister(method string) bool
- type StreamOption
- type StreamTransport
- type Transport
Constants ¶
const ( // CodeParseError indicates invalid JSON was received by the server. CodeParseError = -32700 // CodeInvalidRequest indicates the JSON sent is not a valid Request object. CodeInvalidRequest = -32600 // CodeMethodNotFound indicates the method does not exist or is not available. CodeMethodNotFound = -32601 // CodeInvalidParams indicates invalid method parameters. CodeInvalidParams = -32602 // CodeInternalError indicates an internal JSON-RPC error. CodeInternalError = -32603 // CodeServerError is the conventional code for an application/server error // (reserved range -32000 to -32099). CodeServerError = -32000 )
Standard JSON-RPC 2.0 error codes.
Codes from -32000 to -32099 are reserved for implementation-defined server errors; CodeServerError is the conventional default within that range.
const Version = "2.0"
Version is the JSON-RPC protocol version emitted on every response and required (when present) on every request.
Variables ¶
var ErrClosed = errors.New("jsonrpc: transport closed")
ErrClosed is returned by a stream transport when a call cannot complete because the transport has been closed or its underlying stream ended.
Functions ¶
func IsNotification ¶
IsNotification reports whether the in-flight call is a JSON-RPC notification (a request without an id, for which no response is sent). It is valid only inside a Handler; it returns false elsewhere.
The same handler serves both requests and notifications, so this lets a handler adjust its behaviour for the fire-and-forget case — for example to reject notification-only or request-only methods.
func Unmarshal ¶
func Unmarshal(params json.RawMessage, v any) error
Unmarshal decodes raw params into v. It is a thin convenience wrapper over json.Unmarshal that treats absent (empty) params as a no-op, leaving v at its zero value. Returns a CodeInvalidParams Error on failure so it can be returned directly from a Handler.
Types ¶
type BatchCall ¶
BatchCall is a single call within a batch request to Client.CallBatch. Out mirrors the out parameter of Client.Call: nil discards the result, otherwise the result is JSON-decoded into it. Out is only touched from CallBatch itself (not concurrently), so it need not be safe for concurrent use.
type BatchResult ¶
type BatchResult struct {
Result json.RawMessage
Err error
}
BatchResult is the outcome of one BatchCall from Client.CallBatch. Err is non-nil on a transport failure or a JSON-RPC error response (in which case it is a *Error); Result holds the raw result bytes for callers that did not set Out, or who want to inspect the wire value directly.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a JSON-RPC 2.0 client. It is a thin, transport-agnostic wrapper that generates request ids, encodes params, and decodes results. It is safe for concurrent use if the underlying Transport is.
func (*Client) Call ¶
Call invokes method with params and decodes the result into out.
params is encoded to JSON (a nil params sends no params member; a json.RawMessage is sent verbatim). out may be nil to discard the result; otherwise the result is JSON-decoded into out. A JSON-RPC error response is returned as a *Error.
func (*Client) CallBatch ¶
func (c *Client) CallBatch(ctx context.Context, calls []BatchCall) []BatchResult
CallBatch sends every call in a single JSON-RPC 2.0 batch request when the underlying Transport supports it (HTTPTransport and StreamTransport both do), cutting N round-trips down to one. Transports without native batch support still work: CallBatch falls back to issuing the calls concurrently over Transport.Call.
Results are returned in the same order as calls, regardless of transport or response ordering. A per-call failure (transport error or JSON-RPC error response) is reported in that call's BatchResult.Err; it does not affect other calls in the batch. An empty calls slice returns an empty, non-nil slice.
type Error ¶
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
Error is a JSON-RPC 2.0 error object. It implements the error interface so handlers can return it directly and clients receive it from Client.Call.
Data is optional and, when non-nil, is serialised into the "data" member of the error object.
func InternalError ¶
InternalError returns a -32603 error with the given message.
func InvalidParams ¶
InvalidParams returns a -32602 error with the given message.
func InvalidRequest ¶
InvalidRequest returns a -32600 error with the given message.
func MethodNotFound ¶
MethodNotFound returns a -32601 error with the given message.
func ParseError ¶
ParseError returns a -32700 error with the given message.
func ServerError ¶
ServerError returns a -32000 error with the given message.
type HTTPOption ¶
type HTTPOption func(*HTTPTransport)
HTTPOption configures an HTTPTransport.
func WithHTTPClient ¶
func WithHTTPClient(client *http.Client) HTTPOption
WithHTTPClient sets the http.Client used for requests. By default http.DefaultClient is used.
func WithHeader ¶
func WithHeader(key, value string) HTTPOption
WithHeader adds a header sent on every request (e.g. Authorization). It may be called multiple times to add several headers.
type HTTPTransport ¶
type HTTPTransport struct {
// contains filtered or unexported fields
}
HTTPTransport is a Transport that sends each call as an HTTP POST carrying the JSON-RPC request in the body. It is safe for concurrent use.
func NewHTTPTransport ¶
func NewHTTPTransport(url string, opts ...HTTPOption) *HTTPTransport
NewHTTPTransport creates an HTTP transport targeting url.
func (*HTTPTransport) CallBatch ¶
CallBatch implements the batch-capable transport extension used by Client.CallBatch: it POSTs the whole batch as a single JSON-RPC array and decodes the array response.
func (*HTTPTransport) Close ¶
func (t *HTTPTransport) Close() error
Close implements Transport. It is a no-op for HTTP.
type Handler ¶
Handler processes a single JSON-RPC method call.
params carries the raw JSON of the request's params member (nil when absent), letting the handler decode into whatever type it wants — use Unmarshal or json.Unmarshal directly. The returned value is marshalled into the response result. To signal a JSON-RPC error, return a *Error (its code, message and data are used verbatim); any other non-nil error becomes a CodeServerError.
The same handler serves both requests and notifications. When invoked for a notification the return value and error are discarded (no response is sent), though a returned error is still reported to an [Options.OnError] hook if set.
func Typed ¶
Typed adapts a strongly-typed handler into a Handler. The params are decoded into a fresh T before fn is called; a decode failure is reported as a CodeInvalidParams error without invoking fn.
srv.Handle("add", jsonrpc.Typed(func(ctx context.Context, p AddParams) (any, error) {
return p.A + p.B, nil
}))
type Option ¶
type Option func(*Server)
Option configures a Server.
func WithErrorHandler ¶
WithErrorHandler registers a hook invoked whenever a handler returns a non-nil error or panics — including for notifications, whose errors are otherwise invisible. It is intended for logging and must not block.
func WithSerialDispatch ¶
func WithSerialDispatch() Option
WithSerialDispatch disables concurrent dispatch. Batch elements and stream messages are then processed one at a time in arrival order. By default the server dispatches each message (and each batch element) concurrently.
type Peer ¶
type Peer struct {
// contains filtered or unexported fields
}
Peer is a bidirectional JSON-RPC 2.0 connection over a pair of byte streams. Unlike a Client (outbound only) or a Server served via Server.ServeStream (inbound only), a Peer does both at once over the same stream: it sends outbound requests and correlates their responses by id, AND it receives inbound requests, dispatches them to a method registry, and writes the responses back.
This is the shape protocols use when both ends act as both client and server on a single connection — for example a host that calls methods on a spawned plugin while also serving "host callback" methods the plugin invokes back (remote objects, progress, elicitation).
NewPeer starts a background reader immediately. The reader demultiplexes each incoming message, routing responses to pending outbound calls and requests to the Peer's Server. Inbound request handlers run in their own goroutine so a slow handler cannot stall delivery of outbound responses. Call Peer.Close to stop the reader and release the streams. Outbound calls go through the Peer's Client (see Peer.Client).
func NewPeer ¶
NewPeer returns a Peer that reads JSON-RPC messages from in and writes them to out, dispatching inbound requests to server (pass NewServer if you have no existing registry) and correlating outbound call responses by id.
NewPeer does not start the reader — call Peer.Serve from the goroutine that should own the input. That lets a caller finish wiring up state the handlers depend on (for example, pointing a server runtime at the peer) before any handler can run, avoiding a construction race.
func NewProcessPeer ¶
func NewProcessPeer(name string, args []string, server *Server, opts ...ProcessOption) (*Peer, error)
NewProcessPeer spawns name with args as a JSON-RPC 2.0 peer speaking newline-delimited JSON over its stdin/stdout, and returns a bidirectional Peer connected to it. It is the subprocess counterpart to NewPeer, just as NewProcessTransport is the subprocess counterpart to NewStreamTransport: use it when the child both handles requests and makes reverse-direction requests (host callbacks, remote objects, elicitation).
It accepts the same options as NewProcessTransport (WithStderr, WithShutdownTimeout, WithEnv, WithDir, WithOnExit). Closing the Peer closes the child's stdin, waits up to the shutdown timeout, then kills it if necessary; the OnExit hook fires once after the child is reaped.
func (*Peer) Call ¶
Call implements Transport for the Peer's internal Client. It is exported only to satisfy the interface; callers invoke outbound calls via Peer.Client.
func (*Peer) CallBatch ¶
CallBatch implements the batch-capable transport extension used by Client.CallBatch: it writes the whole batch as a single JSON-RPC array line and collects each response by id as it arrives.
func (*Peer) Client ¶
Client returns the outbound client used to make requests and send notifications that the other end will serve:
var result T
if err := peer.Client().Call(ctx, "remote.method", params, &result); err != nil { ... }
The client shares the Peer's connection and is safe for concurrent use.
func (*Peer) Close ¶
Close stops the read loop, cancels contexts passed to in-flight inbound handlers, and runs the configured close function (if any) exactly once. Pending and future outbound calls fail.
func (*Peer) Done ¶
func (p *Peer) Done() <-chan struct{}
Done returns a channel that is closed when the reader exits — on EOF from in, a fatal decode error, or Close. Err returns the reason (nil for a clean EOF).
func (*Peer) Err ¶
Err returns the read error that stopped the reader, or nil if it ended cleanly (EOF) or the Peer was closed without a read error.
func (*Peer) Handle ¶
Handle registers an inbound handler on the Peer's Server; it is a convenience for p.Server().Handle. It returns the server for chaining.
func (*Peer) Serve ¶
Serve runs the read loop until the input reaches EOF or a fatal decode error, returning nil for a clean EOF and the error otherwise. Serve blocks; inbound handlers run concurrently in their own goroutines. Call Peer.Wait after Serve returns to block until any still-running handlers have flushed.
func (*Peer) Server ¶
Server returns the inbound method registry. Handlers may be registered before or after NewPeer.
func (*Peer) Unregister ¶
Unregister removes an inbound handler; it is a convenience for p.Server().Unregister.
func (*Peer) Wait ¶
func (p *Peer) Wait()
Wait blocks until every in-flight inbound handler has finished, so any response still being written when the reader exited is flushed before Wait returns. Call it after <-Done (or Close) when serving must not return before all handlers are quiesced — for example a stdio server exiting on EOF.
type PeerOption ¶
type PeerOption func(*Peer)
PeerOption configures a Peer.
func WithPeerCloseFunc ¶
func WithPeerCloseFunc(fn func() error) PeerOption
WithPeerCloseFunc registers a function run once when the Peer is closed, after the reader is signalled to stop. Use it to release resources backing the streams (for example, close a subprocess's stdin and reap it via NewProcessTransport's close function).
type ProcessOption ¶
type ProcessOption func(*processConfig)
ProcessOption configures a subprocess transport created by NewProcessTransport.
func WithDir ¶
func WithDir(dir string) ProcessOption
WithDir sets the working directory for the child process.
func WithEnv ¶
func WithEnv(env []string) ProcessOption
WithEnv sets the environment for the child process (as in os/exec Cmd.Env).
func WithOnExit ¶
func WithOnExit(fn func(error)) ProcessOption
WithOnExit registers a callback invoked exactly once when the child process exits — whether it exits on its own or as a result of closing the transport. The callback receives the error returned by the child's wait (nil for a clean exit).
The callback is invoked from a dedicated goroutine after the child has been reaped, so it fires even if StreamTransport.Close is never called (e.g. the child crashes mid-session). It must not block for long and its completion is not waited on by Close; do slow cleanup on a separate goroutine.
func WithShutdownTimeout ¶
func WithShutdownTimeout(d time.Duration) ProcessOption
WithShutdownTimeout sets how long Close waits for the child to exit after its stdin is closed before killing it. The default is 5 seconds.
func WithStderr ¶
func WithStderr(w io.Writer) ProcessOption
WithStderr routes the child process's standard error to w. By default it is inherited from the parent (os.Stderr). Pass io.Discard to silence it.
type Request ¶
type Request struct {
JSONRPC json.RawMessage `json:"jsonrpc,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
ID json.RawMessage `json:"id,omitempty"`
}
Request is a single inbound JSON-RPC request or notification.
JSONRPC, Params and ID are kept as raw JSON. ID is raw so an absent id (notification) is distinguishable from an explicit null and so the original id shape round-trips into the response unchanged. Params is raw so handlers control their own decoding. JSONRPC is raw so a wrong-typed version field yields an Invalid Request error rather than failing the whole batch decode.
func (*Request) IsNotification ¶
IsNotification reports whether the request is a notification, i.e. it carries no id member and therefore expects no response.
type Response ¶
type Response struct {
Result json.RawMessage
Error *Error
ID json.RawMessage
}
Response is a single outbound JSON-RPC response.
Exactly one of Result or Error is meaningful: on success Result holds the (already marshalled) result value and Error is nil; on failure Error is set. The custom MarshalJSON guarantees the wire form matches the spec — a success response always includes a result member (null when the handler returned nothing) and never an error member, and vice versa.
func (Response) MarshalJSON ¶
MarshalJSON renders the response in spec-compliant form.
func (*Response) UnmarshalJSON ¶
UnmarshalJSON parses a response from the wire.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a JSON-RPC 2.0 method registry and dispatcher. It is safe for concurrent use: handlers may be registered before serving, and dispatch is concurrent by default. The zero value is not usable; create one with NewServer.
A Server is transport-agnostic. Server.ServeHTTP exposes it as an http.Handler, Server.ServeStream runs the newline-delimited loop used for stdio, and Server.HandleMessage / Server.HandleRequest expose the dispatch core directly for embedding in other transports.
func (*Server) Handle ¶
Handle registers h for the given method name, replacing any existing handler. It returns the server to allow chaining. It panics if h is nil.
func (*Server) HandleMessage ¶
HandleMessage dispatches one raw inbound JSON-RPC message — a single request, a single notification, or a batch array. It returns the marshalled response and whether a response is due. A notification (or an all-notification batch) yields (nil, false); every other message yields (bytes, true), including parse and invalid-request errors.
func (*Server) HandleRequest ¶
HandleRequest dispatches a single already-decoded request. It returns the response to send, or nil when no response is due (i.e. the request is a notification, valid or not). This is the lowest-level dispatch entry point; most callers use Server.HandleMessage.
func (*Server) Methods ¶
Methods returns the sorted-order-independent set of registered method names.
func (*Server) ServeHTTP ¶
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP exposes the server as an http.Handler. It accepts POST requests carrying a single JSON-RPC object or a batch array. A message that produces no response (a notification, or an all-notification batch) yields 204 No Content; otherwise the JSON response is returned with a 200 status.
func (*Server) ServeStream ¶
ServeStream runs a newline-delimited JSON-RPC loop over the given streams, the transport used for stdio servers. It reads JSON values from in (they need not be newline-separated; any JSON whitespace works) and writes each response as a single line to out. Requests are dispatched concurrently by default and writes are serialised.
The call blocks until in reaches EOF (returning the first write error seen, if any) or a fatal decode error occurs. A decode error means the stream can no longer be framed reliably, so a parse-error response is emitted and the error is returned. Cancelling ctx cancels the context passed to in-flight handlers but does not itself unblock a pending read; close in to stop the loop.
func (*Server) Unregister ¶
Unregister removes the handler for method, if any. It reports whether a handler was present.
type StreamOption ¶
type StreamOption func(*StreamTransport)
StreamOption configures a StreamTransport.
func WithCloseFunc ¶
func WithCloseFunc(fn func() error) StreamOption
WithCloseFunc registers a function run once when the transport is closed, after the read loop is signalled to stop. NewProcessTransport uses it to shut down the child process.
type StreamTransport ¶
type StreamTransport struct {
// contains filtered or unexported fields
}
StreamTransport is a Transport over a pair of byte streams carrying newline-delimited JSON-RPC messages. It is the client-side counterpart to Server.ServeStream and backs both in-process pipes and subprocess communication (see NewProcessTransport).
A background goroutine reads responses from the input stream and correlates them to pending calls by request id, so many calls and notifications may be in flight concurrently. It is safe for concurrent use.
func NewProcessTransport ¶
func NewProcessTransport(name string, args []string, opts ...ProcessOption) (*StreamTransport, error)
NewProcessTransport spawns name with args as a JSON-RPC server speaking newline-delimited JSON over its stdin/stdout, and returns a transport connected to it. Closing the transport closes the child's stdin (signalling it to exit), waits up to the shutdown timeout, then kills it if necessary. For a bidirectional connection use NewProcessPeer instead.
func NewStreamTransport ¶
func NewStreamTransport(in io.Reader, out io.Writer, opts ...StreamOption) *StreamTransport
NewStreamTransport creates a transport that writes requests to out and reads responses from in. It starts the background read loop immediately; call StreamTransport.Close to stop it and release resources.
func (*StreamTransport) CallBatch ¶
CallBatch implements the batch-capable transport extension used by Client.CallBatch: it writes the whole batch as a single JSON-RPC array line and collects each response as it arrives, in any order, via the same id-correlated delivery used by Call.
func (*StreamTransport) Close ¶
func (t *StreamTransport) Close() error
Close implements Transport. It stops the read loop and runs the configured close function (if any) exactly once.
type Transport ¶
type Transport interface {
// Call sends a request and returns the correlated response. The request
// always carries an id. Implementations must return a non-nil *Response on
// success (which may itself carry a JSON-RPC Error), or a non-nil error for
// transport-level failures.
Call(ctx context.Context, req *Request) (*Response, error)
// Notify sends a notification (a request with no id) for which no response
// is expected. It returns only transport-level errors.
Notify(ctx context.Context, req *Request) error
// Close releases any resources held by the transport.
Close() error
}
Transport is the client-side connection to a JSON-RPC endpoint. A single Transport may be shared by concurrent Client calls; implementations must be safe for concurrent use.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
http-client
command
Command http-client calls the http-server example.
|
Command http-client calls the http-server example. |
|
http-server
command
Command http-server exposes a small calculator over JSON-RPC 2.0 on HTTP.
|
Command http-server exposes a small calculator over JSON-RPC 2.0 on HTTP. |
|
peer-client
command
Command peer-client is the host side of a bidirectional JSON-RPC 2.0 connection.
|
Command peer-client is the host side of a bidirectional JSON-RPC 2.0 connection. |
|
peer-plugin
command
Command peer-plugin is the server side of a bidirectional JSON-RPC 2.0 connection.
|
Command peer-plugin is the server side of a bidirectional JSON-RPC 2.0 connection. |
|
stdio-client
command
Command stdio-client launches a JSON-RPC stdio server as a subprocess and talks to it over the child's stdin/stdout.
|
Command stdio-client launches a JSON-RPC stdio server as a subprocess and talks to it over the child's stdin/stdout. |
|
stdio-server
command
Command stdio-server is a JSON-RPC 2.0 server that speaks newline-delimited JSON over stdin/stdout.
|
Command stdio-server is a JSON-RPC 2.0 server that speaks newline-delimited JSON over stdin/stdout. |