kit

package module
v0.12.7 Latest Latest
Warning

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

Go to latest
Published: Apr 18, 2024 License: BSD-3-Clause Imports: 21 Imported by: 30

README

Go Report Card GitHub release (latest by date including pre-releases) Codecov Status

Kit

RonyKit is a library written in Go (Golang), provides the abstraction layer for creating an API server. By defining separate components for each task, you are almost free to develop your API server using RPC (Proto/JSON/...), REST, GraphQL or even with your in-house defined protocol. However, some components are implemented in the 'std' which make it easy to use RonyKit out of the box. In RonyKIT, I wanted to make it more flexible but still very performant. I will maintain two bundles one based on fasthttp and the other provides only rpc over websocket based on two very fast libraries gobwas and gnet, however you are not limited to use those, and can build your own Bundles. To have a better understanding of how it is going to work please check examples folder. in simple-rest-server it shows a very simple hello world app. in mixed-jsonrpc-rest it shows how you can write your services once but use them with multiple formats like REST or RPC.

Installation

To install RonyKIT package, you need to install Go and set your Go workspace first.

First need to installGo (version 1.17+ is required), then you can use the below Go command to install RonyKIT.

$ go get -u github.com/clubpay/ronykit/kit/...

Quick start

You can find some sample codes in the Examples folder.

Define Services and Contracts

In RonyKIT(Kit) you better to define the description of your services and their contracts using desc package, and it provides you many helper methods. Although you can create your concrete implementations of kit.Service and kit.Contract, but we strongly recommend you to use desc package.

When you develop a handler with Kit package you need to think about the input and output of your API, and define these DTOs by defining appropriate structs.

For example, if you want to implement an Echo handler you can write your handler like this:

package main

import "github.com/clubpay/ronykit/kit"

type EchoRequest struct {
    ID string `json:"Id"`
    Timestamp int64 `json:"timestamp"`
}

type EchoResponse struct {
    ID string `json:"Id"`
}

func echoHandler(ctx *kit.Context) {
    req := ctx.In().GetMsg().(*EchoRequest)

    ctx.In().
        Reply().
        SetMsg(&EchoResponse{ID: req.ID}).
        Send()
}

Then you can write the service descriptor to define how your handler is going to be accessed from the Gateway of your server. For example if you have an HTTP Gateway and you need your handler to be accessible using REST API, you can define your service descriptor like this:

package main

import (
    "github.com/clubpay/ronykit/kit"
    "github.com/clubpay/ronykit/kit/desc"
    "github.com/clubpay/ronykit/std/gateways/fasthttp"
)


func MyServiceDesc() *desc.Service {
	return desc.NewService("MyServiceName").
		SetEncoding(kit.JSON).
		AddContract(
			desc.NewContract().
				SetInput(&EchoRequest{}).	// This tells that we expect the incoming request resolves to EchoRequest struct
				SetOutput(&EchoResponse{}). // This tells that we expect the outgoing response resolves to EchoResponse struct
				AddSelector(fasthttp.GET("/echo/:Id")). // This tells that we want to use echoHandler for GET requests with /echo/:Id path
				AddSelector(fasthttp.POST("/echo")). // This tells that we want to use echoHandler for POST requests with /echo path
				SetHandler(echoHandler), // This tells that we want to use echoHandler as the handler of this contract
		)
}

In the ServiceDescriptor we defined two REST endpoints which our Handler would be served. RonyKIT's EdgeServer tries its best to fill the input struct (i.e., EchoRequest) from the parameters: urlParam, queryParam or request's body. If you have a more complex case, you can also add your own custom decoder when you add your selector to the contract.

The last step is to set up our EdgeServer and bind our desired Gateway bundle and the MyServiceDesc. The following code shows how we can do that:

package main

import (
	"github.com/clubpay/ronykit/kit"
	"github.com/clubpay/ronykit/kit/desc"
	"github.com/clubpay/ronykit/std/gateways/fasthttp"
)

type EchoRequest struct {
	ID string `json:"Id"`
	Timestamp int64 `json:"timestamp"`
}

type EchoResponse struct {
	ID string `json:"Id"`
}

func echoHandler(ctx *kit.Context) {
	req := ctx.In().GetMsg().(*EchoRequest)

	ctx.In().
		Reply().
		SetMsg(&EchoResponse{ID: req.ID}).
		Send()
}


var MyServiceDesc desc.ServiceDescFunc = func() *desc.Service {
	return desc.NewService("MyServiceName").
		SetEncoding(kit.JSON).
		AddContract(
			desc.NewContract().
				SetInput(&EchoRequest{}).
				SetOutput(&EchoResponse{}).
				AddSelector(fasthttp.GET("/echo/:Id")).
				AddSelector(fasthttp.POST("/echo")).
				SetHandler(echoHandler),
		)
}

func main() {
	defer kit.NewServer(
		kit.WithGateway(
			fasthttp.MustNew(
				fasthttp.Listen(":80"),
			),
		),
		kit.WithServiceDesc(MyServiceDesc()),
	).
		Start(context.TODO()).
		PrintRoutes(os.Stdout).
		Shutdown(context.TODO(), os.Kill, os.Interrupt)
}

