flatend

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 16, 2020 License: MIT Imports: 15 Imported by: 9

README

flatend

MIT License go.dev reference Discord Chat

Write functions in your favorite language, using your favorite tools and platforms and libraries and databases.

flatend will turn them into production-ready microservices that are connected together and exposed as APIs using battle-tested p2p mesh networking, with just a few lines of code.

flatend at the time being currently only supports NodeJS and Go. Support for Python and Deno is planned: join our Discord server to learn more.

"We should have some ways of connecting programs like garden hose--screw in another segment when it becomes necessary to massage data in another way. This is the way of IO also." Doug McIlroy. October 11, 1964

"It's like low-code, but for developers without the vendor-lockin." Kenta Iwasaki. June 16, 2020

Design

Keep it simple, be flexible.

If you're like me, you dream of spending only 5$ a month to leave your product/service sustaining thousands of customers and requests per second. At this golden age of SaaS' and serverless and Docker containers and low-code tools and configuration hell, that dream is slowly starting to fade away.

Flatend uses the battle-tested p2p overlay networking protocol Kademlia to give you the building blocks necessary to achieve this dream with as much flexibility and as little code as possible. With Kademlia, microservices built with Flatend come with service discovery, load balancing, routing, and fully-encrypted industrial-grade end-to-end encryption built in.

All of that without the rigor of configuring Kubernetes/Istio/Consul over and over again. Of course though, if you love Kubernetes/Istio/Consul, then use it with Flatend. Run your Flatend services in Docker containers, on Kubernetes, or on AWS hassle-free.

Either way, Flatend is as minimal as it gets for you to be able to make the most out of your single 2$/month bare-metal server, or fleet of 10,000$/month cloud instances.

Production-ready from the start.

At the end of the day, your microservices are just functions in Flatend.

Is your single behemoth microservice eating up your resources? Split it up into two functions and run one of the functions on another server without any downtime or networking hassle.

Running multiple projects and want to reuse your code? Package it up as yet another microservice with Flatend, and have it seamlessly interact with all of your projects.

As a matter of fact, one thing I find overly common is rewriting HTTP and websocket server/routing/middleware code over and over again. Flatend comes pre-packaged with a fast, scalable, highly-configurable production-ready HTTP server written in Go with LetsEncrypt support.

Zero vendor lock-in and barriers.

Flatend from the start was made to be self-hosted and open-source. It was made to solve problems low-code tools are solving, without tying you to the bloodline of a newfound startup or company.

Flatend was also made to be completely agnostic to your devops, finances, frameworks, libraries, and preferences. Want to code with NodeJS? SQLite? kdb+? Go? QUIC? ORM X/Y/Z? You can use all of that with Flatend today.

It's agnostic to the point that you can serve your microservice on your laptop, and have your Flatend HTTP server on DigitalOcean route HTTP requests to your laptop.

Security as a service.

All communication across microservices written with Flatend are fully-encrypted end-to-end using industrial-grade AES-256 Galois Counter Mode (GCM). The encryption key is established by a X25519 Diffie-Hellman handshake, whose results are passed through BLAKE-2b 256-bit.

Nonces used for encrypting/decrypting messages are unsigned big-endian 64-bit integers incremented per message. The only information exposed on each packet is a single prefixed 32-bit unsigned integer designating a packet's length.

Microservices that are able to be discovered have public/private keys that are Ed25519. It is optional for microservices to identify themselves under a public key to provide services within a Flatend network.

The session handshake protocol is well-documented here.

Installation

Head over to the Releases section and download the latest version of Flatend for your platform.

Archive checksums come pre-included in every release.

Quickstart

Setup

Create a new config.toml, paste the following in, and run the command below.

addr = "127.0.0.1:9000"

[[http]]
addr = ":3000"

[[http.routes]]
path = "GET /hello"
service = "hello_world"
$ ./flatend -c config.toml

Based on the configuration above, Flatend will create a HTTP server listening on port 3000, serving a single route GET /hello which will route requests to the service named hello_world.

The configuration above will also have Flatend accept and transmit data to other Flatend microservices at 127.0.0.1:9000.

Now, let's write our first microservice.

Go

Let's write a function that describes how we want to handle incoming requests for the service hello_world.

package main

import "github.com/lithdew/flatend"

func helloWorld(ctx *flatend.Context) {
    ctx.Write([]byte("Hello world!"))
}

In this case, we'll just reply to the request with "Hello world!". Take note that ctx has a few other goodies, such as the request body ctx.Body as an io.ReadCloser, and request headers ctx.Headers as a map[string]string.

Now, we need just need to register helloWorld as a handler for the service hello_world, and hook it up to our HTTP server listening for microservices at 127.0.0.1:9000.

package main

import (
	"github.com/lithdew/flatend"
	"os"
	"os/signal"
)

func helloWorld(ctx *flatend.Context) {
    ctx.Write([]byte("Hello world!"))
}

func main() {
	node := &flatend.Node{
		Services: map[string]flatend.Handler{
			"hello_world": helloWorld,
		},
	}
	node.Start("127.0.0.1:9000")

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)
	<-ch

	node.Shutdown()
}

Run your Go program, visit http://localhost:9000/hello in your browser, and you should see "Hello world!".

NodeJS

For the following steps I will be using TypeScript, though use whatever flavor of JavaScript you prefer. Let's write a function that describes how we want to handle incoming requests for the service hello_world.

import {Context} from "flatend";

const helloWorld = (ctx: Context) => ctx.send("Hello world!");

