使用 Go 建立 HTTP 伺服器

前言

Go 語言的標準庫 net/http 提供了 HTTP 客戶端和伺服端的相關方法和實作。

範例

以下是一個監聽在 8081 埠的伺服器的範例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"net/http"
)

type indexHandler struct{}

func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, world!"))
}

func main() {
http.Handle("/", &indexHandler{})
http.ListenAndServe(":8081", nil)
}

由於 indexHandler 結構實作了 Handler 介面,因此可以為特定的路徑提供服務。ServeHTTP 方法會將數據寫入 http.ResponseWriter 介面並且返回,以下是 Handler 介面的原型。

1
2
3
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

http.Handle 方法接受兩個參數:第一個參數是 URL 路徑,第二個參數是實作了 Handler 介面的結構。

http.ListenAndServe 方法監聽在某個埠,每次客戶端有請求時,會將請求封裝成 http.Request 物件,並調用對應的處理器的 ServeHTTP 方法,最後將操作後的 http.ResponseWriter 介面解析,返回到客戶端。

封裝

標準庫 net/http 還提供了 http.HandleFunc 方法,允許直接將特定結構做為處理器。上面的程式碼可以改為:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"io"
"net/http"
)

func indexHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello, world!\n")
}

func main() {
http.HandleFunc("/", indexHandler)
http.ListenAndServe(":8081", nil)
}

http.HandleFunc 方法其實是一個轉接器,接受兩個參數:第一個參數是 URL 路徑,第二個參數是一個處理器函式。

使用 http.HandlerFunc 方法,可以將處理器函式轉換成 Handler 接口的實作對象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"io"
"net/http"
)

func indexHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello, world!\n")
}

func main() {
index := http.HandlerFunc(indexHandler)
http.Handle("/", index)
http.ListenAndServe(":8081", nil)
}

以下是 http.HandlerFunc 方法的原型:

1
2
3
4
5
6
7
8
9
10
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

預設

標準庫 net/http 提供一些使用頻繁的處理器,例如處理文件的 FileServer、處理 404 的 NotFoundHandler 或處理導向的 RedirectHandler。以下是一個靜態文件伺服器的範例:

1
2
3
4
5
6
7
8
9
package main

import (
"net/http"
)

func main() {
http.ListenAndServe(":8081", http.FileServer(http.Dir(".")))
}

路由

ServeMux 可以註冊多個 URL 和處理器的對應關系,並自動把請求轉發到對應的處理器進行處理。Mux 是 multiplexor 的縮寫,就是多路傳輸的意思。以下是使用 ServeMux 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"io"
"net/http"
)

func indexHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, r.URL.Path)
}

func aboutHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello, world!\n")
}

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

mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/about", aboutHandler)

http.ListenAndServe(":8081", mux)
}

這裡透過 http.NewServeMux 方法建立 ServerMux 結構,引此 URL 和處理器可以通過它進行註冊。而 http.ListenAndServe 方法的第二個參數需要放的是控制器,而 mux 變數實際上也是 Handler 介面的實現。以下是 ServeMux 的原型:

1
2
3
4
5
6
7
8
9
type ServeMux struct {
// contains filtered or unexported fields
}

func NewServeMux() *ServeMux
func (mux *ServeMux) Handle(pattern string, handler Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

關於 ServeMux

  • URL 分為兩種,末尾是 / 表示一個子路徑;末尾不是 /,表示一個固定的路徑。
  • / 結尾的 URL 可以匹配它的任何子路徑,比如 /images/ 會匹配 /images/cat.jpg
  • 它採用最長匹配原則,如果有多個匹配,一定採用匹配路徑最長的那個進行處理。
  • 如果沒有找到任何匹配項,會返回 404 錯誤。
  • 它可以識別和處理 ... 符號,轉換成對應的 URL 地址。

先前沒有使用 ServeMux 就實現了路由,是因為 net/http 標準庫預設使用了 DefaultServeMux

套件

標準庫 net/http 提供許多基本功能,但很多時候還是不夠方便,比如:

  • 不支援 URL 匹配,所有的路徑必須完全匹配,不能捕獲 URL 中的變數,不夠靈活。
  • 不支持 HTTP 方法匹配
  • 不支持擴展和巢狀路由,URL 處理都在都一個 ServeMux 變數中

以下是可用的第三方套件:

參考資料