bmux

package module
v1.1.9 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2025 License: MIT Imports: 13 Imported by: 0

README

bmux

bmux is a modular (primarily TCP*) multiplexer and routing framework for Go. It provides a declarative interface for handling custom binary protocols using a router and middleware architecture inspired by modern web frameworks.

*While bmux does support the option to use UDP, some features and config options like MaxConnections may not work due to the nature of UDP being connectionless.

Features

  • Global, router-level, and route-level middleware chaining
  • Organize handlers into routers with isolated configuration
  • Parse and dispatch messages using generic packet structures
  • Built on top of the gnet async networking engine for efficient concurrency
  • Load runtime options via config.Config

Installation

go get github.com/etwodev/bmux

Basic Usage

import (
	"github.com/etwodev/bmux"
	"github.com/etwodev/bmux/router"
)

// Define your context struct, this is what is stored with every request
type Context struct {
	IsEncrypted     bool
	ID              int32
}

// Define your get context wrapper
func GetContext() *Context { return &Context{IsEncrypted: false} }

// Define your "read length" extractor, for the following example, the packet is:
// | 0     | 1     | 2     | 3 ... 3+n-1        | 3+n ... 3+n+m-1     |
// |-------|-------|-------|--------------------|----------------------|
// | headLen | bodyLen (2 bytes LE) | header (n bytes) | body (m bytes) |
func GetReadLength() func(c gnet.Conn, buf []byte) (headLen int, totalLen int) {
	return func(c gnet.Conn, buf []byte) (headLen int, totalLen int) {
		if len(buf) < 3 {
			return 0, 0
		}

		headLen = int(buf[0])
		totalLen = headLen + int(binary.LittleEndian.Uint16(buf[1:3]))
		return headLen, totalLen
	}
}

// Define your "read head" extractor, this should extract the messageID or "identifier" from the packet
// You can also consume or set contextual information with Context.
func GetReadHead() func(c gnet.Conn, head []byte, body []byte) (msgID int) {
	return func(c gnet.Conn, head []byte, body []byte) (msgID int) {
		var h gen.MyProto

		if (ctx.IsEncrypted) {
			// Join head and body...
			// Decrypt payload
		}

		if err := proto.Unmarshal(head, &h); err != nil {
			return -1
		}

		ctx := c.Context().(*Context)
		ctx.ID = h.Msgid

		return int(h.Msgid)
	}
}



func main() {
	s := bmux.New(net.GetContext, net.GetReadLength(), net.GetReadHead(), nil)
	s.LoadRouter(Routers())
	s.LoadMiddleware(Middleware())
	s.Start()
}

// Group your routes
func Routers() []router.Router {
	return []router.Router{
		router.NewRouter(true, Routes(), nil),
	}
}

// Define your routes
func Routes() []router.Route {
	return []router.Route{
		router.NewRoute("Ping", 0x01, true, false, HandlePing(), nil),
	}
}


// Define your handler
func HandlePing() handler.HandlerFunc {
	return func(conn gnet.Conn, buf []byte) gnet.Action {
		var req gen.Ping
		if err := proto.Unmarshal(buf, &req); err != nil {
			fmt.Printf("Failed to unmarshal Ping: %v\n", err)
			return gnet.Close
		}

		body := gen.Ping{
			ServerTs: uint64(time.Now().UnixNano() / 1_000_000),
		}

		// ...Define your header
		// headByes := ...

		body, err := proto.Marshal(&body)
		if err != nil {
			return nil, fmt.Errorf("marshal body: %w", err)
		}

		packet := make([]byte, 3+len(headBytes)+len(body))
		packet[0] = byte(len(headBytes))
		binary.LittleEndian.PutUint16(packet[1:3], uint16(len(body)))
		copy(packet[3:], headBytes)
		copy(packet[3+len(headBytes):], body)

		conn.Writev(packet)
		return gnet.None
	}
}

Middleware

Middleware can be applied at three levels:

  • Global — applies to all routes
  • Router-Level — applies to all routes within a router
  • Route-Level — applies to individual routes
Example: Logging Middleware
var log = zerolog.New(zerolog.ConsoleWriter{
	Out:        os.Stdout,
	TimeFormat: "2006-01-02T15:04:05",
}).With().Timestamp().Str("Group", "bmux").Logger()

