fiber

package module
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2023 License: Apache-2.0 Imports: 8 Imported by: 3

README

fiber

fiber is a Go library for building dynamic proxies, routers and traffic mixers from a set of composable abstract network components.

Core components of fiber are transport agnostic, however, there is Go's net/http-based implementation provided in fiber/http package and a grpc implementation in fiber/grpc.

The grpc implementation will use the byte payload from the request and response using a custom codec to minimize marshaling overhead. It is expected that the client marshal the message and unmarshall into the intended proto response.

Usage

import (
    "github.com/gojek/fiber"                  // fiber core
    "github.com/gojek/fiber/config"           // fiber config 
    fiberhttp "github.com/gojek/fiber/http"   // fiber http if required
    fibergrpc "github.com/gojek/fiber/grpc"   // fiber grpc if required
)

Define your fiber component in YAML config. For example:

fiber.yaml for http:

type: EAGER_ROUTER
id: eager_router
strategy:
  type: fiber.RandomRoutingStrategy
routes:
  - id: route_a
    type: PROXY
    timeout: "20s"
    endpoint: "http://localhost:8080/routes/route-a"
  - id: route_b
    type: PROXY
    timeout: "40s"
    endpoint: "http://localhost:8080/routes/route-b"

fiber.yaml for grpc:

type: EAGER_ROUTER
id: eager_router
strategy:
  type: fiber.RandomRoutingStrategy
routes:
  - id: route_a
    type: PROXY
    timeout: "20s"
    endpoint: "localhost:50555" 
    service: "mypackage.Greeter"
    method: "SayHello"
    protocol: "grpc"
  - id: route_b
    type: PROXY
    timeout: "40s"
    endpoint: "localhost:50555"
    service: "mypackage.Greeter"
    method: "SayHello"
    protocol: "grpc"

Construct new fiber component from the config:

main.go:

import "github.com/gojek/fiber/config"

compomnent, err := config.FromConfig("./fiber.yaml")

Start serving http requests:

main.go:

import (
    fiberhttp "github.com/gojek/fiber/http"
)

options := fiberhttp.Options{
    Timeout: 20 * time.Second,
}

fiberHandler := fiberhttp.NewHandler(component, options)

http.ListenAndServe(":8080", fiberHandler)

It is also possible to define fiber component programmatically, using fiber API. For example:

import (
    "github.com/gojek/fiber"
    fiberhttp "github.com/gojek/fiber/http"
)

component := fiber.NewEagerRouter("eager-router")
component.SetStrategy(new(extras.RandomRoutingStrategy))

httpDispatcher, _ := fiberhttp.NewDispatcher(http.DefaultClient)
caller, _ := fiber.NewCaller("", httpDispatcher)

component.SetRoutes(map[string]fiber.Component{
    "route-a": fiber.NewProxy(
        fiber.NewBackend("route-a", "http://localhost:8080/routes/route-a"),
        caller),
    "route-b": fiber.NewProxy(
        fiber.NewBackend("route-b", "http://localhost:8080/routes/route-b"),
        caller),
})

For more sample code snippets and grpc usage, head over to the example directory.

Concepts

There are few general abstractions used in fiber:

  • Component - a fiber abstraction, which has a goal to take an incoming request, dispatch it, and return a queue with zero or more responses in it. There is also a special kind of components – MultiRouteComponent. The family of multi-route components contains higher-order components that have one or more children components registered as their routes.
    There are few basic components implemented in fiber, such as Proxy, FanOut, Combiner, Router (EagerRouter and LazyRouter).
    Check Components for more information about existing fiber components.

  • Fan In – is an aggregator, that takes a response queue and returns a single response. It can be one of the responses from the queue, a combination of responses or just a new arbitrary response. Fan In is a necessary part of Combiner implementation.

  • Routing Strategy – is used together with Routers (either lazy or eager) and is responsible for defining the order (priority) of routes registered with given router. So, for every incoming request, Routing Strategy should tell which route should dispatch this given request and what is the order of fallback routes to be used in case the primary route has failed to successfully dispatch the request. Routing strategy is generally expected to be implemented by the client application, because it might be domain specific. However, the simplest possible implementation of routing strategy is provided as a reference in RandomRoutingStrategy.

  • Interceptor – fiber supports pluggable interceptors to examine request and responses. Interceptors are useful for implementing various req/response loggers, metrics or distributed traces collectors. fiber comes with few pre-implemented interceptors (extras/interceptor) to serve above-mentioned purposes.