KIT Components

1. Handler

Handler is the function/method that accepts kit.Context as argument, and it will be called based on the selector that is defined in the desc.Service.

2. Contract

Contract defines a business use-case that is very similar to what a function does, just it will be triggered based on the selectors that we define the ServiceDescriptor.

3. Service

One or more Contracts that are doing some operations in a similar domain could be grouped in one Service. Each EdgeServer can have one or more service attached.

4. Gateway

Gateways handle the endpoints of the service. For example, if you need to provide REST apis you can use one of the standard gateway bundles : fasthttp or silverhttp For advanced cases you can develop your own Gateway, but it is not necessary for most cases.

5. Cluster

Cluster defines the relation between different instances of the EdgeServer. This bundle is optional but if attached to EdgeServer then in multi instance cases it can help and provides some facilities to share data between different instances of your EdgeServer.

6. EdgeServer

EdgeServer is the main component of the RonyKIT which glues different components together to make it a working server.




KIT Storage layers

When we develop API server usually we need to store data with different lifecycles. Sometimes we need to store data that lives between different handlers, in some cases, we need to keep the data between different requests of a connection. (i.e. for websocket connection we want to keep some data for next request from this connection). Basically in RonyKIT we provide 4 layers of storage:

Layer Lifecycle
Context in the request, but available in all the handlers
Connection in the connection, for REST apis usually the lifecycle of Connection storage and Context are same, but for websocket data will be available until websocket connection is active
Local this is shared between different contracts, services and is in the local memory of the server
Cluster this is shared between different instances of the EdgeServer. This is enabled ONLY if a Cluster bundle is attached

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNoHandler                     = errors.New("handler is not set for request")
	ErrWriteToClosedConn             = errors.New("write to closed connection")
	ErrDecodeIncomingMessageFailed   = errors.New("decoding the incoming message failed")
	ErrEncodeOutgoingMessageFailed   = errors.New("encoding the outgoing message failed")
	ErrDecodeIncomingContainerFailed = errors.New("decoding the incoming container failed")
	ErrDispatchFailed                = errors.New("dispatch failed")
	ErrPreflight                     = errors.New("preflight request")
)
View Source
var (
	ErrSouthBridgeDisabled        = errors.New("south bridge is disabled")
	ErrWritingToClusterConnection = errors.New("writing to cluster connection is not possible")
)
View Source
var (
	ErrClusterNotSet          = errors.New("cluster is not set")
	ErrClusterMemberNotFound  = errors.New("cluster member not found")
	ErrClusterMemberNotActive = errors.New("cluster member not active")
)
View Source
var (
	Undefined = Encoding{}
	JSON      = Encoding{/* contains filtered or unexported fields */}
	Proto     = Encoding{/* contains filtered or unexported fields */}
	MSG       = Encoding{/* contains filtered or unexported fields */}
)

Functions

func CastRawMessage added in v0.10.40

func CastRawMessage[M Message](rawMsg RawMessage) (*M, error)

func MarshalMessage

func MarshalMessage(m Message) ([]byte, error)

func SetCustomMarshaler added in v0.10.33

func SetCustomMarshaler(mm MessageMarshaler)

func UnmarshalMessage

func UnmarshalMessage(data []byte, m Message) error

Types

type Cluster added in v0.8.5

type Cluster interface {
	// Start starts the gateway to accept connections.
	Start(ctx context.Context) error
	// Shutdown shuts down the gateway gracefully.
	Shutdown(ctx context.Context) error
	// Subscribe registers the southBridge as the delegate of the backend.
	Subscribe(id string, d ClusterDelegate)
	// Publish sends the data to the other instance identified by id.
	Publish(id string, data []byte) error
	// Subscribers return the list of the instance ids.
	Subscribers() ([]string, error)
}

type ClusterDelegate

type ClusterDelegate interface {
	OnMessage(data []byte) error
}

type ClusterStore added in v0.9.1

type ClusterStore interface {
	// Set creates/updates a key-value pair in the cluster
	Set(key, value string, ttl time.Duration) error
	// Delete deletes the key-value pair from the cluster
	Delete(key string) error
	// Get returns the value bind to the key
	Get(key string) (string, error)
	// Scan scans through the keys which have the prefix.
	// If callback returns `false`, then the scan is aborted.
	Scan(prefix string, cb func(string) bool) error
	ScanWithValue(prefix string, cb func(string, string) bool) error
}

ClusterStore identifies a key-value store, which its key-value pairs are shared between different instances of the cluster. Also, the value type is only string.

type ClusterWithStore added in v0.11.39

type ClusterWithStore interface {
	// Store returns a shared key-value store between all instances.
	Store() ClusterStore
}

type Conn

type Conn interface {
	ConnID() uint64
	ClientIP() string
	Write(data []byte) (int, error)
	Stream() bool
	Walk(fn func(key string, val string) bool)
	Get(key string) string
	Set(key string, val string)
}

