Golang 中間件簡介 MiddleWare


https://mp.weixin.qq.com/s/-nRWwy8SjW1TlqCglL0CAQ

引言

web開發的背景下,“中間件”通常意思是“包裝原始應用並添加一些額外的功能的應用的一部分”。這個概念似乎總是不被人理解,但是我認為中間件非常棒。

首先,一個好的中間件有一個責任就是可插拔並且自足。這就意味着你可以在接口級別嵌入你的中間件他就能直接運行。它不會影響你編碼方式,不是框架,僅僅是你請求處理里面的一層而已。完全沒必要重寫你的代碼,如果你想使用中間件的一個功能,你就幫他插入到那里,如果不想使用了,就可以直接移除。

縱觀Go語言,中間件是非常普遍的,即使在標准庫中。雖然開始的時候不會那么明顯,在標准庫net/http中的函數StripText或者TimeoutHandler就是我們要定義和的中間件的樣子,處理請求和相應的時候他們包裝你的handler,並處理一些額外的步驟。

一開始,我們認為編寫中間件似乎很容易,但是我們實際編寫的時候也會遇到各種各樣的坑。讓我們來看看一些例子。

1、讀取請求

在我們的示例中,所有的中間件都將接受http。處理程序作為一個參數,並返回一個http.Handler。這使得人們很容易就能把中間產品串起來。我們所有的中間產品的基本模式是這樣的:

?
1
2
3
4
5
6
func X(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  // Something here...
  h.ServeHTTP(w, r)
  })
}

我們想要將所有的請求重定向到一個斜杠——比方說/message/,到它們的非斜杠等效,比如/message。我們可以這樣寫:

?
1
2
3
4
5
6
7
8
9
func TrailingSlashRedirect(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  if r.URL.Path != "/" && r.URL.Path[len(r.URL.Path)-1] == '/' {
  http.Redirect(w, r, r.URL.Path[:len(r.URL.Path)-1], http.StatusMovedPermanently)
  return
  }
  h.ServeHTTP(w, r)
  })
}

有沒有很簡單。

2、修改請求

比方說,我們想要向請求添加一個標題,或者修改它。http.Handler文檔中指明:

除了讀取主體之外,處理程序不應該修改所提供的請求。

Go標准庫復制http.Request。請求對象在將其傳遞到響應鏈之前,我們也應該這樣做。假設我們想在每個請求上設置Request-Id頭,以便內部跟蹤。創建*Request的淺副本,並在代理之前修改標題。

?
1
2
3
4
5
6
7
8
func RequestID(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  r2 := new(http.Request)
  *r2 = *r
  r2.Header.Set("X-Request-Id", uuid.NewV4().String())
  h.ServeHTTP(w, r2)
  })
}

3、編寫響應頭

如果你想設置響應頭,你可以只寫它們,然后代理請求。

?
1
2
3
4
5
6
func Server(h http.Handler, servername string) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Server", servername)
  h.ServeHTTP(w, r)
  })
}

上面的問題是,如果內部處理器也設置了服務器頭,那么你的頭將被覆蓋。如果不想公開內部軟件的服務器頭,或者如果想在發送響應給客戶端之前去掉頭部,這可能會帶來問題。

要做到這一點,我們必須自己實現ResponseWriter接口。大多數時候,我們只會代理到底層的ResponseWriter,但是如果用戶試圖寫一個響應,我們就會潛入並添加我們的標題。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type serverWriter struct {
  w http.ResponseWriter
  name string
  wroteHeaders bool
}
 
func (s *serverWriter) Header() http.Header {
  return s.w.Header()
}
 
func (s *serverWriter) WriteHeader(code int) http.Header {
  if s.wroteHeader == false {
  s.w.Header().Set("Server", s.name)
  s.wroteHeader = true
  }
  s.w.WriteHeader(code)
}
 
func (s *serverWriter) Write(b []byte) (int, error) {
  if s.wroteHeader == false {
  // We hit this case if user never calls WriteHeader (default 200)
  s.w.Header().Set("Server", s.name)
  s.wroteHeader = true
  } return s.w.Write(b)
}

要在我們的中間件中使用它,我們會寫:

?
1
2
3
4
5
6
7
8
9
func Server(h http.Handler, servername string) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  sw := &serverWriter{
  w: w,
  name: servername,
  }
  h.ServeHTTP(sw, r)
  })
}

問題

如果用戶從不調用Write或WriteHeader呢?例如,有一個200狀態並且是空body,或者對選項請求的響應——我們的攔截函數都不會運行。因此,我們應該在ServeHTTP調用之后再添加校驗。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
func Server(h http.Handler, servername string) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  sw := &serverWriter{
  w: w,
  name: servername,
  }
  h.ServeHTTP(sw, r)
  if sw.wroteHeaders == false {
  s.w.Header().Set("Server", s.name)
  s.wroteHeader = true
  }
  })
}

