isle/go/daemon/jsonrpc2/server_rw.go

61 lines
1.9 KiB
Go

package jsonrpc2
import (
"context"
"encoding/json"
"errors"
"io"
)
// ReadWriterServer wraps an io.ReadWriter so that it can handle RPC calls.
//
// The HandleNext method will process a single request and response. It should
// be called in a loop until it returns an error.
type ReadWriterServer struct {
handler Handler
enc *json.Encoder
dec *json.Decoder
}
// NewReadWriterServer returns a ReadWriterServer which will read/write on the
// given io.ReadWriter.
//
// All methods exported from rpcInterface which accept a context and one other
// value, and return a value and an error, will be considered to be available
// RPC methods, and the ReadWriterServer will execute calls to those methods
// against the given rpcInterface instance.
func NewReadWriterServer(handler Handler, rw io.ReadWriter) *ReadWriterServer {
return &ReadWriterServer{
handler: handler,
enc: json.NewEncoder(rw),
dec: json.NewDecoder(rw),
}
}
// HandleNext blocks until a single RPC call has been handled. If an error is
// returned, including context errors, then the ReadWriterServer should be
// considered in an unusable state and discarded.
func (h *ReadWriterServer) HandleNext(ctx context.Context) error {
req, decErr := decodeRequest(h.dec)
if decErr.Code != 0 {
encErr := encodeErrorResponse(h.enc, req.ID, decErr)
return errors.Join(decErr, encErr)
}
var rpcErr Error
res, err := h.handler.ServeRPC(ctx, req)
if err != nil && !errors.As(err, &rpcErr) {
// We deliberately don't include the actual error in the response. If
// doing so is desired this can be achieved using a middleware which
// translates the non-Error into an Error prior to it reaching this
// point.
rpcErr = NewError(errCodeServerError, "unexpected server-side error")
}
if rpcErr.Code != 0 {
return encodeErrorResponse(h.enc, req.ID, rpcErr)
}
return encodeResponse(h.enc, req.ID, res)
}