[譯] Go中的HTTP請求處理概述

使用 Go 處理 HTTP 請求主要涉及兩件事:ServeMuxes 和 Handlers。git

ServeMux本質上是一個 HTTP 請求路由器(或多路複用器)。它將傳入的請求與預約義的 URL 路徑列表進行比較,並在找到匹配時調用路徑的關聯 handler。github

handler 負責寫入響應頭和響應體。幾乎任何對象均可以是 handler,只要它知足http.Handler接口便可。在非專業術語中,這僅僅意味着它必須是一個擁有如下簽名的ServeHTTP方法:golang

ServeHTTP(http.ResponseWriter, *http.Request)數據庫

Go 的 HTTP 包附帶了一些函數來生成經常使用的 handler,例如FileServerNotFoundHandlerRedirectHandler。讓咱們從一個簡單的例子開始:瀏覽器

$ mkdir handler-example
$ cd handler-example
$ touch main.go
複製代碼

File: main.go安全

package main

import (
  "log"
  "net/http"
)

func main() {
  mux := http.NewServeMux()

  rh := http.RedirectHandler("http://example.org", 307)
  mux.Handle("/foo", rh)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
複製代碼

讓咱們快速介紹一下:閉包

  • main函數中,咱們使用http.NewServeMux函數建立了一個空的ServeMux。
  • 而後咱們使用http.RedirectHandler函數建立一個新的handler。該handler將其接收的全部請求307重定向到http://example.org。
  • 接下來咱們使用mux.Handle函數向咱們的新ServeMux註冊它,所以它充當URL路徑/foo的全部傳入請求的handler。
  • 最後,咱們建立一個新服務並使用http.ListenAndServe函數開始監聽傳入的請求,並傳入ServeMux給這個方法以匹配請求。

繼續運行應用程序:函數

$ go run main.go
Listening...
複製代碼

並在瀏覽器中訪問http://localhost:3000/foo。你會發現請求已經被成功重定向。測試

你可能已經注意到了一些有趣的東西:ListenAndServe 函數的簽名是ListenAndServe(addr string, handler Handler),但咱們傳遞了一個 ServeMux 做爲第二個參數。ui

能這麼作是由於 ServeMux 類型也有一個 ServeHTTP 方法,這意味着它也知足 Handler 接口。

對我而言,它只是將 ServeMux 視爲一種特殊的 handler,而不是把響應自己經過第二個 handler 參數傳遞給請求。這不像剛剛據說時那麼驚訝 - 將 handler 連接在一塊兒在 Go 中至關廣泛。

自定義 handler

咱們建立一個自定義 handler,它以當前本地時間的指定格式響應:

type timeHandler struct {
  format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  tm := time.Now().Format(th.format)
  w.Write([]byte("The time is: " + tm))
}
複製代碼

這裏確切的代碼並不過重要。

真正重要的是咱們有一個對象(在該示例中它是一個timeHandler結構,它一樣能夠是一個字符串或函數或其餘任何東西),而且咱們已經實現了一個帶有簽名ServeHTTP(http.ResponseWriter, *http.Request)的方法。這就是咱們實現一個 handler 所需的所有內容。

讓咱們將其嵌入一個具體的例子中:

File: main.go

package main

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

type timeHandler struct {
  format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  tm := time.Now().Format(th.format)
  w.Write([]byte("The time is: " + tm))
}

func main() {
  mux := http.NewServeMux()

  th := &timeHandler{format: time.RFC1123}
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
複製代碼

main函數中,咱們使用符號生成指針,用與普通結構徹底相同的方式初始化timeHandler。而後,與前面的示例同樣,咱們使用mux.Handle函數將其註冊到咱們的ServeMux。

如今,當咱們運行應用程序時,ServeMux會將任何經過/time路徑的請求直接傳遞給咱們的timeHandler.ServeHTTP方法。

試一試:http://localhost:3000/time

另請注意,咱們能夠輕鬆地在多個路徑中重複使用timeHandler:

func main() {
  mux := http.NewServeMux()

  th1123 := &timeHandler{format: time.RFC1123}
  mux.Handle("/time/rfc1123", th1123)

  th3339 := &timeHandler{format: time.RFC3339}
  mux.Handle("/time/rfc3339", th3339)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
複製代碼

普通函數做爲 handler

對於簡單的狀況(如上例),定義新的自定義類型和 ServeHTTP 方法感受有點囉嗦。讓咱們看看另外一個方法,咱們利用 Go 的http.HandlerFunc類型來使正常的函數知足 Handler 接口。

任何具備簽名func(http.ResponseWriter, *http.Request)的函數均可以轉換爲 HandlerFunc 類型。這頗有用,由於 HandleFunc 對象帶有一個內置的ServeHTTP方法 - 這很是巧妙且方便 - 執行原始函數的內容。

若是這聽起來使人費解,請嘗試查看相關的源代碼。你將看到它是一種讓函數知足 Handler 接口的很是簡潔的方法。

咱們使用這種方法來重寫 timeHandler 應用程序:

File: main.go

package main

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

func timeHandler(w http.ResponseWriter, r *http.Request) {
  tm := time.Now().Format(time.RFC1123)
  w.Write([]byte("The time is: " + tm))
}

func main() {
  mux := http.NewServeMux()

  // Convert the timeHandler function to a HandlerFunc type
  th := http.HandlerFunc(timeHandler)
  // And add it to the ServeMux
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
複製代碼

事實上,將函數轉換爲HandlerFunc類型,而後將其添加到ServeMux的狀況比較常見,Go提供了一個快捷的轉換方法:mux.HandleFunc方法。

若是咱們使用這個轉換方法,main()函數將是這個樣子:

func main() {
  mux := http.NewServeMux()

  mux.HandleFunc("/time", timeHandler)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
複製代碼

大多數時候使用這樣的 handler 頗有效。可是當事情變得愈來愈複雜時,將會受限。

你可能已經注意到,與以前的方法不一樣,咱們必須在timeHandler函數中對時間格式進行硬編碼。當咱們想要將信息或變量從main()傳遞給 handler 時會發生什麼?

一個簡潔的方法是將咱們的 handler 邏輯放入一個閉包中,把咱們想用的變量包起來:

File: main.go

package main

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

func timeHandler(format string) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  }
  return http.HandlerFunc(fn)
}

func main() {
  mux := http.NewServeMux()

  th := timeHandler(time.RFC1123)
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}
複製代碼

timeHandler函數如今有一點點不一樣。如今使用它來返回handler,而不是將函數強制轉換爲handler(就像咱們以前所作的那樣)。能這麼作有兩個關鍵點。

首先它建立了一個匿名函數fn,它訪問造成閉包的format變量。不管咱們如何處理閉包,它老是可以訪問它做用域下所建立的局部變量 - 在這種狀況下意味着它老是能夠訪問format變量。

其次咱們的閉包有簽名爲func(http.ResponseWriter, *http.Request)的函數。你可能還記得,這意味着咱們能夠將其轉換爲HandlerFunc類型(以便它知足Handler接口)。而後咱們的timeHandler函數返回這個轉換後的閉包。

在這個例子中,咱們僅僅將一個簡單的字符串傳遞給handler。但在實際應用程序中,您可使用此方法傳遞數據庫鏈接,模板映射或任何其餘應用程序級的上下文。它是全局變量的一個很好的替代方案,而且可使測試的自包含handler變得更整潔。

你可能還會看到相同的模式,以下所示:

func timeHandler(format string) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  })
}
複製代碼

