jrpc2

package module
v0.0.0-...-339075d Latest Latest
Warning

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

Go to latest
Published: Jun 24, 2026 License: MIT Imports: 20 Imported by: 0

README

jrpc2

JSON-RPC 2.0 client & server over WebSockets.

Features:

  • Method calls, notifications, & errors
  • Offers ServeHTTP for your net/http server
  • Support for reverse RPC calls
  • Can wrap existing net.Conn with NewSession
  • Single dependency (coder/websocket)
  • Does not hijack your logs

Not implemented (and probably never will):

  • Batch requests

Basic usage

// Types

type AddRequest struct {
    A, B int
}

type SqrRequest struct {
    X int
}

type Result struct {
    Value int
}
// Server

mux := jrpc2.NewMux()
mux.RegisterFunc("calc.add", func(ctx context.Context, p *AddRequest) (*Result, error) {
    return &Result{Value: p.A + p.B}, nil
})
mux.RegisterFunc("calc.sqr", func(ctx context.Context, p *SqrRequest) (*Result, error) {
    return &Result{Value: p.X * p.X}, nil
})

rpcServer := jrpc2.NewServer(&ServerConfig{
    SessionConfig: &SessionConfig{
        Handler: mux,
    },
})
server := http.Server{
    Addr: "0.0.0.0:1337",
    Handler: rpcServer,
}
server.ListenAndServe()
// Client

client, err := jrpc2.DialRetry(ctx, "ws://127.0.0.1:1337", &ClientConfig{
    // Client can also expose methods that may be called by server (reverse RPC calls).
    // SessionConfig: &SessionConfig{
    //     Handler: mux,
    // },
})
if err != nil {
    t.Fatal(err)
}

res := &Result{}
err := client.Call(ctx, "calc.add", AddRequest{A: 3, B: 4}, &res)
// Or, with generics:
// res, err := jrpc2.Call[*Result](ctx, client, "calc.add", AddRequest{A: 3, B: 4})
fmt.Println(res.Value) // 7

Reverse RPC calls

Instance of jrpc2.Session can be retrieved from context at any time within server and client methods:

func SomeMethod(ctx context.Context, p *SomeRequest) *Result, error) {
    // Obtain jrpc2 session
    session := jrpc2.SessionFromContext(ctx)
    // Call caller's method
    session.Call(ctx, "foo.bar", Params{}, nil)
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Call

func Call[T any](ctx context.Context, session *Session, name string, param any) (T, error)

Call remote JSON-RPC 2.0 method and return result based on type parameter instead of unmarshaling result into provided arg.

func MethodNameFromContext

func MethodNameFromContext(ctx context.Context) string

Obtain name of method/notification that is currently being handled.

Types

type Callable

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

Callable holds a function along with its argument & return types. Reflection is used to infer those types.

func (*Callable) ServeRPC

func (fn *Callable) ServeRPC(ctx context.Context, session *Session, req Message) (out Message)

type ClientConfig

type ClientConfig struct {
	Transport http.RoundTripper
	Header    http.Header

	*SessionConfig
}

type Error

type Error struct {
	Code    ErrorCode `json:"code"`
	Message string    `json:"message"`
	Data    any       `json:"data"`
}

func (Error) Error

func (e Error) Error() string

type ErrorCode

type ErrorCode int
const (
	ErrorCodeParseError     ErrorCode = -32700
	ErrorCodeInvalidRequest ErrorCode = -32600
	ErrorCodeMethodNotFound ErrorCode = -32601
	ErrorCodeInvalidParams  ErrorCode = -32602
	ErrorCodeInternalError  ErrorCode = -32603
)

type Handler

type Handler interface {
	ServeRPC(ctx context.Context, session *Session, req Message) (res Message)
}

Handler defines anything that can handle RPC requests within a session context. Handler is usually a Mux or a Func.

func NewMethod

func NewMethod(fn any) Handler

NewMethod creates Func from a function to be used for inbound method calls. fn must have signature func(context.Context, *reqType) (*resType, error) or I will panic.

func NewSubscription

func NewSubscription(fn any) Handler

NewSubscription creates Func from a function to be used for inbound notifications. fn must have signature func(context.Context, *reqType) error or I will panic.

type HandlerFunc

type HandlerFunc func(ctx context.Context, session *Session, req Message) Message

func (HandlerFunc) ServeRPC

func (f HandlerFunc) ServeRPC(ctx context.Context, session *Session, req Message) Message

type Message

type Message struct {
	JSONRPC     string           `json:"jsonrpc"`
	ID          *json.RawMessage `json:"id,omitempty"`
	Method      string           `json:"method,omitempty"`
	Params      json.RawMessage  `json:"params,omitempty"`
	Result      json.RawMessage  `json:"result,omitempty"`
	Error       *Error           `json:"error,omitempty"`
	TraceParent *TraceParent     `json:"traceparent,omitempty"`
}

func NewErrorMessage

func NewErrorMessage(id *json.RawMessage, code ErrorCode, s string, arg ...any) Message

type Mux

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

func NewMux

func NewMux() *Mux

NewMux initializes a group of handlers.

func (*Mux) Names

func (mux *Mux) Names() []string

func (*Mux) Register

func (mux *Mux) Register(name string, h Handler) *Mux

Register new handler.

Muxes can be nested by using wildcard in name, e. g.:

usersMux := NewMux()
usersMux.Register("users.list", listUsers)
usersMux.Register("users.get", getUser)

mux := NewMux()
mux.Register("users.*", usersMux)

func (*Mux) RegisterFunc

func (mux *Mux) RegisterFunc(name string, fn any) *Mux

func (*Mux) ServeRPC

func (mux *Mux) ServeRPC(ctx context.Context, session *Session, req Message) (out Message)

type Server

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

TODO: - CheckOrigin - Handle panics in ServeHTTP (close conns, etc)

func NewServer

func NewServer(config *ServerConfig) *Server

NewSession creates a JSON-RPC 2.0 server and exposes methods (if provided).

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP accepts JSON-RPC 2.0 websocket connection.

type ServerConfig

type ServerConfig struct {
	*SessionConfig
}

type Session

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

Session represents active JSON-RPC 2.0 session. It is used for by both client and server.

func Dial

func Dial(ctx context.Context, url string, config *ClientConfig) (*Session, error)

Dial connects to JSON-RPC 2.0 server and exposes methods (if provided).

func DialRetry

func DialRetry(ctx context.Context, url string, config *ClientConfig) (*Session, error)

DialRetry calls Dial with retries. It tries to connect indefinitely and returns error only if context is canceled.

func NewSession

func NewSession(parentCtx context.Context, conn io.ReadWriteCloser, config *SessionConfig) *Session

NewSession creates a JSON-RPC 2.0 session for connection and exposes methods (if provided).

func SessionFromContext

func SessionFromContext(ctx context.Context) *Session

Obtain RPC session from context when used on context within RPC handler. Use to perform reverse RPC calls.

func (*Session) Call

func (s *Session) Call(ctx context.Context, name string, param any, res any) error

Call remote JSON-RPC 2.0 method. If res is nil, result is discarded.

If context has no TraceParent, new TraceParent is created.

func (*Session) Close

func (s *Session) Close() error

Close session and its underlying connection.

func (*Session) Conn

func (s *Session) Conn() io.ReadWriteCloser

Conn returns the underlying io.ReadWriteCloser (which is likely a net.Conn from http.Serve)

func (*Session) Notify

func (s *Session) Notify(ctx context.Context, name string, param any) error

Notify sends JSON-RPC 2.0 notification.

func (*Session) Run

func (s *Session) Run(parentCtx context.Context)

Run session.

func (*Session) SetArrayParams

func (s *Session) SetArrayParams(v bool)

SetArrayParams forces params to be sent as an array (of 1 object) instead of a single object. Default is true.

func (*Session) Subscribe

func (s *Session) Subscribe(name string, h Handler) func()

Subscribe to JSON-RPC 2.0 notifications. h must have type func(context.Context, *notifType) (any, error). First return (any) from fn is ignored and is present for consistency with normal methods. Returns unsubscribe function that must be called when caller is no longer interested in notifications.

func (*Session) SubscribeFunc

func (s *Session) SubscribeFunc(name string, fn any) func()

func (*Session) Wait

func (s *Session) Wait() error

Wait for session to close.

type SessionConfig

type SessionConfig struct {
	Handler               Handler
	OnError               func(error)
	DisallowUnknownFields bool
}

type TraceParent

type TraceParent struct {
	Version      byte
	TraceIDBytes [16]byte
	SpanIDBytes  [8]byte
	Flags        byte
}

func NewTraceParent

func NewTraceParent() TraceParent

func TraceParentFromContext

func TraceParentFromContext(ctx context.Context) (TraceParent, bool)

Retrieve TraceParent from this context.

func (TraceParent) ChildSpan

func (tp TraceParent) ChildSpan() TraceParent

func (TraceParent) MarshalText

func (tp TraceParent) MarshalText() ([]byte, error)

func (TraceParent) String

func (tp TraceParent) String() string

func (TraceParent) TraceID

func (tp TraceParent) TraceID() string

func (*TraceParent) UnmarshalText

func (tp *TraceParent) UnmarshalText(text []byte) error

Jump to

Keyboard shortcuts

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