zenrpc

repository·master·Indexed 41 days ago

https://github.com/semrush/zenrpc

A high-performance JSON-RPC 2.0 server implementation for Go. It utilizes `go generate` instead of runtime reflection to map service structs to RPC methods, providing improved performance and Service Mapping Description (SMD) support. It supports transport via HTTP and WebSockets, includes Prometheus metrics and logging middleware, and provides tools like SMDBox for API browsing.

Tokens
4.2K
Snippets
19
Records
24
Agent score
51%

What's inside zenrpc

  1. Use magic comments to define default values and metadata

    master

    You can use special comments to provide metadata for the SMD (Service Mapping Description) schema, such as default values for parameters or error codes.

    Method comments:

    • //zenrpc:<method parameter>[=<default value>][whitespaces<description>]
    • //zenrpc:<error code>[whitespaces<description>]
    • //zenrpc:return[whitespaces<description>]

    Struct comments:

    • type MyService struct {} //zenrpc (marks the struct as an RPC service)
    // Pow returns x**y. If Exp is not set then default value is 2.
    //zenrpc:exp=2
    func (as ArithService) Pow(base float64, exp float64) float64 {
        return math.Pow(base, exp)
    }
  2. Register and run a zenrpc server

    master

    To start a server, create a new server instance using zenrpc.NewServer, register your services, and handle the resulting object via an HTTP server.

    • Use zenrpc.Options{ExposeSMD: true} to enable SMD support (useful for API browsing).
    • Use rpc.Register(namespace, serviceInstance) to map a namespace to a service. Use an empty string "" for a public namespace.
    • Use rpc.Use(middleware) to add server middleware (e.g., zenrpc.Logger).
    import (
    	"log"
    	"net/http"
    	"github.com/semrush/zenrpc/v2"
    )
    
    func main() {
    	rpc := zenrpc.NewServer(zenrpc.Options{ExposeSMD: true})
    	rpc.Register("arith", ArithService{})
    	rpc.Use(zenrpc.Logger(log.New(os.Stderr, "", log.LstdFlags)))
    
    	http.Handle("/", rpc)
    	log.Fatal(http.ListenAndServe(":9999", nil))
    }
  3. Install and set up zenrpc

    master

    To use zenrpc as a JSON-RPC 2.0 server, follow these steps:

    1. Install the generator: go get github.com/semrush/zenrpc/v2/zenrpc.
    2. Import github.com/semrush/zenrpc/v2 into your project.
    3. Define your RPC service by either:
      • Embedding zenrpc.Service into your service struct.
      • Adding the //zenrpc trailing comment to your service struct definition.
    4. Add a //go:generate zenrpc directive to your code.
    5. Run go generate to trigger the code generation (this replaces the need for reflection).

    Your service struct represents an RPC namespace.

    // Example service definition
    type MyService struct {
        zenrpc.Service 
    }
    
    //go:generate zenrpc
  4. Accepted method signatures for RPC methods

    master

    When defining methods on your service struct, zenrpc supports the following signature patterns. Methods can return values (including pointers) and errors (either standard error or *zenrpc.Error).

    • func(Service) Method([args]) (<value>, <error>)
    • func(Service) Method([args]) <value>
    • func(Service) Method([args]) <error>
    • func(Service) Method([args])
  5. Retrieve RPC request information from context

    master

    If your method accepts a context.Context, you can use zenrpc.RequestFromContext(ctx) to access information about the incoming JSON-RPC request, such as the client's host.

    func (as ArithService) Sum(ctx context.Context, a, b int) (bool, *zenrpc.Error) {
    	r, _ := zenrpc.RequestFromContext(ctx)
    	// r contains request metadata like r.Host
    	return true, zenrpc.NewStringError(a+b, r.Host)
    }
  6. Configure zenrpc Server Options

    master

    The Options struct allows fine-grained control over the server behavior:

    OptionDescription
    BatchMaxLenMaximum number of requests allowed in a single batch. Defaults to 10 if not set.
    TargetURLThe RPC endpoint URL. Defaults to /.
    ExposeSMDIf true, the SMD schema is exposed via a ?smd GET parameter.
    DisableTransportChecksDisables Content-Type and HTTP method checks. Use only for development.
    AllowCORSIf true, adds Access-Control-Allow-Origin: * header.
    UpgraderCustom *websocket.Upgrader for WebSocket transport. If nil, default options are used.
    HideErrorDataFieldIf true, removes the data field from error responses.
  7. Populate a Response using Set()

    master

    The Set(v interface{}, er ...error) method on the Response struct is a convenience method to populate either the Result or the Error field based on the provided arguments.

    Behavior:

    • If an error is passed in er (or if v itself is a non-nil error), the Response.Error field is populated. If the error is not a *zenrpc.Error, it is wrapped as an InternalError.
    • If no error is provided, v is marshaled into Response.Result.
    • If marshaling v fails, the Response.Error is set to a ServerError.
    var resp zenrpc.Response
    
    // Success case
    resp.Set(map[string]int{"foo": 1})
    
    // Error case using standard error
    resp.Set(nil, errors.New("something went wrong"))
    
    // Error case using a specific zenrpc.Error
    customErr := zenrpc.NewStringError(zenrpc.InvalidParams, "bad param")
    resp.Set(nil, customErr)
  8. Get error text with ErrorMsg()

    master

    The ErrorMsg(code int) function returns the standard human-readable text associated with a specific JSON-RPC error code (e.g., passing MethodNotFound returns "Method not found").

    msg := zenrpc.ErrorMsg(zenrpc.MethodNotFound) // "Method not found"
  9. Use the Metrics middleware for Prometheus

    master

    The Metrics middleware tracks RPC request performance and errors using Prometheus. It automatically registers two metrics with the Prometheus registry:

    1. [appName]_rpc_error_requests_count: A counter tracking the number of error requests, partitioned by method and code.
    2. [appName]_rpc_responses_duration_seconds: A summary tracking the response duration, partitioned by method and code.

    If no appName is provided, it defaults to zenrpc. The method label includes the namespace if one is present in the context.

    // Example usage of Metrics middleware
    server := zenrpc.NewServer(
        zenrpc.WithMiddleware(zenrpc.Metrics("my_app")),
    )
  10. Initialize a new zenrpc Server

    master

    Use NewServer(opts Options) to create a new JSON-RPC 2.0 server instance. You can configure the server using the Options struct to control batch limits, target URLs, CORS, and SMD exposure.

    server := zenrpc.NewServer(zenrpc.Options{
        BatchMaxLen: 20,
        TargetURL:   "/rpc",
        AllowCORS:   true,
        ExposeSMD:   true,
    })
  11. Use SMDBoxHandler for the SMDBox web app

    master

    The SMDBoxHandler is a specialized HTTP handler that serves a minimal HTML page. This page loads the smdbox web application (via CDN), which is designed to browse your JSON-RPC API and perform test API calls using the SMD (Service Metadata) schema.

    http.HandleFunc("/smdbox", zenrpc.SMDBoxHandler)
    http.ListenAndServe(":8080", nil)
  12. Use middleware in zenrpc

    master

    Middleware is implemented using the MiddlewareFunc type. A middleware wraps an InvokeFunc and returns a new InvokeFunc. You can register multiple middlewares using the Use(...MiddlewareFunc) method. Middlewares are executed in the order they are registered.

    func MyMiddleware(next zenrpc.InvokeFunc) zenrpc.InvokeFunc {
        return func(ctx context.Context, method string, params json.RawMessage) zenrpc.Response {
            // Pre-processing logic
            resp := next(ctx, method, params)
            // Post-processing logic
            return resp
        }
    }
    
    server.Use(MyMiddleware)