基本逻辑完成
This commit is contained in:
134
leaf/context.go
Normal file
134
leaf/context.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package leaf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/eclipse/paho.golang/paho"
|
||||
"math"
|
||||
)
|
||||
|
||||
// abortIndex represents a typical value used in abort functions.
|
||||
const abortIndex int8 = math.MaxInt8 >> 1
|
||||
|
||||
type endKeyType = string
|
||||
|
||||
const EndKey endKeyType = "end"
|
||||
|
||||
type EndType int
|
||||
|
||||
const (
|
||||
EndTimer EndType = iota + 1
|
||||
EndStop
|
||||
)
|
||||
|
||||
type KeyValue struct {
|
||||
parent *KeyValue
|
||||
key any
|
||||
value any
|
||||
}
|
||||
|
||||
var rootKeyType = &KeyValue{}
|
||||
|
||||
type Context struct {
|
||||
context.Context
|
||||
*paho.Publish
|
||||
engine *Engine
|
||||
|
||||
index int8
|
||||
handlers HandlersChain
|
||||
|
||||
value *KeyValue
|
||||
}
|
||||
|
||||
func WithLeafContext(c context.Context, p *paho.Publish, engine *Engine, handlers HandlersChain) *Context {
|
||||
return &Context{
|
||||
Context: c,
|
||||
Publish: p,
|
||||
engine: engine,
|
||||
index: -1,
|
||||
handlers: handlers,
|
||||
value: rootKeyType,
|
||||
}
|
||||
}
|
||||
|
||||
// HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()",
|
||||
// this function will return "main.handleGetUsers".
|
||||
func (c *Context) HandlerName() string {
|
||||
return nameOfFunction(c.handlers.Last())
|
||||
}
|
||||
|
||||
// HandlerNames returns a list of all registered handlers for this context in descending order,
|
||||
// following the semantics of HandlerName()
|
||||
func (c *Context) HandlerNames() []string {
|
||||
hn := make([]string, 0, len(c.handlers))
|
||||
for _, val := range c.handlers {
|
||||
if val == nil {
|
||||
continue
|
||||
}
|
||||
hn = append(hn, nameOfFunction(val))
|
||||
}
|
||||
return hn
|
||||
}
|
||||
|
||||
// Handler returns the main handler.
|
||||
func (c *Context) Handler() HandlerFunc {
|
||||
return c.handlers.Last()
|
||||
}
|
||||
|
||||
/************************************/
|
||||
/*********** FLOW CONTROL ***********/
|
||||
/************************************/
|
||||
|
||||
// Next should be used only inside middleware.
|
||||
// It executes the pending handlers in the chain inside the calling handler.
|
||||
// See example in GitHub.
|
||||
func (c *Context) Next() {
|
||||
c.index++
|
||||
for c.index < int8(len(c.handlers)) {
|
||||
if c.handlers[c.index] == nil {
|
||||
continue
|
||||
}
|
||||
c.handlers[c.index](c)
|
||||
c.index++
|
||||
}
|
||||
}
|
||||
|
||||
// IsAborted returns true if the current context was aborted.
|
||||
func (c *Context) IsAborted() bool {
|
||||
return c.index >= abortIndex
|
||||
}
|
||||
|
||||
// Abort prevents pending handlers from being called. Note that this will not stop the current handler.
|
||||
// Let's say you have an authorization middleware that validates that the current request is authorized.
|
||||
// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers
|
||||
// for this request are not called.
|
||||
func (c *Context) Abort() {
|
||||
c.index = abortIndex
|
||||
}
|
||||
|
||||
func WithValue[T any](ctx *Context, k any, v T) {
|
||||
ctx.value = &KeyValue{
|
||||
parent: ctx.value,
|
||||
key: k,
|
||||
value: v,
|
||||
}
|
||||
}
|
||||
|
||||
func Value[T any](ctx *Context, k any) (v T) {
|
||||
vo := ctx.value
|
||||
for {
|
||||
if vo.key == k {
|
||||
v, _ = vo.value.(T)
|
||||
break
|
||||
} else if vo.parent == rootKeyType {
|
||||
break
|
||||
}
|
||||
vo = vo.parent
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func WithCancel(ctx *Context) context.CancelFunc {
|
||||
c, cancel := context.WithCancel(ctx.Context)
|
||||
ctx.Context = c
|
||||
return cancel
|
||||
}
|
||||
144
leaf/leaf.go
Normal file
144
leaf/leaf.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package leaf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/eclipse/paho.golang/packets"
|
||||
"github.com/eclipse/paho.golang/paho"
|
||||
"github.com/eclipse/paho.golang/paho/log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Router interface {
|
||||
RegisterHandler(string, ...HandlerFunc)
|
||||
UnregisterHandler(string)
|
||||
Route(*packets.Publish)
|
||||
SetDebugLogger(log.Logger)
|
||||
Use(...HandlerFunc)
|
||||
}
|
||||
|
||||
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||
type HandlerFunc func(*Context)
|
||||
|
||||
// OptionFunc defines the function to change the default configuration
|
||||
type OptionFunc func(*Engine)
|
||||
|
||||
// HandlersChain defines a HandlerFunc slice.
|
||||
type HandlersChain []HandlerFunc
|
||||
|
||||
// Last returns the last handler in the chain. i.e. the last handler is the main one.
|
||||
func (c HandlersChain) Last() HandlerFunc {
|
||||
if length := len(c); length > 0 {
|
||||
return c[length-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Engine struct {
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
Handlers HandlersChain
|
||||
defaultHandler HandlersChain
|
||||
subscriptions map[string]HandlersChain
|
||||
aliases map[uint16]string
|
||||
debug log.Logger
|
||||
}
|
||||
|
||||
func New(ctx context.Context) *Engine {
|
||||
return &Engine{
|
||||
ctx: ctx,
|
||||
Handlers: make(HandlersChain, 0),
|
||||
subscriptions: make(map[string]HandlersChain),
|
||||
aliases: make(map[uint16]string),
|
||||
debug: log.NOOPLogger{},
|
||||
}
|
||||
}
|
||||
|
||||
func Default(ctx context.Context) *Engine {
|
||||
engine := &Engine{
|
||||
ctx: ctx,
|
||||
Handlers: make(HandlersChain, 0),
|
||||
subscriptions: make(map[string]HandlersChain),
|
||||
aliases: make(map[uint16]string),
|
||||
debug: log.NOOPLogger{},
|
||||
}
|
||||
engine.Use(Recovery())
|
||||
return engine
|
||||
}
|
||||
|
||||
func (e *Engine) RegisterHandler(topic string, handlers ...HandlerFunc) {
|
||||
e.debug.Println("registering handler for:", topic)
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
e.subscriptions[topic] = e.combineHandlers(handlers)
|
||||
}
|
||||
|
||||
func (e *Engine) UnregisterHandler(topic string) {
|
||||
e.debug.Println("unregistering handler for:", topic)
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
delete(e.subscriptions, topic)
|
||||
}
|
||||
|
||||
func (e *Engine) Route(pb *packets.Publish) {
|
||||
e.debug.Println("routing message for:", pb.Topic)
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
m := paho.PublishFromPacketPublish(pb)
|
||||
|
||||
var topic string
|
||||
if pb.Properties.TopicAlias != nil {
|
||||
e.debug.Println("message is using topic aliasing")
|
||||
if pb.Topic != "" {
|
||||
// Register new alias
|
||||
e.debug.Printf("registering new topic alias '%d' for topic '%s'", *pb.Properties.TopicAlias, m.Topic)
|
||||
e.aliases[*pb.Properties.TopicAlias] = pb.Topic
|
||||
}
|
||||
if t, ok := e.aliases[*pb.Properties.TopicAlias]; ok {
|
||||
e.debug.Printf("aliased topic '%d' translates to '%s'", *pb.Properties.TopicAlias, m.Topic)
|
||||
topic = t
|
||||
}
|
||||
} else {
|
||||
topic = m.Topic
|
||||
}
|
||||
|
||||
handlerCalled := false
|
||||
for route, handlers := range e.subscriptions {
|
||||
if match(route, topic) {
|
||||
e.debug.Println("found handler for:", route)
|
||||
go WithLeafContext(e.ctx, m, e, handlers).Next()
|
||||
handlerCalled = true
|
||||
}
|
||||
}
|
||||
|
||||
if !handlerCalled && e.defaultHandler != nil {
|
||||
go WithLeafContext(e.ctx, m, e, e.defaultHandler).Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) SetDebugLogger(l log.Logger) {
|
||||
e.debug = l
|
||||
}
|
||||
|
||||
func (e *Engine) Use(middleware ...HandlerFunc) {
|
||||
e.Handlers = append(e.Handlers, middleware...)
|
||||
}
|
||||
|
||||
func (e *Engine) DefaultHandler(h HandlerFunc) {
|
||||
e.debug.Println("registering default handler")
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
e.defaultHandler = e.combineHandlers(HandlersChain{h})
|
||||
}
|
||||
|
||||
func (e *Engine) combineHandlers(handlers HandlersChain) HandlersChain {
|
||||
finalSize := len(e.Handlers) + len(handlers)
|
||||
assert1(finalSize < int(abortIndex), "too many handlers")
|
||||
mergedHandlers := make(HandlersChain, finalSize)
|
||||
copy(mergedHandlers, e.Handlers)
|
||||
copy(mergedHandlers[len(e.Handlers):], handlers)
|
||||
return mergedHandlers
|
||||
}
|
||||
69
leaf/mode.go
Normal file
69
leaf/mode.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package leaf
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const EnvLeafMode = "LEAF_MODE"
|
||||
|
||||
const (
|
||||
// DebugMode indicates gin mode is debug.
|
||||
DebugMode = "debug"
|
||||
// ReleaseMode indicates gin mode is release.
|
||||
ReleaseMode = "release"
|
||||
// TestMode indicates gin mode is test.
|
||||
TestMode = "test"
|
||||
)
|
||||
|
||||
const (
|
||||
debugCode = iota
|
||||
releaseCode
|
||||
testCode
|
||||
)
|
||||
|
||||
var DefaultWriter io.Writer = os.Stdout
|
||||
|
||||
var DefaultErrorWriter io.Writer = os.Stderr
|
||||
|
||||
var leafMode int32 = debugCode
|
||||
var modeName atomic.Value
|
||||
|
||||
func init() {
|
||||
mode := os.Getenv(EnvLeafMode)
|
||||
SetMode(mode)
|
||||
}
|
||||
|
||||
// SetMode sets gin mode according to input string.
|
||||
func SetMode(value string) {
|
||||
if value == "" {
|
||||
if flag.Lookup("test.v") != nil {
|
||||
value = TestMode
|
||||
} else {
|
||||
value = DebugMode
|
||||
}
|
||||
}
|
||||
|
||||
switch value {
|
||||
case DebugMode, "":
|
||||
atomic.StoreInt32(&leafMode, debugCode)
|
||||
case ReleaseMode:
|
||||
atomic.StoreInt32(&leafMode, releaseCode)
|
||||
case TestMode:
|
||||
atomic.StoreInt32(&leafMode, testCode)
|
||||
default:
|
||||
panic("leaf mode unknown: " + value + " (available mode: debug release test)")
|
||||
}
|
||||
modeName.Store(value)
|
||||
}
|
||||
|
||||
func IsDebugging() bool {
|
||||
return atomic.LoadInt32(&leafMode) == debugCode
|
||||
}
|
||||
|
||||
// Mode returns current gin mode.
|
||||
func Mode() string {
|
||||
return modeName.Load().(string)
|
||||
}
|
||||
48
leaf/recovery.go
Normal file
48
leaf/recovery.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package leaf
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
// RecoveryFunc defines the function passable to CustomRecovery.
|
||||
type RecoveryFunc func(c *Context, err any)
|
||||
|
||||
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
||||
func Recovery() HandlerFunc {
|
||||
return RecoveryWithWriter(DefaultErrorWriter)
|
||||
}
|
||||
|
||||
// CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
|
||||
func CustomRecovery(handle RecoveryFunc) HandlerFunc {
|
||||
return RecoveryWithWriter(DefaultErrorWriter, handle)
|
||||
}
|
||||
|
||||
// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
|
||||
func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
|
||||
if len(recovery) > 0 {
|
||||
return CustomRecoveryWithWriter(out, recovery[0])
|
||||
}
|
||||
return CustomRecoveryWithWriter(out, defaultHandleRecovery)
|
||||
}
|
||||
|
||||
// CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
|
||||
func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
||||
return func(c *Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Println("执行出错: ", err)
|
||||
handle(c, err)
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func defaultHandleRecovery(c *Context, _ any) {
|
||||
c.Abort()
|
||||
}
|
||||
64
leaf/utils.go
Normal file
64
leaf/utils.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package leaf
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func assert1(guard bool, text string) {
|
||||
if !guard {
|
||||
panic(text)
|
||||
}
|
||||
}
|
||||
|
||||
func nameOfFunction(f any) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
||||
}
|
||||
|
||||
func match(route, topic string) bool {
|
||||
return route == topic || routeIncludesTopic(route, topic)
|
||||
}
|
||||
|
||||
func matchDeep(route []string, topic []string) bool {
|
||||
if len(route) == 0 {
|
||||
return len(topic) == 0
|
||||
}
|
||||
|
||||
if len(topic) == 0 {
|
||||
return route[0] == "#"
|
||||
}
|
||||
|
||||
if route[0] == "#" {
|
||||
return true
|
||||
}
|
||||
|
||||
if (route[0] == "+") || (route[0] == topic[0]) {
|
||||
return matchDeep(route[1:], topic[1:])
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func routeIncludesTopic(route, topic string) bool {
|
||||
return matchDeep(routeSplit(route), topicSplit(topic))
|
||||
}
|
||||
|
||||
func routeSplit(route string) []string {
|
||||
if len(route) == 0 {
|
||||
return nil
|
||||
}
|
||||
var result []string
|
||||
if strings.HasPrefix(route, "$share") {
|
||||
result = strings.Split(route, "/")[2:]
|
||||
} else {
|
||||
result = strings.Split(route, "/")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func topicSplit(topic string) []string {
|
||||
if len(topic) == 0 {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(topic, "/")
|
||||
}
|
||||
Reference in New Issue
Block a user