Conn represents a connection between EdgeServer and client.

type ConnDelegate added in v0.9.23

type ConnDelegate interface {
	// OnOpen must be called whenever a new connection is established.
	OnOpen(c Conn)
	// OnClose must be called whenever the connection is gone.
	OnClose(connID uint64)
}

type Context

type Context struct {
	utils.SpinLock
	// contains filtered or unexported fields
}

func (*Context) AddModifier

func (ctx *Context) AddModifier(modifiers ...ModifierFunc)

AddModifier adds one or more modifiers to the context which will be executed on each outgoing Envelope before writing it to the wire.

func (*Context) ClusterID added in v0.8.5

func (ctx *Context) ClusterID() string

func (*Context) ClusterMembers added in v0.8.8

func (ctx *Context) ClusterMembers() ([]string, error)

func (*Context) ClusterStore added in v0.9.1

func (ctx *Context) ClusterStore() ClusterStore

ClusterStore returns a key-value store which is shared between different instances of the cluster.

NOTE: If you don't set any Cluster for your EdgeServer, then this method will panic. NOTE: If the cluster doesn't support key-value store, then this method will return nil.

func (*Context) Conn

func (ctx *Context) Conn() Conn

Conn returns the underlying connection

func (*Context) Context

func (ctx *Context) Context() context.Context

Context returns a context.Background which can be used a reference context for other context-aware function calls.

func (*Context) ContractID

func (ctx *Context) ContractID() string

func (*Context) Error

func (ctx *Context) Error(err error) bool

Error is useful for some kind of errors that you are not going to return it to the connection, or you want to use its side effect for logging, monitoring, etc. This will call your ErrHandlerFunc. The boolean result indicates if 'err' was an actual error.

func (*Context) Exists

func (ctx *Context) Exists(key string) bool

func (*Context) Get

func (ctx *Context) Get(key string) any

func (*Context) GetBytes

func (ctx *Context) GetBytes(key string, defaultValue []byte) []byte

func (*Context) GetInt32

func (ctx *Context) GetInt32(key string, defaultValue int32) int32

func (*Context) GetInt64

func (ctx *Context) GetInt64(key string, defaultValue int64) int64

func (*Context) GetStatusCode

func (ctx *Context) GetStatusCode() int

func (*Context) GetStatusText

func (ctx *Context) GetStatusText() string

func (*Context) GetString

func (ctx *Context) GetString(key string, defaultValue string) string

func (*Context) GetUint32

func (ctx *Context) GetUint32(key string, defaultValue uint32) uint32

func (*Context) GetUint64

func (ctx *Context) GetUint64(key string, defaultValue uint64) uint64

func (*Context) HasCluster added in v0.9.27

func (ctx *Context) HasCluster() bool

HasCluster returns true if the cluster is set for this EdgeServer.

func (*Context) HasError

func (ctx *Context) HasError() bool

HasError returns true if there is an error set by calling Error method.

func (*Context) In

func (ctx *Context) In() *Envelope

In returns the incoming Envelope which received from the connection. You **MUST NOT** call Send method of this Envelope. If you want to return a message/envelope to connection, use Out or OutTo methods of the Context

func (*Context) InputRawData added in v0.10.13

func (ctx *Context) InputRawData() RawMessage

InputRawData returns the raw input data from the connection. This slice is not valid after this Context lifetime. If you need to use it after the Context lifetime, you need to copy it. You should not use this method in your code, ONLY if you need it for debugging.

func (*Context) IsREST added in v0.9.27

func (ctx *Context) IsREST() bool

func (*Context) Limited

func (ctx *Context) Limited() *LimitedContext

Limited returns a LimitedContext. This is useful when you do not want to give all capabilities of the Context to some other function/method.

func (*Context) LocalStore added in v0.9.1

func (ctx *Context) LocalStore() Store

LocalStore returns a local store which could be used to share some key-values between different requests. If you need to have a shared key-value store between different instances of your server then you need to use ClusterStore method.

func (*Context) Next

func (ctx *Context) Next()

func (*Context) Out

func (ctx *Context) Out() *Envelope

Out generate a new Envelope which could be used to send data to the connection.

func (*Context) OutTo

func (ctx *Context) OutTo(c Conn) *Envelope

OutTo is similar to Out except that it lets you send your envelope to another connection. This is useful for scenarios where you want to send a cross-client message. For example, in a fictional chat server, you want to pass a message from client A to client B.

func (*Context) PresetHdr

func (ctx *Context) PresetHdr(k, v string)

PresetHdr sets the common header key-value pairs, so in Out method we do not need to repeatedly set those. This method is useful for some cases if we need to update the header in some middleware before the actual response is prepared. If you only want to set the header for an envelope, you can use Envelope.SetHdr method instead.

func (*Context) PresetHdrMap

func (ctx *Context) PresetHdrMap(hdr map[string]string)

