Go Web學習(1)——標準庫http實現server

最近放假在家好好學習了一下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功能。好了,今天寫到這裏,太晚了(笑)。

相關文章
相關標籤/搜索