jrpc

package module
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: MIT Imports: 12 Imported by: 7

README

jrpc - rpc with json Build Go Report Card Coverage Status godoc

jrpc library provides client and server for RPC-like communication over HTTP with json encoded messages. The protocol is a somewhat simplified version of json-rpc with a single POST call sending Request json (method name and the list of parameters) moreover, receiving json Response with result data and an error string.

Usage

Plugin (server)
// Plugin wraps jrpc.Server and adds synced map to store data
type Plugin struct {
	*jrpc.Server
}

// create plugin (jrpc server) with NewServer where required param is a base url for rpc calls
plugin := jrpc.NewServer("/command")

// then add your function to map
plugin.Add("mycommand", func(id uint64, params json.RawMessage) jrpc.Response {
    return jrpc.EncodeResponse(id, "hello, it works", nil)
})

// and run server with port number value
plugin.Run(8080)

The constructor NewServer accepts two parameters:

  • API - a base url for rpc calls
  • Options - optional parameters such as timeouts, logger, limits, middlewares and so on.
    • Auth - sets basic auth credentials, accepts username and password. Auth is enforced only if both of them set to non-empty values; setting just one leaves the server serving every request unauthenticated
    • WithTimeouts - sets server timeouts, accepts a Timeouts struct with ReadHeaderTimeout, WriteTimeout, IdleTimeout and CallTimeout. CallTimeout limits the time allowed for a single call and responds with 503 if exceeded, and has to be set below WriteTimeout, otherwise the write deadline kills the connection before the 503 can be sent
    • WithLimits - defines a limit of calls/sec per client, accepts limit value in float64 type
    • WithThrottler - sets throttler middleware limiting the number of parallel calls to the server
    • WithSignature - sets server signature, accepts appName, author and version. Disabled by default
    • WithLogger - defines custom logger (e.g. lgr)
    • WithMiddlewares - sets custom middlewares list to server, accepts list of handlers with idiomatic type func(http.Handler) http.Handler

Example with options:

import (
	"time"

	"github.com/go-pkgz/jrpc"
	"github.com/go-pkgz/rest"
)

plugin := jrpc.NewServer("/command",
	jrpc.Auth("user", "password"),
	jrpc.WithTimeouts(jrpc.Timeouts{
		ReadHeaderTimeout: 5 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       10 * time.Second,
		CallTimeout:       25 * time.Second,
	}),
	jrpc.WithThrottler(120),
	jrpc.WithLimits(100),
	jrpc.WithSignature("the best plugin ever", "author", "1.0.0"),
	jrpc.WithMiddlewares(rest.Trace),
)
Application (client)
// Client makes jrpc.Client and invoke remote call
rpcClient := jrpc.Client{
    API:        "http://127.0.0.1:8080/command",
    Client:     http.Client{},
    AuthUser:   "user",
    AuthPasswd: "password",
}

resp, err := rpcClient.Call("mycommand")
var message string
if err = json.Unmarshal(*resp.Result, &message); err != nil {
    panic(err)
}
Running the example

_example has a working pair of a plugin and an application. Both are separate go modules pointing to the local jrpc with a replace directive, so no extra setup is needed beyond go 1.24 or later and a free local port 8080. Start the plugin first, in one terminal:

cd _example/plugin
go run .

It registers two handlers and listens on port 8080:

[INFO] add handler for store.save
[INFO] add handler for store.load
[INFO] listen on [::]:8080

Then run the application in another terminal:

cd _example/application
go run .

It calls the plugin three times and prints the results:

stored {TS:2025-01-12 12:00:00 +0000 UTC Value:12345} with id=54118548792
loaded {TS:2025-01-12 12:00:00 +0000 UTC Value:12345} from id=54118548792
can't load for id=something, not found

The application exits on its own, the plugin keeps listening until stopped with Ctrl-C.

Technical details

  • jrpc.Server runs on user-defined port as a regular http server
  • Server accepts a single POST request on user-defined url with Request sent as json payload