PresetHdrMap sets the common header key-value pairs so in Out method we do not need to repeatedly set those. Please refer to PresetHdr for more details

func (*Context) RESTConn added in v0.9.27

func (ctx *Context) RESTConn() RESTConn

RESTConn returns the underlying REST connection. It panics if the underlying connection does not implement RESTConn interface. If you want to be safe when calling this method you can use IsREST method: Example:

 if ctx.IsREST() {
		conn := ctx.RESTConn()
 }

func (*Context) Route

func (ctx *Context) Route() string

func (*Context) ServiceName

func (ctx *Context) ServiceName() string

func (*Context) Set

func (ctx *Context) Set(key string, val any) *Context

func (*Context) SetStatusCode

func (ctx *Context) SetStatusCode(code int)

SetStatusCode set the connection status. It **ONLY** works if the underlying connection is a RESTConn connection.

func (*Context) SetUserContext

func (ctx *Context) SetUserContext(userCtx context.Context)

func (*Context) StopExecution

func (ctx *Context) StopExecution()

StopExecution stops the execution of the next handlers. When you call this in your handler, any other middleware that is not executed yet will be skipped over.

func (*Context) Walk

func (ctx *Context) Walk(f func(key string, val any) bool)

type Contract

type Contract interface {
	// ID identifies the contract. This MUST be unique per Service. This MUST NOT be a runtime
	// random number. Since this is used in RemoteExecute method of ClusterMember to execute the
	// right set of handlers on remote EdgeServer.
	ID() string
	// RouteSelector returns a RouteSelector function which selects this Contract based on the
	// client requests.
	RouteSelector() RouteSelector
	// EdgeSelector returns an EdgeSelectorFunc function which selects the EdgeServer instance
	// that the request should forward to for execution.
	EdgeSelector() EdgeSelectorFunc
	Encoding() Encoding
	Input() Message
	Output() Message
	Handlers() []HandlerFunc
	Modifiers() []ModifierFunc
}

Contract defines the set of Handlers based on the Query. Query is different per bundles, hence, this is the implementor's task to make sure return correct value based on 'q'. In other words, Contract 'r' must return valid response for 'q's required by Gateway 'gw' in order to be usable by Gateway 'gw' otherwise it panics.

func WrapContract

func WrapContract(c Contract, wrappers ...ContractWrapper) Contract

WrapContract wraps a contract, this is useful for adding middlewares to the contract. Some middlewares like OpenTelemetry, Logger, ... could be added to the contract using this function.

type ContractWrapper

type ContractWrapper interface {
	Wrap(c Contract) Contract
}

ContractWrapper is like an interceptor which can add Pre- and Post- handlers to all the Contracts of the Contract.

type ContractWrapperFunc

type ContractWrapperFunc func(Contract) Contract

ContractWrapperFunc implements ContractWrapper interface.

func (ContractWrapperFunc) Wrap

func (sw ContractWrapperFunc) Wrap(svc Contract) Contract

type EdgeSelectorFunc

type EdgeSelectorFunc func(ctx *LimitedContext) (string, error)

EdgeSelectorFunc returns the target EdgeServer's ID in the Cluster. If you have multiple instances of the EdgeServer, and you want to forward some requests to a specific instance, you can set up this function in desc.Contract's SetCoordinator method, then, the receiving EdgeServer will detect the target instance by calling this function and forwards the request to the returned instance. From the external client point of view this forwarding request is not observable.

type EdgeServer

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

EdgeServer is the main component of the kit. It glues all other components of the app to each other.

func NewServer

func NewServer(opts ...Option) *EdgeServer

func (*EdgeServer) PrintRoutes

func (s *EdgeServer) PrintRoutes(w io.Writer) *EdgeServer

func (*EdgeServer) PrintRoutesCompact added in v0.10.31

func (s *EdgeServer) PrintRoutesCompact(w io.Writer) *EdgeServer

func (*EdgeServer) Shutdown

func (s *EdgeServer) Shutdown(ctx context.Context, signals ...os.Signal)

Shutdown stops the server. If there is no signal input, then it shut down the server immediately. However, if there is one or more signals added in the input argument, then it waits for any of them to trigger the shutdown process. Since this is a graceful shutdown, it waits for all flying requests to complete. However, you can set the maximum time that it waits before forcefully shutting down the server, by WithShutdownTimeout option. The Default value is 1 minute.

func (*EdgeServer) Start

func (s *EdgeServer) Start(ctx context.Context) *EdgeServer

Start registers services in the registered bundles and start the bundles.

type EmptyMessage added in v0.10.29

type EmptyMessage struct{}

EmptyMessage is a special kind of Message which is empty.

type Encoding

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

Encoding defines the encoding of the messages which will be sent/received. Gateway implementor needs to call the correct method based on the encoding value.

func CustomEncoding

func CustomEncoding(tag string) Encoding

func (Encoding) Tag

func (enc Encoding) Tag() string

type Envelope

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