其他ResponseWriter接口

ResponseWriter接口只需要有三種方法。但在實踐中,它也可以對其他接口作出響應,例如http.Pusher。你的中間件可能會意外地禁用HTTP/2支持,這是不好的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Push implements the http.Pusher interface.
func (s *serverWriter) Push(target string, opts *http.PushOptions) error {
  if pusher, ok := s.w.(http.Pusher); ok {
  return pusher.Push(target, opts)
  }
  return http.ErrNotSupported
}
 
// Flush implements the http.Flusher interface.
func (s *serverWriter) Flush() {
  f, ok := s.w.(http.Flusher)
  if ok {
  f.Flush()
  }
}

總結

通過以上的學習,不知道大家對Go編寫中間件有沒有一個完整的認識。大家也可以嘗試着用Go去編寫一個中間件。

好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

 

https://slarker.me/go-middleware/

今天學習了一個之前雖然聽過,但是從來沒搞懂的概念 — 中間件。這個概念在服務端開發中經常會用到,大的公司也會有專門的團隊來做中間件開發,用來提高應用層的開發效率。在 Web 開發中,中間件也就是在請求流程中新增了一層,添加一些額外的功能,比如驗證 Token,記錄日志等等。一個優秀的中間件能做到可插拔,自我約束,無須重寫代碼。

在 Go 語言中,實現一個中間件也非常簡單,其原理和 Python 中的裝飾器非常類似。比如我們想實現一個中間件,用來統計每個接口調用的次數:

package middleware

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func RequestMiddleWare() gin.HandlerFunc {
	return func(c *gin.Context) {
		path := c.Request.URL.Path
		fmt.Println(string(path))
		c.Next()
	}
}

RequestMiddleWare 函數的返回值是一個函數,在函數實現中,可以根據 *gin.Context 拿到請求信息,做一些想要的操作,完成之后可以 c.Next() 執行下一個步驟。整個過程就是在原來的請求處理中新增了一層,非常方便。

除此之外,如果希望在請求結束后還可以做一些操作,可以把相關邏輯放到 c.Next() 之后,當請求結束之后,還會返回來再把中間件中剩余的邏輯執行完畢。也就相當於:

before middleware
    c.Next()
after middleware

 

 

 https://www.jianshu.com/p/877492d3bcc8
 

當構建 web 應用程序,可能對所有的請求會共享一些功能。例如我們每一個請求都會寫入日志。

  • 打印 http 請求或返回的日志
  • 壓縮 http 請求的返回值
  • 將 http 請求頭保持一致
  • 實現安全以及權限驗證

在 go 語言 net/http 標准庫中提供了中間件類似的函數 StripPrefix 和 TimeoutHandler。
如何自己編寫一個中間件呢?答案是

  • 這個函數接收一個 http.Handler 作為參數,目的是可使用其他中間件或應用處理器,調用其他中間件或應用的方法就是調用其 HttpServe 的方法。
  • 並且將 http.Handler 作為返回值,這樣其他中間件可以使用 http.Handler 作為輸入的參數,達到鏈式調用。
func middlewareHandler(next http.Handler) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 // 在執行應用處理器前編譯中間件的邏輯
 next.ServeHTTP(w, r)
 // 在執行應用處理器后將會執行的編譯中間件的邏輯
 })
}

從上面示例我們可以看出 go 語言是支持高階函數,通過向下傳遞 http.Handler 來實現中間件。

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func logginHandler(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
        start := time.Now()
        log.Printf("Started %s %s",r.Method, r.URL.Path)
        next.ServeHTTP(w,r)
        log.Printf("Completed %s in %v", r.URL.Path, time.Since(start))
    })
}

func index(w http.ResponseWriter,r *http.Request ){
    log.Println("Executing index handler")
    fmt.Fprintf(w,"welcome!")
}

func about(w http.ResponseWriter, r *http.Request){
    log.Println("Executing about handler")
    fmt.Fprintf(w,"Go Middleware")
}

func iconHandler(w http.ResponseWriter, r *http.Request){

}

func main()  {

    http.HandleFunc("/favicon.ico", iconHandler)

    indexHandler := http.HandlerFunc(index)
    aboutHandler := http.HandlerFunc(about)

    http.Handle("/",logginHandler(indexHandler))
    http.Handle("/about",logginHandler(aboutHandler))

    server :=  &http.Server{
        Addr:":8080",
    }

    log.Println("Listening...")
    server.ListenAndServe()
}
  • 上面的代碼關鍵是 logginHandler 這個日志處理器,作為中間件會攔截請求,進行處理也就是輸出日志,通過next.ServeHTTP(w,r)然后將請求向下進行傳遞
  • 這里創建兩個簡單路由處理器indexabout。都是向客戶端輸出不同的文本。
  • http.Handle("/",logginHandler(indexHandler)) 通過 logginHandler 函數進行包裹,從容我們日志中間件可以攔截到發起的請求進行一些業務邏輯,業務邏輯可以位於next.ServeHTTP(w,r)之前或之后。通過下面的輸出我們可以清晰可見代碼執行的順序。