request details and an example:
 ```go
 type Request struct {
 	Method string      `json:"method"`
 	Params interface{} `json:"params,omitempty"`
 	ID     uint64      `json:"id"`
 }
 ```
 example: 
 
 ```json
   {
    "method":"test",
    "params":[123,"abc"],
    "id":1
    }
 ```
  • Params can be a struct, primitive type or slice of values, even with different types.
  • Server defines ServerFn handler function to react on a POST request. The handler provided by the user.
  • Communication between the server and the caller can be protected with basic auth. The protection is on only if both user and password set with the Auth option; with either of them empty the server responds to every request without asking for credentials.
  • Client provides a single method Call and return Response
response details:
 // Response encloses result and error received from remote server
 type Response struct {
 	Result *json.RawMessage `json:"result,omitempty"`
 	Error  string           `json:"error,omitempty"`
 	ID     uint64           `json:"id"`
 }
  • User should encode and decode json payloads on the application level, see provided examples
  • jrpc.Server doesn't support https internally (yet). If used on exposed or non-private networks, should be proxied with something providing https termination (nginx and others).

Status

The code was extracted from remark42 and still under development. Until v1.x released the API & protocol may change.

Documentation

Overview

Package jrpc implements client and server for RPC-like communication over HTTP with json encoded messages. The protocol is somewhat simplified version of json-rpc with a single POST call sending Request json (method name and the list of parameters) and receiving back json Response with "result" json and error string

Index

Examples

Constants

This section is empty.

Variables

View Source
var NoOpLogger = LoggerFunc(func(format string, args ...any) {}) //nolint

NoOpLogger logger does nothing

Functions

This section is empty.

Types

type Client

type Client struct {
	API        string      // URL to jrpc server with entrypoint, i.e. http://127.0.0.1:8080/command
	Client     http.Client // http client injected by user
	AuthUser   string      // basic auth user name, should match Server.AuthUser, optional
	AuthPasswd string      // basic auth password, should match Server.AuthPasswd, optional
	// contains filtered or unexported fields
}

Client implements remote engine and delegates all calls to remote http server if AuthUser and AuthPasswd defined will be used for basic auth in each call to server

func (*Client) Call

func (r *Client) Call(method string, args ...any) (*Response, error)

Call remote server with given method and arguments. Empty args will be ignored, single arg will be marshaled as-us and multiple args marshaled as []interface{}. Returns Response and error. Note: Response has it's own Error field, but that onw controlled by server. Returned error represent client-level errors, like failed http call, failed marshaling and so on.

Example

ExampleClient_Call shows how an application calls the remote method.

package main

import (
	"encoding/json"
	"log"

	"github.com/go-pkgz/jrpc"
)

func main() {
	rpcClient := jrpc.Client{
		API:        "http://127.0.0.1:8080/command",
		AuthUser:   "user",
		AuthPasswd: "password",
	}

	resp, err := rpcClient.Call("mycommand")
	if err != nil {
		log.Fatalf("call failed: %v", err)
	}

	var message string
	if err = json.Unmarshal(*resp.Result, &message); err != nil {
		log.Fatalf("failed to decode result: %v", err)
	}
	log.Printf("got %q", message)
}

type HandlersGroup

type HandlersGroup map[string]ServerFn

HandlersGroup alias for map of handlers

type L

type L interface {
	Logf(format string, args ...any)
}

L defined logger interface used for an optional rest logging

type LoggerFunc

type LoggerFunc func(format string, args ...any)

LoggerFunc type is an adapter to allow the use of ordinary functions as Logger.

func (LoggerFunc) Logf

func (f LoggerFunc) Logf(format string, args ...any)

Logf calls f(id)

type Option added in v0.3.0

type Option func(s *Server)

Option func type

func Auth added in v0.3.0

func Auth(user, password string) Option

Auth sets basic auth credentials, optional. Auth enforced only if both user and password set to non-empty values, otherwise the server keeps serving every request unauthenticated. Setting just one of them doesn't enable auth.

func WithLimits added in v0.3.0

func WithLimits(limit float64) Option

WithLimits sets value for client limit call/sec per client middleware

func WithLogger added in v0.3.0

func WithLogger(logger L) Option

WithLogger sets custom logger, optional

func WithMiddlewares added in v0.3.0

func WithMiddlewares(middlewares ...func(http.Handler) http.Handler) Option

WithMiddlewares sets custom middlewares list, optional