Envelope is an envelope around Message in RonyKIT. Envelopes are created internally by the RonyKIT framework, and provide the abstraction which Bundle implementations could take advantage of. For example in std/fasthttp Envelope headers translate from/to http request/response headers.

func (*Envelope) GetHdr

func (e *Envelope) GetHdr(key string) string

func (*Envelope) GetID

func (e *Envelope) GetID() string

func (*Envelope) GetMsg

func (e *Envelope) GetMsg() Message

func (*Envelope) IsOutgoing added in v0.11.7

func (e *Envelope) IsOutgoing() bool

IsOutgoing returns `true` if this Envelope is sending from Server to Client.

func (*Envelope) Reply

func (e *Envelope) Reply() *Envelope

Reply creates a new envelope which it's id is

func (*Envelope) Send

func (e *Envelope) Send()

Send writes the envelope to the connection based on the Gateway specification. You **MUST NOT** use the Envelope after calling this method. You **MUST NOT** call this function more than once. You **MUST NOT** call this method on incoming envelopes. You **MUST NOT** call this on the Envelope that you get from Context.In

func (*Envelope) SetHdr

func (e *Envelope) SetHdr(key, value string) *Envelope

func (*Envelope) SetHdrMap

func (e *Envelope) SetHdrMap(kv map[string]string) *Envelope

func (*Envelope) SetHdrWalker

func (e *Envelope) SetHdrWalker(walker Walker) *Envelope

func (*Envelope) SetID

func (e *Envelope) SetID(id string) *Envelope

func (*Envelope) SetMsg

func (e *Envelope) SetMsg(msg Message) *Envelope

func (*Envelope) WalkHdr

func (e *Envelope) WalkHdr(f func(key string, val string) bool) *Envelope

type EnvelopeHdr

type EnvelopeHdr map[string]string

type ErrHandlerFunc added in v0.9.8

type ErrHandlerFunc = func(ctx *Context, err error)

ErrHandlerFunc is called when an error happens in internal layers. NOTICE: ctx could be nil, make sure you do nil-check before calling its methods.

type ErrorMessage

type ErrorMessage interface {
	GetCode() int
	GetItem() string
	Message
	error
}

ErrorMessage is a special kind of Message which is also an error.

type ExecuteArg

type ExecuteArg struct {
	ServiceName string
	ContractID  string
	Route       string
}

ExecuteArg is used by bundle developers, if they want to build a Gateway or Cluster to be used with EdgeServer. If you are the user of the framework, you don't need this. In general ExecuteArg carrys information about the Context that is running, for example it identifies that which Contract from which Service is this Context for. Route identifies which RouteSelector was this request comes from.

type Gateway

type Gateway interface {
	// Start starts the gateway to accept connections.
	Start(ctx context.Context, cfg GatewayStartConfig) error
	// Shutdown shuts down the gateway gracefully.
	Shutdown(ctx context.Context) error
	// Register registers the route in the Bundle. This is how Bundle gets information
	// about the services and their contracts.
	Register(
		serviceName, contractID string, enc Encoding, sel RouteSelector, input Message,
	)
	// Subscribe will be called by the EdgeServer. These delegate functions
	// must be called by the Gateway implementation. In other words, Gateway communicates
	// with EdgeServer through the GatewayDelegate methods.
	//
	// NOTE: This func will be called only once and before calling Start function.
	Subscribe(d GatewayDelegate)
	// Dispatch receives the messages from external clients and runs the execFunc with appropriate
	// arguments. The user of the Gateway does not need to implement this. If you are using some
	// standard bundles like std/gateway/fasthttp or std/gateway/fastws, then all the implementation
	// is taken care of.
	Dispatch(ctx *Context, in []byte) (ExecuteArg, error)
}

Gateway is the main component of the EdgeServer. Without Gateway, the EdgeServer is not functional. You can use some standard bundles in std/bundle path. However, if you need special handling of communication between your server and clients, you are free to implement your own Gateway. If you are a bundle developer need to work with interface otherwise, you don't need to know much about this interface.

type GatewayDelegate

type GatewayDelegate interface {
	ConnDelegate
	// OnMessage must be called whenever a new message arrives.
	OnMessage(c Conn, wf WriteFunc, msg []byte)
}

GatewayDelegate is the delegate that connects the Gateway to the rest of the system.

type GatewayStartConfig added in v0.8.9

type GatewayStartConfig struct {
	ReusePort bool
}

type HandlerFunc

type HandlerFunc = func(ctx *Context)

HandlerFunc is a function that will execute code in its context. If there is another handler set in the path, by calling ctx.Next you can move forward and then run the rest of the code in your handler.

type HandlerFuncChain

type HandlerFuncChain []HandlerFunc

type IncomingRPCContainer

type IncomingRPCContainer interface {
	GetID() string
	// Unmarshal deserialize the received payload.
	Unmarshal(data []byte) error
	// ExtractMessage extracts the payload of the container to the input message.
	ExtractMessage(m Message) error
	// GetHdr to read header. This method is used by RonyKIT to fill Envelope's header fields.
	GetHdr(key string) string
	// GetHdrMap returns all the header key-values.
	GetHdrMap() map[string]string
	// Release will be called when we finished our work with it.
	Release()
}

