最近放假在家好好學習了一下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,不須要任何容器即可以實現一個高併發的簡單服務器。下面咱們來分析一下這個代碼:bash
首先,咱們導入了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)
複製代碼
這個函數自己並無實現什麼,須要咱們本身去實現它的內容。也就是咱們上面所提到的響應函數。ui
// 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接口。url
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功能。好了,今天寫到這裏,太晚了(笑)。