Components

fiber allows defining request processing rules by composing basic fiber components in more complicated execution graphs. There are few standard fiber components implemented in this library:

  • PROXY – component, that dispatches incoming request against configured proxy backend url.
    Configuration:

    • id – component ID. Example my_proxy
    • endpoint - proxy endpoint url. Example for http http://your-proxy:8080/nested/path or grpc 127.0.0.1:50050
    • timeout - request timeout for dispatching a request. Example 100ms
    • protocol - communication protocol. Only "grpc" or "http" supported.
    • service - for grpc only, package name and service name. Example fiber.Greeter
    • method - for grpc only, method name of the grpc service to invoke. Example SayHello
  • FAN_OUT - component, that dispatches incoming request by sending it to each of its registered routes. Response queue will contain responses of each route in order they have arrived.
    Configuration:

    • id - component ID. Example fan_out
    • routes – list of fiber components definitions. Fan Out will send incoming request to each of its route components and collect responses into a queue.
  • COMBINER - dispatches incoming request by sending it to each of its registered routes and then aggregating received responses into a single response by using provided fan_in.
    Configuration:

    • id - component ID
    • fan_in - configuration of the FanIn, that will be used in this combiner
      • type - registered type name of the fan in. Example: fiber.FastestResponseFanIn. (See also [Custom Types](#Custom Types))
      • properties - arbitrary yaml configuration that would be passed to the FanIn's Initialize method during the component initialization
    • routes - list of fiber component definitions that would be registered as this combiner's routes.
  • EAGER_ROUTER - dispatches incoming request by sending it simultaneously to each registered route and then returning either a response from the primary route (defined by the routing strategy) or switches back to one of the fallback routes. Eager routers are useful in situations, when it's crucial to return fallback response with a minimal delay in case if primary route failed to respond with successful response. Configuration:

    • id – component ID
    • strategy - configuration of the RoutingStrategy, that would be used with this router
      • type - registered type name of the routing strategy. Example: fiber.RandomRoutingStrategy. (See also [Custom Types](#Custom Types))
      • properties - arbitrary yaml configuration that would be passed to the RoutingStrategy's Initialize method during the component initialization
    • routes - list of fiber components definitions that would be registered as this router routes.
  • LAZY_ROUTER - dispatches incoming request by retrieving information about the primary and fallback routes order from its RoutingStrategy and then sending the request to the routes in defined order until one of the routes responds with successful response. Configuration:

    • id – component ID
    • strategy - configuration of the RoutingStrategy, that would be used with this router
      • type - registered type name of the routing strategy. Example: fiber.RandomRoutingStrategy
      • properties - arbitrary yaml configuration that would be passed to the RoutingStrategy's Initialize method during the component initialization
    • routes - list of fiber components definitions that would be registered as this router routes.

Interceptors

fiber comes with few pre-defined interceptors, that are serving the most common use-cases:

Using interceptors

It's also possible to create a custom interceptor by implementing fiber.Interceptor interface:

type Interceptor interface {
	BeforeDispatch(ctx context.Context, req Request) context.Context

	AfterDispatch(ctx context.Context, req Request, queue ResponseQueue)

	AfterCompletion(ctx context.Context, req Request, queue ResponseQueue)
}

Then, one or more interceptors can be attached to the fiber component by calling AddInterceptor method:

import (
    "github.com/gojek/fiber/config"
    "github.com/gojek/fiber/extras/interceptor"
)

compomnent, err := config.FromConfig("./fiber.yaml")

statsdClient := // initialize statsd client
zapLog :=       // initialize logger

component.AddInterceptor(
    true,  // add interceptors recursively to children components
    interceptor.NewMetricsInterceptor(statsdClient),
    interceptor.NewLoggingInterceptor(zapLog),
)

Custom Types

It is also possible to register a custom RoutingStrategy or FanIn implementation in fiber's type system.

First, create your own RoutingStrategy. For example, let's define a routing strategy, that directs requests to route-a in case if session ID (passed via Header) is odd and to route-b if it is even:

package mypackage

import (
	"context"
	"strconv"
)

type OddEvenRoutingStrategy struct {}

func (s *OddEvenRoutingStrategy) SelectRoute(
	ctx context.Context,
	req fiber.Request,
	routes map[string]fiber.Component,
) (fiber.Component, []fiber.Component, fiber.Labels, error) {
	sessionIdStr := ""
	if sessionHeader, ok := req.Header()["X-Session-ID"]; ok {
		sessionIdStr = sessionHeader[0]
	}
    // Metadata that can be propagated upstream for logging / debugging
    labels := fiber.NewLabelsMap()
	
	if sessionID, err := strconv.Atoi(sessionIdStr); err != nil {
		return nil, nil, err
	} else {
		if sessionID % 2 != 0 {
			return routes["route-a"], []fiber.Component{}, labels.WithLabel("Match-Type", "even"), nil
		} else {
			return routes["route-b"], []fiber.Component{}, labels.WithLabel("Match-Type", "odd"), nil
		}
	}
}

Then, register this routing strategy in fiber's type system:

package main

import (
	"github.com/gojek/fiber/types"
)

func main() {
    if err := types.InstallType(
        "mypackage.OddEvenRoutingStrategy", 
        &mypackage.OddEvenRoutingStrategy{}); err != nil {
        panic(err)
    }
    // ... 
}

So now, mypackage.OddEvenRoutingStrategy is registered and can be used in fiber component configuration:

type: LAZY_ROUTER
id: lazy-router
routes:
  - id: "route-a"
    type: PROXY
    endpoint: "http://www.mocky.io/v2/5e4caccc310000e2cad8c071"
    timeout: 5s
  - id: "route-b"
    type: PROXY
    endpoint: "http://www.mocky.io/v2/5e4cacd4310000e1cad8c073"
    timeout: 5s
strategy:
  type: mypackage.OddEvenRoutingStrategy

Licensing

Apache 2.0 License

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Backend

type Backend interface {
	URL(requestURI string) string
}

Backend is an abstraction to be used for defining different backend endpoints for routers or combiners

func NewBackend

func NewBackend(name string, endpoint string) Backend

NewBackend creates a new backend with the given name and endpoint

type BaseComponent

type BaseComponent struct {
	BaseFiberType
	// contains filtered or unexported fields
}

BaseComponent implements those contracts on the Component interface associated with the ID and Interceptors. Other network components can embed this type to re-use these methods.

func NewBaseComponent

func NewBaseComponent(id string, kind ComponentKind) *BaseComponent

func (*BaseComponent) AddInterceptor

func (c *BaseComponent) AddInterceptor(recursive bool, interceptors ...Interceptor)

AddInterceptor can be used to add one or more interceptors to the BaseComponent

func (*BaseComponent) ID

func (c *BaseComponent) ID() string

ID is the getter for the BaseComponent's unique ID

func (*BaseComponent) Kind

func (c *BaseComponent) Kind() ComponentKind

Kind is the getter for the type of the encompassing structure

type BaseFanIn

type BaseFanIn struct {
	BaseFiberType
}

BaseFanIn provides the default implementation for the Type interface

type BaseFanOut

type BaseFanOut struct {
	*BaseMultiRouteComponent
}

BaseFanOut is a component, that dispatches incoming request by each of its nested sub-routes

func NewFanOut

func NewFanOut(id string) *BaseFanOut

NewFanOut initializes a new BaseFanOut component and assigns to it a generated unique ID and the list of nested children components

func (*BaseFanOut) Dispatch

func (fanOut *BaseFanOut) Dispatch(ctx context.Context, req Request) ResponseQueue

Dispatch creates a copy of incoming request (one for each sub-route), asynchronously dispatches these request by its children components and then merges response channels into a single response channel with zero or more responseQueue in it

type BaseFiberType

type BaseFiberType struct{}

BaseFiberType implements Type

func (BaseFiberType) Initialize

func (b BaseFiberType) Initialize(_ json.RawMessage) error

Initialize is a dummy implementation for the Type interface

type BaseMultiRouteComponent

type BaseMultiRouteComponent struct {
	BaseComponent
	// contains filtered or unexported fields
}

BaseMultiRouteComponent is a reference implementation of a MultiRouteComponent

func NewMultiRouteComponent

func NewMultiRouteComponent(id string) *BaseMultiRouteComponent

NewMultiRouteComponent is a factory function for creating a MultiRouteComponent

func (*BaseMultiRouteComponent) AddInterceptor

func (multiRoute *BaseMultiRouteComponent) AddInterceptor(recursive bool, interceptors ...Interceptor)

AddInterceptor can be used to (optionally, recursively) add one or more interceptors to the BaseMultiRouteComponent

func (*BaseMultiRouteComponent) GetRoutes

func (multiRoute *BaseMultiRouteComponent) GetRoutes() map[string]Component

GetRoutes is a getter for the routes configured on the BaseMultiRouteComponent

func (*BaseMultiRouteComponent) SetRoutes

func (multiRoute *BaseMultiRouteComponent) SetRoutes(routes map[string]Component)

SetRoutes sets possible routes for this multi-route component

type CachedPayload

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

CachedPayload caches []byte contents

func NewCachedPayload

func NewCachedPayload(data []byte) *CachedPayload

NewCachedPayload is a creator factory for a CachedPayload structure

func (*CachedPayload) Payload

func (b *CachedPayload) Payload() []byte

Payload returns the cached []byte contents

type Caller

type Caller struct {
	BaseComponent
	// contains filtered or unexported fields
}

Caller is the basic network component, that dispatches incoming request using configured transport-agnostic Dispatcher and asynchronously sends the response into an output channel

func NewCaller

func NewCaller(id string, dispatcher Dispatcher) (*Caller, error)

NewCaller is a factory method that creates a new instance of Caller with given id and provided Dispatcher

func (*Caller) Dispatch

func (c *Caller) Dispatch(ctx context.Context, req Request) ResponseQueue

Dispatch uses Dispatcher to process incoming request and asynchronously sends received response into the output channel. The output channel will be closed after Dispatcher has processed request and response was sent back

type Combiner

type Combiner struct {
	BaseComponent
	FanOut
	// contains filtered or unexported fields
}

Combiner is a network component, that uses BaseFanOut to dispatch incoming request by all of its sub-routes and then merge all the responseQueue from them into a single response, using provided FanIn

func NewCombiner

func NewCombiner(id string) *Combiner

NewCombiner is a factory for the Combiner type.

func (*Combiner) AddInterceptor

func (c *Combiner) AddInterceptor(recursive bool, interceptor ...Interceptor)

AddInterceptor can be used to add the given interceptor to the Combiner and optionally, to all its nested components.

func (*Combiner) Dispatch

func (c *Combiner) Dispatch(ctx context.Context, req Request) ResponseQueue

Dispatch method on the Combiner will ask its embedded dispatcher to simultaneously dispatch the incoming request by all of its nested components. After that, Combiner's FanIn listens to responseQueue and aggregate them into a single response, that is being sent to output

func (*Combiner) ID

func (c *Combiner) ID() string

ID is the getter for the combiner's ID

func (*Combiner) Kind

func (c *Combiner) Kind() ComponentKind

Kind is the getter for the combiner's type

func (*Combiner) WithFanIn

func (c *Combiner) WithFanIn(fanIn FanIn) *Combiner

WithFanIn is a Setter for the FanIn (aggregation strategy) on the given Combiner

type Component

type Component interface {
	Type

	// Returns component id
	ID() string

	// Returns the type of the encompassing structure, set at initialization
	Kind() ComponentKind

	// Dispatches the incoming request and returns a ResponseQueue
	// with zero or more responses in it
	Dispatch(ctx context.Context, req Request) ResponseQueue

	AddInterceptor(recursive bool, interceptors ...Interceptor)
}

Component is the Base interface, that other network components should implement

type ComponentKind

type ComponentKind string

ComponentKind can be used to define the types of Fiber components that support the Component interface

const (
	// CallerKind represents a Fiber component that implements the Caller interface
	CallerKind ComponentKind = "Caller"
	// CombinerKind represents the Combiner type
	CombinerKind ComponentKind = "Combiner"
	// MultiRouteComponentKind represents a Fiber component that implements
	// the MultiRouteComponent interface
	MultiRouteComponentKind ComponentKind = "MultiRouteComponent"
)

type CtxKey

type CtxKey string

CtxKey is an alias for a string and is used to associate keys to context objects

var (
	// CtxComponentIDKey is used to denote the component's id in the request context
	CtxComponentIDKey CtxKey = "CTX_COMPONENT_ID"
	// CtxComponentKindKey is used to denote the component's kind in the request context
	CtxComponentKindKey CtxKey = "CTX_COMPONENT_KIND"
	// CtxComponentLabelsKey is used to denote the component's labels in the request context
	CtxComponentLabelsKey CtxKey = "CTX_COMPONENT_LABELS"
)

type Dispatcher

type Dispatcher interface {
	Do(request Request) Response
}

type EagerRouter

type EagerRouter struct {
	*Combiner
}

EagerRouter implements Router interface and performs routing of incoming requests based on the routing strategy. The reason why it's 'eager' is because it dispatches incoming request by its every possible route in parallel and then returns either a response from a primary route (defined by the routing strategy) or switches back to one of fallback options.

In a sense, EagerRouter is a Combiner, that aggregates responses from its all routes into a single response by selecting this response based on a provided RoutingStrategy

func NewEagerRouter

func NewEagerRouter(id string) *EagerRouter

NewEagerRouter initializes new EagerRouter

func (*EagerRouter) SetStrategy

func (router *EagerRouter) SetStrategy(strategy RoutingStrategy)

SetStrategy sets routing strategy for this router

type ErrorResponse

type ErrorResponse struct {
	*CachedPayload
	// contains filtered or unexported fields
}

func (*ErrorResponse) BackendName

func (resp *ErrorResponse) BackendName() string

func (*ErrorResponse) IsSuccess

func (resp *ErrorResponse) IsSuccess() bool

func (*ErrorResponse) Label added in v0.2.0

func (resp *ErrorResponse) Label(key string) []string

func (*ErrorResponse) StatusCode

func (resp *ErrorResponse) StatusCode() int

func (*ErrorResponse) WithBackendName

func (resp *ErrorResponse) WithBackendName(backendName string) Response

func (*ErrorResponse) WithLabel added in v0.2.0

func (resp *ErrorResponse) WithLabel(key string, values ...string) Response

func (*ErrorResponse) WithLabels added in v0.2.0

func (resp *ErrorResponse) WithLabels(labels Labels) Response

type FanIn

type FanIn interface {
	Type
	Aggregate(ctx context.Context, req Request, queue ResponseQueue) Response
}

FanIn is the base interface for structural FanIn components, that is supposed to listen for zero or more incoming responseQueue from `in` and aggregates them into a single Response

type FanOut

type FanOut interface {
	MultiRouteComponent
}

FanOut is the base interface for structural FanOut Components, that is used to dispatch the incoming request simultaneously and asynchronously across all configured routes, writing the responseQueue, as available, to a single results channel.

type Interceptor

type Interceptor interface {
	BeforeDispatch(ctx context.Context, req Request) context.Context
	AfterDispatch(ctx context.Context, req Request, queue ResponseQueue)
	AfterCompletion(ctx context.Context, req Request, queue ResponseQueue)
}

Interceptor is the interface for a structural interceptor

type Labels added in v0.2.0

type Labels interface {
	Keys() []string
	Label(key string) []string
	WithLabel(key string, values ...string) Labels
}

func NewLabelsMap added in v0.2.0

func NewLabelsMap() Labels

type LabelsMap added in v0.2.0

type LabelsMap map[string][]string

LabelsMap implements the Labels interface via a simple map

func (LabelsMap) Keys added in v0.2.0

func (a LabelsMap) Keys() []string

func (LabelsMap) Label added in v0.2.0

func (a LabelsMap) Label(key string) []string

func (LabelsMap) WithLabel added in v0.2.0

func (a LabelsMap) WithLabel(key string, values ...string) Labels

type LazyRouter

type LazyRouter struct {
	*BaseMultiRouteComponent
	// contains filtered or unexported fields
}

LazyRouter implements Router interface and performs routing of incoming requests based on the routing strategy. The reason why it's 'lazy' is because it tries to dispatch an incoming request by a primary route first and switches to fallback options (one by one) only if received response is not OK

func NewLazyRouter

func NewLazyRouter(id string) *LazyRouter

NewLazyRouter initializes new LazyRouter

func (*LazyRouter) Dispatch

func (r *LazyRouter) Dispatch(ctx context.Context, req Request) ResponseQueue

Dispatch makes a synchronous call to a routing strategy to select the primary route and fallbacks. After receiving a response it asynchronously asks a primary route to dispatch the request. If all responseQueue from a primary route are OK, it sends them back to output Otherwise it repeats the same with all fallback options one by one until one of fallbacks successfully dispatches a request or all fallbacks tried and failed to dispatch it

func (*LazyRouter) SetStrategy

func (r *LazyRouter) SetStrategy(strategy RoutingStrategy)

SetStrategy sets routing strategy for this router

type MultiRouteComponent

type MultiRouteComponent interface {
	Component

	SetRoutes(routes map[string]Component)
	GetRoutes() map[string]Component
}

MultiRouteComponent - is a network component with zero or more possible routes, such as FanOut, Combiner, Router

type NoopAfterCompletionInterceptor

type NoopAfterCompletionInterceptor struct{}

NoopAfterCompletionInterceptor does no operations after request completion

func (*NoopAfterCompletionInterceptor) AfterCompletion

AfterCompletion is an empty method

type NoopAfterDispatchInterceptor

type NoopAfterDispatchInterceptor struct{}

NoopAfterDispatchInterceptor does no operations after dispatch

func (*NoopAfterDispatchInterceptor) AfterDispatch

AfterDispatch is an empty method

type NoopBeforeDispatchInterceptor

type NoopBeforeDispatchInterceptor struct{}

NoopBeforeDispatchInterceptor does no operations before dispatch

func (*NoopBeforeDispatchInterceptor) BeforeDispatch

BeforeDispatch is an empty method

type Proxy

type Proxy struct {
	Component
	// contains filtered or unexported fields
}

Proxy can be used to configure an intermediary for requests

func NewProxy

func NewProxy(backend Backend, component Component) *Proxy

NewProxy is a factory function to create a new Proxy structure

func (*Proxy) Dispatch

func (p *Proxy) Dispatch(ctx context.Context, req Request) ResponseQueue

Dispatch is used to dispatch the incoming request against the proxy backend

type Request

type Request interface {
	Payload() []byte
	Header() map[string][]string
	Clone() (Request, error)
	OperationName() string
	Protocol() protocol.Protocol

	Transform(backend Backend) (Request, error)
}

type Response

type Response interface {
	IsSuccess() bool
	Payload() []byte
	StatusCode() int
	BackendName() string
	WithBackendName(string) Response
	Label(key string) []string
	WithLabel(key string, values ...string) Response
	WithLabels(Labels) Response
}

func NewErrorResponse

func NewErrorResponse(err error) Response

type ResponseQueue

type ResponseQueue interface {
	Iter() <-chan Response
}

func NewResponseQueue

func NewResponseQueue(in <-chan Response, bufferSize int) ResponseQueue

NewResponseQueue takes an input channel and creates a Queue with all responseQueue from it

func NewResponseQueueFromResponses

func NewResponseQueueFromResponses(responses ...Response) ResponseQueue

NewResponseQueueFromResponses takes list of responses and constructs an instance of ResponseQueue from them

type Router

type Router interface {
	MultiRouteComponent

	// Sets routing strategy for this router
	SetStrategy(strategy RoutingStrategy)
}

Router is a network component, that uses provided RoutingStrategy to select a route (child component), that should dispatch an incoming request

type RoutingStrategy

type RoutingStrategy interface {
	Type
	// req - Incoming request (so the route can be selected based on the request)
	// routes - map of all possible routes
	SelectRoute(ctx context.Context,
		req Request,
		routes map[string]Component,
	) (route Component, fallbacks []Component, labels Labels, err error)
}

RoutingStrategy picks up primary route and zero or more fallbacks from the map of router routes

type Type

type Type interface {
	Initialize(cfgProperties json.RawMessage) error
}

Type interface provides a method to do custom initialization of fiber components

Jump to

Keyboard shortcuts

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