IncomingRPCContainer defines the behavior of RPC message envelopes. Basically in RPC communication the actual message should be contained in some kind of container. This interface defines a set of guidelines for the implementation of those containers. The user of the RonyKIT does not need to use this, and it is basically useful for Bundle developers. Although even Bundle developers are not forced to use this interface in their implementation, but they are encouraged to.

Example implementations: common.SimpleIncomingJSONRPC

type IncomingRPCFactory

type IncomingRPCFactory func() IncomingRPCContainer

type JSONMarshaler

type JSONMarshaler interface {
	MarshalJSON() ([]byte, error)
}

type JSONMessage added in v0.10.38

type JSONMessage = json.RawMessage

type JSONUnmarshaler

type JSONUnmarshaler interface {
	UnmarshalJSON(data []byte) error
}

type LimitedContext

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

LimitedContext is a wrapper around Context, which limit the capabilities of the original Context. This is useful in cases where we need to pass the Context, but we do not want to give access to all the exposed methods. For example, this is used in EdgeSelectorFunc.

func (*LimitedContext) ClusterID added in v0.8.5

func (ctx *LimitedContext) ClusterID() string

func (*LimitedContext) ClusterMembers added in v0.8.8

func (ctx *LimitedContext) ClusterMembers() ([]string, error)

func (*LimitedContext) ClusterStore added in v0.9.1

func (ctx *LimitedContext) ClusterStore() ClusterStore

func (*LimitedContext) Conn

func (ctx *LimitedContext) Conn() Conn

Conn returns the underlying connection

func (*LimitedContext) Context

func (ctx *LimitedContext) Context() context.Context

Context returns a context.WithCancel, which can be used a reference context for other context-aware function calls. This context will be canceled at the end of Context lifetime.

func (*LimitedContext) In

func (ctx *LimitedContext) In() *Envelope

In returns the incoming Envelope which contains the client's request.

func (*LimitedContext) IsREST added in v0.11.32

func (ctx *LimitedContext) IsREST() bool

func (*LimitedContext) Route

func (ctx *LimitedContext) Route() string

func (*LimitedContext) ServiceName

func (ctx *LimitedContext) ServiceName() string

func (*LimitedContext) SetHdr

func (ctx *LimitedContext) SetHdr(k, v string)

SetHdr sets the common header key-value pairs, so in Out method we do not need to repeatedly set those. If you only want to set the header for an envelope, you can use Envelope.SetHdr method instead.

func (*LimitedContext) SetHdrMap

func (ctx *LimitedContext) SetHdrMap(hdr map[string]string)

SetHdrMap sets the common header key-value pairs, so in Out method we do not need to repeatedly set those.

func (*LimitedContext) SetUserContext added in v0.9.6

func (ctx *LimitedContext) SetUserContext(userCtx context.Context)

type LimitedHandlerFunc added in v0.9.5

type LimitedHandlerFunc = func(ctx *LimitedContext)

type Logger

type Logger interface {
	Debugf(format string, args ...any)
	Errorf(format string, args ...any)
}

type Marshaler

type Marshaler interface {
	Marshal() ([]byte, error)
}

type Message

type Message any

Message is a generic interface for all messages. Message MUST BE serializable. It could implement one or many of the following interfaces:

  • Marshaler
  • Unmarshaler
  • JSONMarshaler
  • JSONUnmarshaler
  • ProtoMarshaler
  • ProtoUnmarshaler
  • encoding.BinaryMarshaler
  • encoding.BinaryUnmarshaler
  • encoding.TextMarshaler
  • encoding.TextUnmarshaler

type MessageFactoryFunc

type MessageFactoryFunc func() Message

func CreateMessageFactory

func CreateMessageFactory(in Message) MessageFactoryFunc

type MessageMarshaler added in v0.10.33

type MessageMarshaler interface {
	Marshal(m any) ([]byte, error)
	Unmarshal(data []byte, m any) error
}

type ModifierFunc added in v0.9.8

type ModifierFunc func(envelope *Envelope)

ModifierFunc is a function which can modify the outgoing Envelope before sending it to the client. ModifierFunc only applies to outgoing envelopes.

type MultipartFormMessage added in v0.12.4

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

MultipartFormMessage is a message type for multipart form data. This is like RawMessage a special kind of message. When you define them in Descriptor, your MUST NOT pass address of them like normal messages. Example:

SetInput(kit.MultipartFormMessage)

func (*MultipartFormMessage) GetForm added in v0.12.4

func (mfm *MultipartFormMessage) GetForm() *multipart.Form

func (*MultipartFormMessage) SetForm added in v0.12.4

func (mfm *MultipartFormMessage) SetForm(frm *multipart.Form)

type NOPLogger added in v0.11.39

type NOPLogger struct{}

func (NOPLogger) Debug added in v0.11.39

func (n NOPLogger) Debug(_ ...any)

