rpc API

rpc

package

API reference for the rpc package.

S
struct

Request

Request is a JSON RPC request.

rpc/rpc.go:15-19
type Request struct

Fields

Name Type Description
ID string json:"id"
Method string json:"method"
Params json.RawMessage json:"params,omitempty"
S
struct

Response

Response is a JSON RPC response.

rpc/rpc.go:22-26
type Response struct

Fields

Name Type Description
ID string json:"id"
Result json.RawMessage json:"result,omitempty"
Error string json:"error,omitempty"
T
type

HandlerFunc

HandlerFunc handles an RPC method.

rpc/rpc.go:29-29
type HandlerFunc func(context.Context, json.RawMessage) (any, error)
S
struct

Server

Server dispatches requests to registered handlers.

rpc/rpc.go:32-35
type Server struct

Methods

Register
Method

Register adds or replaces a method handler.

Parameters

method string
func (*Server) Register(method string, h HandlerFunc)
{
	s.mu.Lock()
	defer s.mu.Unlock()
	s.handlers[method] = h
}
Handle
Method

Handle decodes and dispatches one RPC request.

Parameters

reqBytes []byte

Returns

[]byte
error
func (*Server) Handle(ctx context.Context, reqBytes []byte) ([]byte, error)
{
	var req Request
	if err := json.Unmarshal(reqBytes, &req); err != nil {
		return nil, err
	}
	if req.Method == "" {
		return nil, errors.New("rpc: missing method")
	}

	s.mu.RLock()
	h := s.handlers[req.Method]
	s.mu.RUnlock()

	if h == nil {
		res := Response{ID: req.ID, Error: ErrMethodNotFound.Error()}
		return json.Marshal(res)
	}

	out, err := h(ctx, req.Params)
	if err != nil {
		res := Response{ID: req.ID, Error: err.Error()}
		return json.Marshal(res)
	}
	b, err := json.Marshal(out)
	if err != nil {
		return nil, err
	}
	res := Response{ID: req.ID, Result: b}
	return json.Marshal(res)
}

Fields

Name Type Description
mu sync.RWMutex
handlers map[string]HandlerFunc
F
function

NewServer

NewServer creates an empty RPC server.

Returns

rpc/rpc.go:38-38
func NewServer() *Server

{ return &Server{handlers: map[string]HandlerFunc{}} }
F
function

Call

Call invokes an RPC method over HTTP.

Parameters

endpoint
string
method
string
params
any
out
any

Returns

error
rpc/rpc.go:83-125
func Call(ctx context.Context, endpoint, method string, params any, out any) error

{
	req := Request{ID: "1", Method: method}
	if params != nil {
		b, err := json.Marshal(params)
		if err != nil {
			return err
		}
		req.Params = b
	}

	body, err := json.Marshal(req)
	if err != nil {
		return err
	}

	httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
	if err != nil {
		return err
	}
	httpReq.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(httpReq)
	if err != nil {
		return err
	}

	var res Response
	decodeErr := json.NewDecoder(resp.Body).Decode(&res)
	closeErr := resp.Body.Close()
	if decodeErr != nil {
		return decodeErr
	}
	if closeErr != nil {
		return closeErr
	}
	if res.Error != "" {
		return fmt.Errorf("rpc: %s", res.Error)
	}
	if out == nil {
		return nil
	}
	return json.Unmarshal(res.Result, out)
}
F
function

TestServerHandle_DispatchSuccess

Parameters

rpc/rpc_test.go:13-43
func TestServerHandle_DispatchSuccess(t *testing.T)

{
	s := NewServer()
	s.Register("echo", func(_ context.Context, p json.RawMessage) (any, error) {
		var in struct {
			S string `json:"s"`
		}
		_ = json.Unmarshal(p, &in)
		return map[string]string{"out": in.S}, nil
	})

	req := []byte(`{"id":"99","method":"echo","params":{"s":"hi"}}`)
	b, err := s.Handle(context.Background(), req)
	if err != nil {
		t.Fatalf("Handle error: %v", err)
	}

	var res Response
	if err := json.Unmarshal(b, &res); err != nil {
		t.Fatalf("unmarshal response: %v", err)
	}
	if res.ID != "99" || res.Error != "" {
		t.Fatalf("unexpected response: %+v", res)
	}
	var out map[string]string
	if err := json.Unmarshal(res.Result, &out); err != nil {
		t.Fatalf("unmarshal result: %v", err)
	}
	if out["out"] != "hi" {
		t.Fatalf("expected hi, got %v", out)
	}
}
F
function

TestServerHandle_MethodNotFound

Parameters

rpc/rpc_test.go:45-56
func TestServerHandle_MethodNotFound(t *testing.T)

{
	s := NewServer()
	b, err := s.Handle(context.Background(), []byte(`{"id":"1","method":"missing"}`))
	if err != nil {
		t.Fatalf("Handle error: %v", err)
	}
	var res Response
	_ = json.Unmarshal(b, &res)
	if res.Error == "" {
		t.Fatalf("expected error")
	}
}
F
function

TestCall_HTTPRoundTrip

Parameters

rpc/rpc_test.go:58-92
func TestCall_HTTPRoundTrip(t *testing.T)

{
	s := NewServer()
	s.Register("add", func(_ context.Context, p json.RawMessage) (any, error) {
		var in struct {
			A int `json:"a"`
			B int `json:"b"`
		}
		_ = json.Unmarshal(p, &in)
		return map[string]int{"sum": in.A + in.B}, nil
	})

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var req Request
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			t.Fatalf("decode request: %v", err)
		}
		b, err := s.Handle(r.Context(), mustJSON(req))
		if err != nil {
			t.Fatalf("handle: %v", err)
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write(b)
	}))
	defer srv.Close()

	var out struct {
		Sum int `json:"sum"`
	}
	if err := Call(context.Background(), srv.URL, "add", map[string]int{"a": 2, "b": 5}, &out); err != nil {
		t.Fatalf("Call error: %v", err)
	}
	if out.Sum != 7 {
		t.Fatalf("expected 7, got %d", out.Sum)
	}
}
F
function

mustJSON

Parameters

v
any

Returns

[]byte
rpc/rpc_test.go:94-97
func mustJSON(v any) []byte

{
	b, _ := json.Marshal(v)
	return b
}