Listening...
 Started GET /
 Executing index handler
 Completed / in 41.129µs Started GET / Executing index handler Completed / in 50.475µs Started GET /about Executing about handler Completed /about in 49.483µs 

許多書都提供代碼示例,個人建議是不要 copy 過來運行看效果,自己手 coding 就可以發現一些問題,同時可以思考為什么要這樣編寫代碼。

通過上面示例我們了解如何寫一個簡單中間件,這個個人看來和 nodejs 實現中間件沒有什么差別。實際開發中我們往往會有多個中間件來執行業務,那么這些中間件執行順序也是我們值得考慮的問題。

package main

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

func middlewareFirst(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
        log.Println("MiddlewareFirst - before Handler")
        next.ServeHTTP(w,r)
        log.Println("MiddlewareFirst - after Handler")
    })
}

func middlewareSecond(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
        log.Println("MiddlewareSecond - before Handler")
        if r.URL.Path == "/message"{
            if r.URL.Query().Get("password") == "123"{
                log.Println("Authorized to system...")
                next.ServeHTTP(w,r)
            }else{
                log.Println("Failed to authorize to the system")
                return
            }
        }else{
            next.ServeHTTP(w,r)
        }
        log.Println("MiddlewareScond - after Handler")      
    })
}

func index(w http.ResponseWriter, r *http.Request){
    log.Println("Executed index Handler")
    fmt.Fprintf(w,"welcome")
}

func message(w http.ResponseWriter, r *http.Request){
    log.Println("Executed message Handler")
    fmt.Fprintf(w,"message...")
}

func iconHandler(w http.ResponseWriter, r *http.Request){

}

func main() {
    http.HandleFunc("/favicon",iconHandler)
    indexHandler := http.HandlerFunc(index)
    messageHandler := http.HandlerFunc(message)

    http.Handle("/",middlewareFirst(middlewareSecond(indexHandler)))
    http.Handle("/message",middlewareFirst(middlewareSecond(messageHandler)))

    server  := &http.Server{
        Addr:":8080",
    }

    log.Println("Listen...")

    server.ListenAndServe()

}

在上面代碼中其實也沒有什么特別之處,就是我們創建了兩個middlewareFirstmiddlewareSecond兩個中間件並且有一定先后順序,然后在 middlewareSecond中寫了一個對訪問權限校驗的邏輯。大家可以嘗試地去運行一下。

 

 

https://segmentfault.com/a/1190000018819804

中間件是一種計算機軟件,可為操作系統提供的軟件應用程序提供服務,以便於各個軟件之間的溝通,特別是系統軟件和應用軟件。廣泛用於web應用和面向服務的體系結構等。

縱觀GO語言,中間件應用比較普遍,主要應用:

  • 記錄對服務器發送的請求(request)
  • 處理服務器響應(response )
  • 請求和處理之間做一個權限認證工作
  • 遠程調用
  • 安全
  • 等等

中間件處理程序是簡單的http.Handler,它包裝另一個http.Handler做請求的一些預處理和/或后處理。它被稱為“中間件”,因為它位於Go Web服務器和實際處理程序之間的中間位置。

下面是一些中間件例子

記錄日志中間件

package main

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

func logging(f http.HandlerFunc) http.HandlerFunc {
   return func(w http.ResponseWriter, r *http.Request) {
      log.Println(r.URL.Path)
      f(w, r)
   }
}
func foo(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintln(w, "foo")
}

func bar(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintln(w, "bar")
}

func main() {
   http.HandleFunc("/foo", logging(foo))
   http.HandleFunc("/bar", logging(bar))
   http.ListenAndServe(":8080", nil)
}

訪問 http://localhost:8080/foo

返回結果

foo

將上面示例修改下,也可以實現相同的功能

package main

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

func foo(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintln(w, "foo")
}
func bar(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintln(w, "bar")
}

func loggingMiddleware(next http.Handler) http.Handler {
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
      log.Println(r.URL.Path)
      next.ServeHTTP(w, r)
   })
}

func main() {

   http.Handle("/foo", loggingMiddleware(http.HandlerFunc(foo)))
   http.Handle("/bar", loggingMiddleware(http.HandlerFunc(bar)))
   http.ListenAndServe(":8080", nil)
}

訪問 http://localhost:8080/foo

返回結果

foo

多中間件例子

package main

import (
   "fmt"
   "log"
   "net/http"
   "time"
)

type Middleware func(http.HandlerFunc) http.HandlerFunc