func (NOPLogger) Debugf added in v0.11.39

func (n NOPLogger) Debugf(_ string, _ ...any)

func (NOPLogger) Error added in v0.11.39

func (n NOPLogger) Error(_ ...any)

func (NOPLogger) Errorf added in v0.11.39

func (n NOPLogger) Errorf(_ string, _ ...any)

type Option

type Option func(s *edgeConfig)

func WithCluster added in v0.10.29

func WithCluster(cb Cluster) Option

WithCluster lets you register a cluster in constructor of the EdgeServer.

func WithConnDelegate added in v0.9.23

func WithConnDelegate(d ConnDelegate) Option

WithConnDelegate registers the delegate to receive callbacks on connection open/close events. This delegate could be useful to add metrics based on the connections, or any other advanced scenarios. For most use cases, this is not necessary.

func WithErrorHandler

func WithErrorHandler(h ErrHandlerFunc) Option

WithErrorHandler registers a global error handler to catch any error that happens before EdgeServer can deliver the incoming message to the handler, or delivering the outgoing message to the client. Internal errors are usually wrapped and could be checked for better error handling. You can check with errors.Is function to see if the error is one of the following: ErrDispatchFailed, ErrWriteToClosedConn, ErrNoHandler ErrDecodeIncomingMessageFailed, ErrEncodeOutgoingMessageFailed

func WithGateway added in v0.10.29

func WithGateway(gw ...Gateway) Option

WithGateway lets you register a bundle in constructor of the EdgeServer.

func WithGlobalHandlers

func WithGlobalHandlers(handlers ...HandlerFunc) Option

WithGlobalHandlers sets the handlers that will be executed before any service's contract.

func WithLogger

func WithLogger(l Logger) Option

func WithPrefork added in v0.8.9

func WithPrefork() Option

func WithService added in v0.10.29

func WithService(service ...Service) Option

WithService lets you register a service in constructor of the EdgeServer.

func WithServiceDesc added in v0.10.29

func WithServiceDesc(desc ...ServiceDescriptor) Option

WithServiceDesc lets you register a ServiceDescriptor in constructor of the EdgeServer.

func WithShutdownTimeout

func WithShutdownTimeout(d time.Duration) Option

WithShutdownTimeout sets the maximum time to wait until all running requests to finish. Default is to wait forever.

func WithTrace added in v0.9.5

func WithTrace(tp Tracer) Option

type OutgoingRPCContainer

type OutgoingRPCContainer interface {
	SetID(id string)
	// Marshal serializes the contained message
	Marshal() ([]byte, error)
	// SetHdr set the header.
	SetHdr(k, v string)
	// InjectMessage set the body/payload of the container with the actual Message.
	InjectMessage(m Message)
	// Release will be called when we finished our work with it.
	Release()
}

OutgoingRPCContainer define the behavior of RPC message envelope. Similar to IncomingRPCContainer but in another direction.

Example implementations: common.SimpleOutgoingJSONRPC

type OutgoingRPCFactory

type OutgoingRPCFactory func() OutgoingRPCContainer

type ProtoMarshaler

type ProtoMarshaler interface {
	MarshalProto() ([]byte, error)
}

type ProtoUnmarshaler

type ProtoUnmarshaler interface {
	UnmarshalProto(data []byte) error
}

type RESTConn

type RESTConn interface {
	Conn
	GetMethod() string
	GetHost() string
	// GetRequestURI returns uri without Method and Host
	GetRequestURI() string
	// GetPath returns uri without Method, Host and Query parameters.
	GetPath() string
	SetStatusCode(code int)
	Redirect(code int, url string)
	WalkQueryParams(fn func(key string, val string) bool)
}

RESTConn could be implemented by Gateway, so in Dispatcher user can check if Conn also implements RESTConn then it has more information about the RESTConn request.

type RESTRouteSelector

type RESTRouteSelector interface {
	RouteSelector
	GetMethod() string
	GetPath() string
}

RESTRouteSelector is an interface that extends RouteSelector with methods specific to REST operations, primarily used by REST-based gateways. With GetMethod and GetPath methods, this interface provides the HTTP method and path information necessary for routing REST requests to the appropriate contract. Implementations of this interface are expected to provide their own GetMethod and GetPath methods that return the desired method and path as strings.

type RPCRouteSelector

type RPCRouteSelector interface {
	RouteSelector
	GetPredicate() string
}

RPCRouteSelector defines the RouteSelector which could be used in RPC operations. Gateway implementation which handle RPC requests could check the selector if it supports RPC. It is an interface that extends RouteSelector with a method specific to RPC operations. It is primarily used by RPC-based gateways for routing RPC requests to the appropriate contract. Implementations of this interface should provide their own GetPredicate method, which returns a string representing a predicate used to evaluate the route. In this context, a predicate is a condition or criteria that helps in selecting the proper handler for the RPC request.

type RawMessage

type RawMessage []byte

RawMessage is a byte slice which could be used as a Message. This is helpful for raw data messages. Example:

SetInput(kit.RawMessage)

func (*RawMessage) Clone added in v0.11.7

func (rm *RawMessage) Clone(dst []byte) []byte

Clone copies the underlying byte slice into dst. It is SAFE to pass nil for dst.

func (*RawMessage) CopyFrom added in v0.8.9

func (rm *RawMessage) CopyFrom(in []byte)

func (*RawMessage) CopyTo added in v0.11.7

func (rm *RawMessage) CopyTo(out []byte)

func (RawMessage) Marshal

func (rm RawMessage) Marshal() ([]byte, error)

func (RawMessage) MarshalJSON added in v0.11.1

func (rm RawMessage) MarshalJSON() ([]byte, error)

func (*RawMessage) Unmarshal added in v0.11.1

func (rm *RawMessage) Unmarshal(data []byte) error

func (*RawMessage) UnmarshalJSON added in v0.11.3

func (rm *RawMessage) UnmarshalJSON(data []byte) error

type RouteSelector

type RouteSelector interface {
	Query(q string) any
	GetEncoding() Encoding
}

RouteSelector holds information about how this Contract is going to be selected. Each Gateway may need different information to route the request to the right Contract. RouteSelector is actually a base interface and Gateway implementors usually implement either RESTRouteSelector, RPCRouteSelector or both. It is an interface that provides methods for querying and getting the encoding of a routing object. Implementations of RouteSelector are used by Gateway to handle different types of request routing based on the method and protocol. The Query method is for searching specific routes with a query string, and GetEncoding is for returning the encoding format required for communication between the gateway and the target contract.

type Service

type Service interface {
	// Name of the service which must be unique per EdgeServer.
	Name() string
	// Contracts return a list of APIs which this service provides.
	Contracts() []Contract
}

Service defines a set of RPC handlers which usually they are related to one service. Name must be unique per each Gateway.

func WrapService

func WrapService(svc Service, wrappers ...ServiceWrapper) Service

WrapService wraps a service, this is useful for adding middlewares to the service. Some middlewares like OpenTelemetry, Logger, ... could be added to the service using this function.

func WrapServiceContracts

func WrapServiceContracts(svc Service, wrapper ...ContractWrapper) Service

type ServiceDescriptor added in v0.8.9

type ServiceDescriptor interface {
	Generate() Service
}

type ServiceWrapper

type ServiceWrapper interface {
	Wrap(s Service) Service
}

ServiceWrapper lets you add customizations to your service. A specific case of it is serviceInterceptor which can add Pre- and Post-handlers to all the Contracts of the Service.

type ServiceWrapperFunc

type ServiceWrapperFunc func(Service) Service

func (ServiceWrapperFunc) Wrap

func (sw ServiceWrapperFunc) Wrap(svc Service) Service

type Store added in v0.9.1

type Store interface {
	Get(key string) any
	Exists(key string) bool
	Set(key string, val any)
	Delete(key string)
	Scan(prefix string, cb func(key string) bool)
}

Store identifies a key-value store, which its value doesn't have any limitation.

type TestContext

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

TestContext is useful for writing end-to-end tests for your Contract handlers.

func NewTestContext

func NewTestContext() *TestContext

func (*TestContext) Input

func (testCtx *TestContext) Input(m Message, hdr EnvelopeHdr) *TestContext

func (*TestContext) Receiver

func (testCtx *TestContext) Receiver(f func(out ...*Envelope) error) *TestContext

func (*TestContext) Run

func (testCtx *TestContext) Run(stream bool) error

func (*TestContext) RunREST

func (testCtx *TestContext) RunREST() error

func (*TestContext) SetClientIP

func (testCtx *TestContext) SetClientIP(ip string) *TestContext

func (*TestContext) SetHandler

func (testCtx *TestContext) SetHandler(h ...HandlerFunc) *TestContext

type TraceCarrier added in v0.9.5

type TraceCarrier interface {
	// Get returns the value associated with the passed key.
	Get(key string) string

	// Set stores the key-value pair.
	Set(key string, value string)
}

TraceCarrier is the storage medium used by a TracePropagator.

type TracePropagator added in v0.9.5

type TracePropagator interface {
	// Inject set cross-cutting concerns from the Context into the carrier.
	Inject(ctx context.Context, carrier TraceCarrier)

	// Extract reads cross-cutting concerns from the carrier into a Context.
	Extract(ctx context.Context, carrier TraceCarrier) context.Context
}

TracePropagator propagates cross-cutting concerns as key-value text pairs within a carrier that travels in-band across process boundaries.

type Tracer added in v0.9.5

type Tracer interface {
	TracePropagator
	Handler() HandlerFunc
}

type Unmarshaler

type Unmarshaler interface {
	Unmarshal(data []byte) error
}

type Walker

type Walker interface {
	Walk(f func(k, v string) bool)
}

type WriteFunc

type WriteFunc func(conn Conn, e *Envelope) error

Directories

Path Synopsis
internal
tpl
buf
srl

Jump to

Keyboard shortcuts

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