golangはゼロからミドルウェアを実現する


ミドルウェアの実装の背景
まず次のコードを見てください.
package main

func hello(wr http.ResponseWriter, r *http.Request) {
    wr.Write([]byte("hello"))
}

func main() {
    http.HandleFunc("/", hello)
    err := http.ListenAndServe(":8080", nil)
    ...
}

簡単なHTTPインタフェースサービスです
インタフェースにサービスの時間のかかる処理ログを追加する必要がある新しいニーズがあり、上記のプログラムを少量修正しました.
func hello(wr http.ResponseWriter, r *http.Request) {
    timeStart := time.Now()
    wr.Write([]byte("hello"))
    timeElapsed := time.Since(timeStart)
    fmt.Println(timeElapsed)
}

ビジネスが増加するにつれて、ルートは
package main

func helloHandler(wr http.ResponseWriter, r *http.Request) {
    // ...
}

func showInfoHandler(wr http.ResponseWriter, r *http.Request) {
    // ...
}

func showEmailHandler(wr http.ResponseWriter, r *http.Request) {
    // ...
}

func showFriendsHandler(wr http.ResponseWriter, r *http.Request) {
    timeStart := time.Now()
    wr.Write([]byte("your friends is tom and alex"))
    timeElapsed := time.Since(timeStart)
    logger.Println(timeElapsed)
}

func main() {
    http.HandleFunc("/", helloHandler)
    http.HandleFunc("/info/show", showInfoHandler)
    http.HandleFunc("/email/show", showEmailHandler)
    http.HandleFunc("/friends/show", showFriendsHandler)
    // ...
}

この場合、各ルーティングにパラメータ情報ログを追加する必要がある場合、コードはよく記述されていますが、すべてのhandleにこのコードを変更する必要があり、後で追加されるhandleもこのコードを理解して追加する必要があります.コードのメンテナンスがますます面倒になります.したがって、ミドルウェアを使用して、ビジネスコードと非ビジネスコード(例えば、上記のインタフェースの時間消費統計、要求パラメータログなど)を分割する必要があります.
インプリメンテーションコード
package main

import(
	"fmt"
	"net/http"
	"sync"
)


// HandlerFunc defines the handler used by  middleware as return value.
type HandlerFunc func(*Context)

// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc

//      
type Context struct {
	Request   *http.Request
	Writer    http.ResponseWriter
	handlers HandlersChain
	index    int8

}

//       
func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		//     HandlersChain    
		//      c.Next()            
		//      c.Next()          c.Next()     ,               c.Next()    
		c.handlers[c.index](c)
		c.index++
	}
}
func (c *Context) reset() {
	c.handlers = nil
	c.index = -1
}

//    
type RouterGroup struct {
	//        
	Handlers HandlersChain
	engine   *Engine
}

func (group *RouterGroup) Use(middleware ...HandlerFunc) {
	group.Handlers = append(group.Handlers, middleware...)
}

func (group *RouterGroup) AddRoute(absolutePath string, handlers ...HandlerFunc) {
	handlers = group.combineHandlers(handlers)
	//              
	group.engine.addRoute(absolutePath, handlers)
}

//                    
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
	finalSize := len(group.Handlers) + len(handlers)
	mergedHandlers := make(HandlersChain, finalSize)
	copy(mergedHandlers, group.Handlers)
	copy(mergedHandlers[len(group.Handlers):], handlers)
	return mergedHandlers
}

type Engine struct{

	tree map[string]HandlersChain	// tree       map        
	RouterGroup
	pool             sync.Pool		//               ,            
}

func NewEngine() *Engine {
	engine := &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
		},
		tree: make(map[string]HandlersChain),
	}
	engine.RouterGroup.engine = engine
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}

func (engine *Engine) allocateContext() *Context {
	return &Context{}
}

//url   ,      
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := engine.pool.Get().(*Context)
	c.Writer = w
	c.Request = req
	c.reset()

	engine.handleHTTPRequest(c)

	engine.pool.Put(c)
}

func (engine *Engine) handleHTTPRequest(c *Context) {
	rPath := c.Request.URL.Path

	handlers := engine.getValue(rPath)
	if handlers != nil {
		c.handlers = handlers
		//        
		c.Next()
		return
	}

}

//        HandlersChain
func (engine *Engine)getValue(path string)(handlers HandlersChain){
	handlers,ok := engine.tree[path]
	if !ok {
		return nil
	}
	return
}

func (engine *Engine) addRoute(path string, handlers HandlersChain) {
	engine.tree[path]=handlers
}

func (engine *Engine) Use(middleware ...HandlerFunc)  {
	engine.RouterGroup.Use(middleware...)
}

func main(){
	engine := NewEngine()
	engine.Use(func(c * Context){
		fmt.Println("begin middle1")
		fmt.Println("end middle1")
	})
	engine.Use(func(c * Context){
		fmt.Println("begin middle2")
		c.Next()
		fmt.Println("end middle2")
	})
	engine.Use(func(c * Context){
		fmt.Println("begin middle3")
		c.Next()
		fmt.Println("end middle3")
	})
	engine.AddRoute("/path1",func( c *Context){
		fmt.Println("path1")
		c.Writer.Write([]byte("path1"))

	})
	engine.AddRoute("/path2",func( c *Context){
		fmt.Println("path2")
		c.Writer.Write([]byte("path2"))

	})
	http.ListenAndServe(":8080", engine)
}

ブラウザの入力:http://localhost:8080/path1
実行結果
golang 从零实现一个中间件(middleware)_第1张图片
結果を実行するスタックフロー
golang 从零实现一个中间件(middleware)_第2张图片
シーンの適用
  •  http          
  •          ,  /ping,/healthcheck,                  
  •           ,        ,    
  •        X-Forwarded-For X-Real-IP, http.Request  RemoteAddr      RealIP
  •           requestid,     ,           ,                   
  •  context.Timeout      ,     http.Request      
  •        channel  token,     token       

  • 参考資料
    ginフレームワークソース中間価格実現
    GO高度プログラミング