這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。編寫 go 中介軟體看起來挺簡單的,但是有些情況下我們可能會遇到一些麻煩。讓我們來看一些例子。## 讀取請求我們例子中的所有中介軟體都會接收一個 `http.Handler` 作為參數,並返回一個 `http.Handler` 。這樣便於將中介軟體連結起來。我們所有的中介軟體將會遵循如下基本模式:```gofunc X(h http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {// Something here...// 這裡還有一些其他資訊h.ServeHTTP(w, r)})}```假設我們想要將所有請求重新導向到一個末尾斜杠(例如,`/messages/`),這與重新導向到它們的“非跟蹤-斜杠”(比如 `/messages`)是等價的。我們可以這麼寫 :```gofunc 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)})}```如此簡單。## 修改請求 假設我們要向請求添加一個頭部資訊,或者修改它。`http.Handler` 文檔說明如下: >除了讀取主體之外,處理常式不應修改所提供的請求。 Go 標註庫在[傳遞 `http.Request` 對象到響應鏈之前會先拷貝 `http.Request`](https://golang.org/src/net/http/server.go#L1981),我們也應該這樣做。 假設我們要為每個請求設定一個 `Request-Id` 頭部資訊,用於內部跟蹤。 建立 `*Request` 的一個淺拷貝,並在代理之前修改頭。 ```gofunc RequestID(h http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {r2 := new(http.Request)*r2 = *rr2.Header.Set("X-Request-Id", uuid.NewV4().String())h.ServeHTTP(w, r2)})}```## 寫入回應標頭資訊如果你想設定回應標頭資訊,你可以編寫它們,然後代理請求。```gofunc 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`,但是如果使用者試圖寫一個響應,我們會偷偷添加頭部資訊。```gotype serverWriter struct {w http.ResponseWritername stringwroteHeaders 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)// 如果使用者從不調用 `WriteHeader`,我們就會遇到這種情況。s.w.Header().Set("Server", s.name)s.wroteHeader = true}return s.w.Write(b)}```要將它用於我們的中介軟體,我們會這麼寫: ```gofunc 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` 呢?如果使用者不調用 `Write` 或 `WriteHeader` 方法,比如一個狀態代碼為 200 的空響應體,或者一個對可選請求的響應,對於這些情況我們的攔截函數都不會被執行。所以,鑒於這種情況我們應當在 `ServeHTTP` 調用之後添加至少一個檢查。```gofunc 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支援,這是不好的。```go// Push implements the http.Pusher interface.// Push 實現 http.Pusher 介面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.// Flush 實現 http.Flusher 介面func (s *serverWriter) Flush() {f, ok := s.w.(http.Flusher)if ok {f.Flush()}}```## 就是這樣祝你好運!你正在寫什麼中介軟體呢,它們啟動並執行怎樣?
via: https://kev.inburke.com/kevin/how-to-write-go-middleware/
作者:Kevin Burke 譯者:SergeyChang 校對:rxcai
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
802 次點擊 ∙ 1 贊