最近放假在家好好學習了一下Go語言,Go作為Google官推的Server語言,因為天生的併發性和完備的標準庫讓Go語言在服務端如魚得水。筆者在簡單的學習了之後,真的是驚訝連連,好了進入正題。
首先,我們必須實現一個Go Web版的Hello World。
package main
import (
"fmt"
"net/http"
"log"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s\n", "Hello World")
})
err := http.ListenAndServe(":8000", nil)
if err != nil {
log.Fatal(err)
}
}
複製程式碼
我們可以看到Go語言實現一個Web HelloWorld的簡潔程度甚至直接媲美Node.js,不需要任何容器便可以實現一個高併發的簡單伺服器。下面我們來分析一下這個程式碼:
首先,我們匯入了fmt,http包,log包其實對於HelloWorld來說並沒有匯入的必要,但是日誌輸出這個良好習慣還是得遵從。在main()函式的第一行,我們通過http.HandleFunc定義了路由為"/"的響應函式,這個響應函式,接受傳來的Request,並對Response做一定的處理即寫入HelloWorld然後直接返回給瀏覽器。然後便可以直接呼叫http.ListenAndServe來監聽本地的8000埠,便可以直接在瀏覽器上看到HelloWorld。
好,上面的流程其實很簡單,有一定Web程式設計的人便都能明白,接下來我們便從Go的原始碼中看一看,這段程式碼究竟是如何實現的。
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
複製程式碼
上面這段便是Go原始碼中對HandleFunc函式的實現,我們可以看到這個函式直接將所有引數全部傳遞給了DefaultServeMux.HandleFunc來呼叫。
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
複製程式碼
DefaultServeMux是http包中的全域性變數,它的原型是ServeMux這個結構體,我們再往上翻看這個結構體的HandleFunc方法。
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
複製程式碼
我們可以看到,似乎沒完沒了,HandleFunc也是直接呼叫這個結構體的另一個方法Handle,另外HandlerFunc(handler)中的HandlerFunc也只是一個type的定義。
type HandlerFunc func(ResponseWriter, *Request)
複製程式碼
這個函式本身並沒有實現什麼,需要我們自己去實現它的內容。也就是我們上面所提到的響應函式。
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler)
複製程式碼
終於我們找到了源頭,當然這個方法的原始碼還比較長,這裡就不貼出全部,Handle這個方法接受兩個引數,pattern這個string型別的參數列示路由,第二個引數handle它其實是Handler介面。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
複製程式碼
可以看到Handler這個介面中只定義了ServeHTTP這一個方法,換句話說,我們也可以直接實現ServeHTTP這個方法來實現Handler這個介面,然後我們便可以傳給ServeMux來自定義我們的HelloWorld.
package main
import (
"fmt"
"net/http"
"log"
)
type CustomHandler struct{}
func (*CustomHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s\n", "Hello World")
}
func main() {
mux := http.NewServeMux()
mux.Handle("/", &CustomHandler{})
err := http.ListenAndServe(":8000", mux)
if err != nil {
log.Fatal(err)
}
}
複製程式碼
上面的程式碼可以看到,我們定義了一個CustomHandler,然後實現了ServeHTTP這個方法從而實現了Handler這個介面,在main方法中,我們通過NewServeMux建立了一個自己的mux而不去使用http內的預設ServerMux。然後呼叫ListenAndServe方法,並將自己的mux傳入,程式便會實現自定義的HelloWorld了。 接下來我們來看一下ListenAndServe這個方法:
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
複製程式碼
原始碼中可以看到該方法會將傳入進來的addr引數和handler送給Server這個結構體,從而新建一個server然後呼叫這個server的ListenAndServe方法,對於Server這個結構它已經是Go語言對於這個方面非常底層的實現了,它非常強大,而且實現了很多的方法,這裡不過多闡述,主要是實力不夠(笑)。 好,回到正題,既然如此,我們便可以自己建立Server這個例項,來自定義我們的HelloWorld的第二版本。
package main
import (
"fmt"
"net/http"
"log"
"time"
)
type CustomHandler struct{}
var mux = make(map[string]func(http.ResponseWriter, *http.Request))
func Hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s\n", "Hello World")
}
func (*CustomHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if handler, ok := mux[r.URL.String()]; ok {
handler(w, r)
}
}
func main() {
server := http.Server{
Addr:":8000",
Handler:&CustomHandler{},
ReadHeaderTimeout:5 * time.Second,
}
mux["/"] = Hello
err := server.ListenAndServe()
if err != nil {
log.Fatal(err)
}
}
複製程式碼
上面這段程式碼便是自創server的實現了,這裡挑選幾條新的程式碼說明一下,我們定義了一個mux的全域性變數,它來裝配我們的路由與相應函式的對映,相當於上面的mux.Handle("/", .....),這裡比較簡陋的直接用Map來實現,接下來我們定義了Hello這個響應函式,我們也重寫了ServeHTTP這個方法,它會判斷request的url路徑與我們mux裡面的路徑是否匹配,如果匹配在從mux中取出相應的響應函式並將w http.ResponseWriter, r *http.Request這兩個引數傳遞給這個相應函式。
在main函式裡,我們建立了自己的server,通過埠號,Handler及timeout時間來定義它,然後呼叫它的ListenAndServe方法,便可以實現與前面兩個相同的HelloWorld功能。好了,今天寫到這裡,太晚了(笑)。