func WithSignature added in v0.3.0

func WithSignature(appName, author, version string) Option

WithSignature sets signature data for server response headers

func WithThrottler added in v0.3.0

func WithThrottler(limit int) Option

WithThrottler sets throttler middleware with specify limit value, optional

func WithTimeouts added in v0.3.0

func WithTimeouts(timeouts Timeouts) Option

WithTimeouts sets server timeout values such as ReadHeader, Write and Idle timeout, optional. If this option not defined server use default timeout values

type Request

type Request struct {
	Method string `json:"method"`           // method (function) name
	Params any    `json:"params,omitempty"` // function arguments
	ID     uint64 `json:"id"`               // unique call id
}

Request encloses method name and all params

type Response

type Response struct {
	Result *json.RawMessage `json:"result,omitempty"` // response json
	Error  string           `json:"error,omitempty"`  // optional remote (server side / plugin side) error
	ID     uint64           `json:"id"`               // unique call id, echoed Request.ID to allow calls tracing
}

Response encloses result and error received from remote server

func EncodeResponse

func EncodeResponse(id uint64, resp any, e error) Response

EncodeResponse convert anything (type interface{}) and incoming error (if any) to Response

type Server

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

Server is json-rpc server with an optional basic auth. Auth enforced only if both authUser and authPasswd set, see Auth option.

func NewServer added in v0.3.0

func NewServer(api string, options ...Option) *Server

NewServer the main constructor of server instance which pass API url and another options values

Example

ExampleNewServer shows the minimal server setup with a single method registered.

package main

import (
	"encoding/json"

	"github.com/go-pkgz/jrpc"
)

func main() {
	plugin := jrpc.NewServer("/command")

	plugin.Add("mycommand", func(id uint64, params json.RawMessage) jrpc.Response {
		return jrpc.EncodeResponse(id, "hello, it works", nil)
	})

	// blocks until Shutdown called or the server failed
	_ = plugin.Run(8080)
}
Example (Options)

ExampleNewServer_options shows the server setup with all the available options.

package main

import (
	"encoding/json"
	"log"
	"time"

	"github.com/go-pkgz/rest"

	"github.com/go-pkgz/jrpc"
)

func main() {
	plugin := jrpc.NewServer("/command",
		jrpc.Auth("user", "password"),
		jrpc.WithTimeouts(jrpc.Timeouts{
			ReadHeaderTimeout: 5 * time.Second,
			WriteTimeout:      30 * time.Second,
			IdleTimeout:       10 * time.Second,
			CallTimeout:       25 * time.Second,
		}),
		jrpc.WithThrottler(120),
		jrpc.WithLimits(100),
		jrpc.WithSignature("the best plugin ever", "author", "1.0.0"),
		jrpc.WithLogger(jrpc.LoggerFunc(log.Printf)),
		jrpc.WithMiddlewares(rest.Trace),
	)

	plugin.Add("mycommand", func(id uint64, params json.RawMessage) jrpc.Response {
		return jrpc.EncodeResponse(id, "hello, it works", nil)
	})

	_ = plugin.Run(8080)
}

func (*Server) Add

func (s *Server) Add(method string, fn ServerFn)

Add method handler. Handler will be called on matching method (Request.Method)

func (*Server) Group

func (s *Server) Group(prefix string, m HandlersGroup)

Group of handlers with common prefix, match on group.method

func (*Server) Run

func (s *Server) Run(port int) error

Run http server on given port, blocks until Shutdown called or the server failed

func (*Server) Shutdown

func (s *Server) Shutdown() error

Shutdown http server

type ServerFn

type ServerFn func(id uint64, params json.RawMessage) Response

ServerFn handler registered for each method with Add or Group. Implementations provided by consumer and defines response logic.

type Timeouts added in v0.3.0

type Timeouts struct {
	ReadHeaderTimeout time.Duration // amount of time allowed to read request headers
	WriteTimeout      time.Duration // max duration before timing out writes of the response
	IdleTimeout       time.Duration // max amount of time to wait for the next request when keep-alive enabled
	CallTimeout       time.Duration // max time allowed to finish the call, optional
}

Timeouts includes values and timeouts for the server

Jump to

Keyboard shortcuts

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