In this case, we'll just reply to the request with "Hello world!". Take note that ctx in this case is a NodeJS Duplex stream which you may pipe data into and out of, with header data from our HTTP microservice accessible at ctx.headers.

Now, we need just need to register helloWorld as a handler for the service hello_world, and hook it up to our HTTP server listening for microservices at 127.0.0.1:9000.

import {Node, Context} from "flatend";

const helloWorld = (ctx: Context) => ctx.send("Hello world!");

async function main() {
    const node = new Node();
    node.register("hello_world", helloWorld);
    await node.dial("127.0.0.1:9000");
}

main().catch(err => console.error(err));

Run your NodeJS program, visit http://localhost:9000/hello in your browser, and you should see "Hello world!".

Documentation

Index

Constants

View Source
const ChunkSize = 2048

Variables

View Source
var ErrProviderNotAvailable = errors.New("provider unable to provide service")

Functions

func Addr

func Addr(host net.IP, port uint16) string

func GenerateSecretKey

func GenerateSecretKey() kademlia.PrivateKey

Types

type BindFunc

type BindFunc func() (net.Listener, error)

func BindAny

func BindAny() BindFunc

func BindTCP

func BindTCP(addr string) BindFunc

func BindTCPv4

func BindTCPv4(addr string) BindFunc

func BindTCPv6

func BindTCPv6(addr string) BindFunc

type Context

type Context struct {
	Headers map[string]string
	Body    io.ReadCloser

	ID   uint32 // stream id
	Conn *monte.Conn
	// contains filtered or unexported fields
}

func (*Context) Write

func (c *Context) Write(data []byte) (int, error)

func (*Context) WriteHeader

func (c *Context) WriteHeader(key, val string)

type DataPacket

type DataPacket struct {
	ID   uint32
	Data []byte
}

func UnmarshalDataPacket

func UnmarshalDataPacket(buf []byte) (DataPacket, error)

func (DataPacket) AppendTo

func (p DataPacket) AppendTo(dst []byte) []byte

type Handler

type Handler func(ctx *Context)

type HandshakePacket

type HandshakePacket struct {
	ID        *kademlia.ID
	Services  []string
	Signature kademlia.Signature
}

func UnmarshalHandshakePacket

func UnmarshalHandshakePacket(buf []byte) (HandshakePacket, error)

func (HandshakePacket) AppendPayloadTo

func (h HandshakePacket) AppendPayloadTo(dst []byte) []byte

func (HandshakePacket) AppendTo

func (h HandshakePacket) AppendTo(dst []byte) []byte

func (HandshakePacket) Validate

func (h HandshakePacket) Validate(dst []byte) error

type Node

type Node struct {
	PublicAddr string
	SecretKey  kademlia.PrivateKey

	BindAddrs []BindFunc
	Services  map[string]Handler
	// contains filtered or unexported fields
}

func (*Node) HandleConnState

func (n *Node) HandleConnState(conn *monte.Conn, state monte.ConnState)

func (*Node) HandleMessage

func (n *Node) HandleMessage(ctx *monte.Context) error

func (*Node) Probe

func (n *Node) Probe(addr string) error

func (*Node) Push

func (n *Node) Push(services []string, headers map[string]string, body io.ReadCloser) (*Stream, error)

func (*Node) Shutdown

func (n *Node) Shutdown()

func (*Node) Start

func (n *Node) Start(addrs ...string) error

type Opcode

type Opcode = uint8
const (
	OpcodeHandshake Opcode = iota
	OpcodeServiceRequest
	OpcodeServiceResponse
	OpcodeData
)

type Provider

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

func (*Provider) Addr

func (p *Provider) Addr() string

func (*Provider) Close

func (p *Provider) Close()

func (*Provider) CloseStreamWithError

func (p *Provider) CloseStreamWithError(stream *Stream, err error)

func (*Provider) GetStream

func (p *Provider) GetStream(id uint32) (*Stream, bool)

func (*Provider) NextStream

func (p *Provider) NextStream() *Stream

func (*Provider) Push

func (p *Provider) Push(services []string, headers map[string]string, body io.ReadCloser) (*Stream, error)

func (*Provider) RegisterStream

func (p *Provider) RegisterStream(header ServiceRequestPacket) (*Stream, bool)

func (*Provider) Services

func (p *Provider) Services() []string

type Providers

type Providers struct {
	sync.Mutex
	// contains filtered or unexported fields
}

func NewProviders

func NewProviders() *Providers

type ServiceRequestPacket

type ServiceRequestPacket struct {
	ID       uint32            // stream id
	Services []string          // services this packet may be processed through
	Headers  map[string]string // headers for this packet
}

func UnmarshalServiceRequestPacket

func UnmarshalServiceRequestPacket(buf []byte) (ServiceRequestPacket, error)

func (ServiceRequestPacket) AppendTo

func (p ServiceRequestPacket) AppendTo(dst []byte) []byte

type ServiceResponsePacket

type ServiceResponsePacket struct {
	ID      uint32            // stream id
	Handled bool              // whether or not the service was handled
	Headers map[string]string // headers for this packet
}

func UnmarshalServiceResponsePacket

func UnmarshalServiceResponsePacket(buf []byte) (ServiceResponsePacket, error)

func (ServiceResponsePacket) AppendTo

func (p ServiceResponsePacket) AppendTo(dst []byte) []byte

type Stream

type Stream struct {
	ID     uint32
	Header *ServiceResponsePacket
	Reader *io.PipeReader
	Writer *io.PipeWriter
	// contains filtered or unexported fields
}

Directories

Path Synopsis
cmd
flatend command
microservice command

Jump to

Keyboard shortcuts

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