// Logging logs all requests with its path and the time it took to process
func Logging() Middleware {

   // Create a new Middleware
   return func(f http.HandlerFunc) http.HandlerFunc {

      // Define the http.HandlerFunc
      return func(w http.ResponseWriter, r *http.Request) {

         // Do middleware things
         start := time.Now()
         defer func() { log.Println(r.URL.Path, time.Since(start)) }()

         // Call the next middleware/handler in chain
         f(w, r)
      }
   }
}

// Method ensures that url can only be requested with a specific method, else returns a 400 Bad Request
func Method(m string) Middleware {

   // Create a new Middleware
   return func(f http.HandlerFunc) http.HandlerFunc {

      // Define the http.HandlerFunc
      return func(w http.ResponseWriter, r *http.Request) {

         // Do middleware things
         if r.Method != m {
            http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
            return
         }

         // Call the next middleware/handler in chain
         f(w, r)
      }
   }
}

// Chain applies middlewares to a http.HandlerFunc
func Chain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
   for _, m := range middlewares {
      f = m(f)
   }
   return f
}

func Hello(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintln(w, "hello world")
}

func main() {
   http.HandleFunc("/", Chain(Hello, Method("GET"), Logging()))
   http.ListenAndServe(":8080", nil)
}

中間件本身只是將其http.HandlerFunc作為其參數之一,包裝它並返回一個新http.HandlerFunc的服務器來調用。在這里,我們定義了一種新類型Middleware,最終可以更容易地將多個中間件鏈接在一起。

當然我們也可以改成如下形式

package main

import (
   "fmt"
   "log"
   "net/http"
   "time"
)

type Middleware func(http.Handler) http.Handler

func Hello(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintln(w, "hello world")
}

func Chain(f http.Handler, mmap ...Middleware) http.Handler {
   for _, m := range mmap {
      f = m(f)
   }
   return f
}
func Method(m string) Middleware {
   return func(f http.Handler) http.Handler {
      return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
         log.Println(r.URL.Path)
         if r.Method != m {
            http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
            return
         }
         f.ServeHTTP(w, r)
      })
   }

}
func Logging() Middleware {
   return func(f http.Handler) http.Handler {
      return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
         //log.Println(r.URL.Path)
         // Do middleware things
         start := time.Now()
         defer func() { log.Println(r.URL.Path, time.Since(start)) }()
         f.ServeHTTP(w, r)
      })
   }
}

func main() {
   http.Handle("/", Chain(http.HandlerFunc(Hello), Method("GET"), Logging()))
   http.ListenAndServe(":8080", nil)
}

在gin框架下實現中間件
r := gin.Default() 創建帶有默認中間件的路由,默認是包含logger和recovery中間件的
r :=gin.new()      創建帶有沒有中間件的路由

示例

package main

import (
   "github.com/gin-gonic/gin"
   "log"
   "time"
)

func Logger() gin.HandlerFunc {
   return func(c *gin.Context) {
      t := time.Now()
      // Set example variable
      c.Set("example", "12345")
      // before request
      c.Next()
      // after request
      latency := time.Since(t)
      log.Print(latency) //時間  0s
      // access the status we are sending
      status := c.Writer.Status()
      log.Println(status) //狀態 200
   }
}
func main() {
   r := gin.New()
   r.Use(Logger())

   r.GET("/test", func(c *gin.Context) {
      example := c.MustGet("example").(string)

      // it would print: "12345"
      log.Println(example)
   })

   // Listen and serve on 0.0.0.0:8080
   r.Run(":8080")
}
以上示例也可改為
package main

import (
   "github.com/gin-gonic/gin"
   "log"
   "time"
)

func Logger() gin.HandlerFunc {
   return func(c *gin.Context) {
      t := time.Now()
      // Set example variable
      c.Set("example", "12345")
      // before request
      c.Next()
      // after request
      latency := time.Since(t)
      log.Print(latency) //時間  0s
      // access the status we are sending
      status := c.Writer.Status()
      log.Println(status) //狀態 200
   }
}

func main() {
   r := gin.New()
   r.GET("/test", Logger(), func(c *gin.Context) {
      example := c.MustGet("example").(string)
      // it would print: "12345"
      log.Println(example)
   })
   // Listen and serve on 0.0.0.0:8080
   r.Run(":8080")
}

即不用r.use添加中間件,直接將Logger() 寫到r.GET 方法的參數里("/test"之后)。

更多gin中間件示例可參考 https://github.com/gin-gonic/gin

在 Gin 中,接入中間件也非常簡單:

router := gin.Default()
router.Use(middleware.RequestMiddleWare())

上面這段代碼是在所有的 API 中接入,如果僅僅想在某些 API 上接入,可以使用 Gin 的路由分組:

group := router.Group("/api/v1", middleware.RequestMiddleWare())


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM