rpc

package module
v1.3.3 Latest Latest
Warning

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

Go to latest
Published: Jun 19, 2023 License: MIT Imports: 7 Imported by: 0

README

RPC

Go Reference

Calling Golang backend should be simple.

  • no runtime dependencies (for TS generator there is a compile-time dependency)
  • 100% test coverage
  • simple and efficient

This project was created within one night after my frustration while writing yet another service which basically exposes DB operations as an HTTP endpoint. The problem is that most of the current approaches offer some kind of framework lock. Once you write your business logic, you will, basically, have to duplicate the method, which will just accept values from an HTTP request and proxy it to the business function.

This project allows you to expose almost any kind of structure method as HTTP endpoints.

Supports:

  • Any input and output arguments as soon as it is supported by JSON encoder/decoder
    • (optionally) First argument can be context.Context and it will be wired to request.Context()
  • Value and/or error output. Example:
    • Foo(...)
    • Foo(...) error
    • Foo(...) int64
    • Foo(...) (int64, error)

There are two main packages: rpc and jrpc. The rpc is array-based interaction (function arguments are mapped as-is as array), and jrpc package is single-payload oriented.

rpc provides less restrictions for Go side, while jrpc more friendly for UI and code-generators.

Simplest possible example:

package main

type Service struct{}

func (srv *Service) Sum(a, b int64) int64 {
	return a + b
}

func main() {
	http.Handle("/api/", http.StripPrefix("/api", rpc.New(&Service{})))
	http.ListenAndServe("127.0.0.1:8080", nil)
}

In JS side (you can just copy-and-paste)

function RPC(baseURL = "") {
    return new Proxy({}, {
        get(obj, method) {
            method = method.toLowerCase();
            if (method in obj) {
                return obj[method]
            }

            const url = baseURL + "/" + encodeURIComponent(method)
            const fn = async function () {
                const args = Array.prototype.slice.call(arguments);
                const res = await fetch(url, {
                    method: "POST",
                    body: JSON.stringify(args),
                    headers: {
                        "Content-Type": "application/json"
                    }
                })
                if (!res.ok) {
                    const errMessage = await res.text();
                    throw new Error(errMessage);
                }
                return await res.json()
            }
            return obj[method] = fn
        }
    })
}

And use it as:

const api = RPC("/api");

const amount = await api.sum(123, 456)

Alternative is to use CDN (seriously? for 375 bytes?)


<script type="module">
    import RPC from "https://cdn.jsdelivr.net/gh/reddec/rpc@1/js/rpc.min.js"

    const API = RPC("/api");
    const total = await API.sum(123, 456);
</script>

Dynamic session

In some cases you may need to prepare session, based on request: find user, authenticate it and so on. For that use Builder.

Builder will invoke factory on each request and use returned value as API session object.

For example:

package main

import (
	"net/http"
	"github.com/reddec/rpc"
)

type userSession struct {
	user string
}

func (us *userSession) Greet() string { // this will be an exported method
	return "Hello, " + us.user + "!"
}

type server struct{}

func (srv *server) newSession(r *http.Request) (*userSession, error) {
	user := r.Header.Get("X-User") // mimic real authorization
	return &userSession{
		user: user,
	}, nil
}

func main() {
	var srv server // initialize it!
	http.Handle("/api/", http.StripPrefix("/api", rpc.Builder(srv.newSession)))
	http.ListenAndServe("127.0.0.1:8080", nil)
}

Now, on call api.greet(), first will be executed newSession and then userSession.Greet

Supporting tools
RPC script

Minified version of js/rpc.js supporting script (~400B) embedded to the library and available as global variable rpc.JS and can be exposed as handler by Script function:

// ...
http.Handle("/static/js/rpc.min.js", rpc.Script())
Schema

Package schema provides simple way to generate OpenAPI 3.1 schema based on indexed methods from server. The generated object could be serialized as YAML or JSON and served as handler.

The function schema.OpenAPI uses result of Index function to generate schema and definition.

var srv Server
index := rpc.Index(&srv)
schema := schema.OpenAPI(index) // customizable by Options
// render as JSON or YAML

For the convenience use handler to export schema over HTTP. It will pre-generate and cache schema.

var srv Server
index := rpc.Index(&srv)
// ...
http.Handle("/schema", schema.Handler(index))

Documentation

Index

Constants

This section is empty.

Variables

View Source
var JS []byte

JS is embedded content of supporting script. Can be served as-is.

Functions

func Builder

func Builder[T any](factory func(r *http.Request) (T, error)) http.Handler

Builder creates new path-based, POST-only router, with custom receiver (aka session) for each request.

	type API struct {
   		User string // to be filled by Server
	}
	type Server struct {}
	func (srv *Server) newAPI(r *http.Request) (*API, error) {}

	// ...
	var server Server
	handler := Builder(server.newAPI)

Status codes

- 400 Bad Request in case payload can not be unmarshalled to arguments or number of arguments not enough. - 404 Not Found in case method is not known (case-insensitive). - 500 Internal Server Error in case method returned an error or factory returned error. Response payload will be error message (plain text) - 200 OK in case everything fine

func Index

func Index(object interface{}) map[string]*ExposedMethod

Index object's (usually pointer to struct) method. Matched public methods will be wrapped to http handler, which parses request body as JSON array and passes it to function. Result will be returned also as json.

Supported methods

Criteria for matching methods: no return values, or single return value/error, or two return values, where second one must be an error. First input argument could be context.Context which will be automatically wired from request.Context().

	Foo()                                          // OK
	Foo(ctx context.Context)                       // OK
	Foo(ctx context.Context, bar int, baz SomeObj) // OK
	Foo(bar int, baz string)                       // OK

	Foo(...) error        // OK
	Foo(...) int          // OK
	Foo(...) (int, error) // OK
    Foo(...) (int, int)   // NOT ok - last argument is not an error

Status codes

- 400 Bad Request in case payload can not be unmarshalled to arguments or number of arguments not enough. - 500 Internal Server Error in case method returned an error. Response payload will be error message (plain text) - 200 OK in case everything fine

func New

func New(object interface{}) http.Handler

New exposes matched methods of object as HTTP endpoints. It's shorthand for Router(Index(object)).

func Router

func Router(index map[string]*ExposedMethod) http.Handler

Router creates mux handler which exposes all indexed method with name as path, in lower case, and only for POST method.

	http.Handle("/api/", http.StripPrefix("/api", Router(...)))

  	MyFoo(..) -> POST /myfoo

func Script added in v1.1.0

func Script() http.Handler

Script exposes embedded JS helper as endpoint.

Types

type ExposedMethod

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

func (*ExposedMethod) Args added in v1.1.0

func (em *ExposedMethod) Args() []reflect.Type

func (*ExposedMethod) HasResponse added in v1.1.0

func (em *ExposedMethod) HasResponse() bool

func (*ExposedMethod) Response added in v1.1.0

func (em *ExposedMethod) Response() reflect.Type

func (*ExposedMethod) ServeHTTP

func (em *ExposedMethod) ServeHTTP(writer http.ResponseWriter, request *http.Request)

Directories

Path Synopsis
cmd
demo
internal
Package jrpc defines JSON-oriented, simple version of HTTP RPC
Package jrpc defines JSON-oriented, simple version of HTTP RPC
Package schema defines basic OpenAPI schema for RPC
Package schema defines basic OpenAPI schema for RPC

Jump to

Keyboard shortcuts

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