func Middleware(next handler.HandlerFunc) handler.HandlerFunc {
	return func(conn gnet.Conn, buf []byte) gnet.Action {
		ctx := conn.Context().(*net.Context)

		log.Info().
			Str("Group", "example-server").
			Int("MsgId", int(ctx.ID)).
			Str("Remote", conn.RemoteAddr().String()).
			Msg("Incoming message")

		return next(conn, buf)
	}
}

To register:

func main() {
	// ...
	// s.LoadRouter(...)
	s.LoadMiddleware(Middleware())
	s.Start()
}

// ... func Routers()
// ...

func Middleware() []middleware.Middleware {
	return []middleware.Middleware{
		middleware.NewMiddleware(logging.Middleware, "connection_logger", true, true),
	}
}

Configuration

bmux uses the config.Config struct to load runtime settings such as:

  • Server address and port
  • Logging level (e.g., debug, info, warn)
  • Timeout duration (shutdown)
  • Maximum concurrent connections
  • Enable or disable multi-core mode for gnet

If you do not want to use the json config, you can set the config manually in bmux.New()

Project Structure

bmux/
├── bmux.go          → Core server and lifecycle management
├── pkg/config/          → Configuration loading and management
├── pkg/middleware/      → Middleware primitives and implementations
├── pkg/router/          → Router, route, and context definitions
├── pkg/engine/          → Core networking engine integration (gnet wrapper)

Example Config File

{
  "port": 30000,
  "protocol": "tcp://",
  "address": "0.0.0.0",
  "experimental": false,
  "logLevel": "debug",
  "maxConnections": 1024,
  "headSize": 3,
  "shutdownTimeout": 10,
  "enableMulticore": true
}

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests when applicable
  4. Submit a pull request with a clear description

License

MIT License © 2025 etwodev

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Option

type Option[T any] func(*Server[T])

Option defines a functional option to customize the Server.

type Server

type Server[T any] struct {
	// contains filtered or unexported fields
}

Server represents the bmux server instance. It manages routers, middleware, and the underlying event engine.

T is a generic type parameter representing the connection context type.

Usage:

ctxFactory := func() *MyContext { return &MyContext{} }
extractLen := func(c gnet.Conn, buf []byte) (headLen, totalLen int) { ... }
extractID := func(c gnet.Conn, head []byte) int { ... }

server := bmux.New(ctxFactory, extractLen, extractID, nil)
server.LoadRouter(myRouters)
server.LoadMiddleware(myMiddleware)
server.Start()

The server handles connections using gnet for high-performance async I/O.

func New

func New[T any](
	contextFactory func() *T,
	extractLength engine.ExtractLengthFunc[T],
	extractMsgID engine.ExtractMsgIDFunc[T],
	override *config.Config,
	opts ...Option[T],
) *Server[T]

New creates a new bmux Server instance with the given context factory, length extractor, message ID extractor, optional config override, and options.

It validates required arguments and loads configuration.

Example:

ctxFactory := func() *MyContext { return &MyContext{} }
extractLen := func(c gnet.Conn, buf []byte) (headLen, totalLen int) { ... }
extractID := func(c gnet.Conn, head []byte) int { ... }

server := bmux.New(ctxFactory, extractLen, extractID, nil)

The server is ready to have routers and middleware loaded before starting.

func (*Server[T]) LoadMiddleware

func (s *Server[T]) LoadMiddleware(middleware []middleware.Middleware)

LoadMiddleware appends global middleware to the server.

Middleware applied here runs for all routes.

Example:

server.LoadMiddleware([]middleware.Middleware{myMiddleware})

func (*Server[T]) LoadRouter

func (s *Server[T]) LoadRouter(routes []router.Router)

LoadRouter appends one or more routers to the server.

Routers contain groups of routes and their associated middleware.

Example:

myRouter := router.NewRouter(true, routes, middleware, opts...)
server.LoadRouter([]router.Router{myRouter})

func (*Server[T]) Shutdown

func (s *Server[T]) Shutdown(ctx context.Context) error

Shutdown gracefully stops the server using the provided context for timeout control.

Returns any error encountered during shutdown.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := server.Shutdown(ctx)

func (*Server[T]) Start

func (s *Server[T]) Start()

Start launches the server, listening on the configured address and port, and gracefully handles shutdown on system interrupts.

It blocks until the server exits.

Example:

server.Start()

Directories

Path Synopsis
pkg

Jump to

Keyboard shortcuts

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