或者在返回時使用隱式轉換爲 HandlerFunc 類型:

func timeHandler(format string) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  }
}
複製代碼

DefaultServeMux

你可能已經看到過不少地方提到的 DefaultServeMux,包括最簡單的 Hello World 示例到 Go 源代碼。

我花了很長時間才意識到它並不特別。 DefaultServeMux 只是一個普通的 ServeMux,就像咱們已經使用的那樣,默認狀況下在使用 HTTP 包時會實例化。如下是 Go 源代碼中的相關行:

var DefaultServeMux = NewServeMux()
複製代碼

一般,你不該使用 DefaultServeMux,由於它會帶來安全風險

因爲 DefaultServeMux 存儲在全局變量中,所以任何程序包均可以訪問它並註冊路由 - 包括應用程序導入的任何第三方程序包。若是其中一個第三方軟件包遭到破壞,他們可使用 DefaultServeMux 向 Web 公開惡意 handler。

所以,根據經驗,避免使用 DefaultServeMux 是一個好主意,取而代之使用你本身的本地範圍的 ServeMux,就像咱們到目前爲止同樣。但若是你決定使用它……

HTTP 包提供了一些使用 DefaultServeMux 的便捷方式:http.Handlehttp.HandleFunc。這些與咱們已經看過的同名函數徹底相同,不一樣之處在於它們將 handler 添加到 DefaultServeMux 而不是你本身建立的 handler。

此外,若是沒有提供其餘 handler(即第二個參數設置爲nil),ListenAndServe 將退回到使用 DefaultServeMux。

所以,做爲最後一步,讓咱們更新咱們的 timeHandler 應用程序以使用 DefaultServeMux:

File: main.go

package main

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

func timeHandler(format string) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(format)
    w.Write([]byte("The time is: " + tm))
  }
  return http.HandlerFunc(fn)
}

func main() {
  // Note that we skip creating the ServeMux...

  var format string = time.RFC1123
  th := timeHandler(format)

  // We use http.Handle instead of mux.Handle...
  http.Handle("/time", th)

  log.Println("Listening...")
  // And pass nil as the handler to ListenAndServe.
  http.ListenAndServe(":3000", nil)
}
複製代碼

若是你喜歡這篇博文,請不要忘記查看個人新書《用 Go 構建專​​業的 Web 應用程序》

在推特上關注我 @ajmedwards

此文章中的全部代碼均可以在MIT Licence許可下無償使用。

相關文章
相關標籤/搜索