golang基礎-http server

golang實現一個簡單的http server

若是搜索golang http server,會發現網上有不少不一樣的寫法,本節將介紹多種寫法,並把他們的關係捋清楚。golang

寫法1

直接傳入函數web

func SayHello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
}

func main() {
	http.HandleFunc("/say_hello", SayHello)
	err := http.ListenAndServe(":12345", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
複製代碼

寫法2

定義結構體,須要實現ServerHTTP方法。編程

type ValueHandler struct {}
func (p *ValueHandler) ServerHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("value-pretty"))
}
func main() {
	http.Handle("/get_value", &ValueHandler)
	err := http.ListenAndServe(":12345", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
複製代碼

1,2兩種寫法極其類似,區別在於寫法2須要一個結構體,而且必須實現ServerHTTP這個接口。1實際上是對2的簡化,至關於在net/http幫你們定義了一個實現了ServerHTTP的函數定義。json

  • http.HandleFunc 註冊了路由關係
  • http.ListenAndServe表示啓動了一個服務。

寫法3

顯示使用Mux,Mux即multiplexor,保存了路由和方法的映射。能夠記錄多個url和handler的對應關係。bash

func SayHello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/say_hello", SayHello)
    err := http.ListenAndServe(":12345", mux)
    if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
複製代碼

這裏可能會問,http.HanlderFunc沒有使用mux,也能夠註冊多路映射。實際是底層有DefaultMux,在http Server中若沒有傳遞,即用默認的。如下是http.HanlderFunc定義:app

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux
複製代碼

寫法4

使用server.ListenAndServe框架

func SayHello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/say_hello", SayHello)

	server := http.Server{
		Addr:        ":12345",
		Handler:     serveMux,
		ReadTimeout: 5 * time.Second,
	}
    err := server.ListenAndServe()
    if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
複製代碼

和寫法3的區別,在於監聽函數http.ListenAndServe,替換爲了server.ListenAndServe 。其實Server結構體是必然存在的,http.ListenAndServe只是作了封裝,實際仍是生成了一個Server字段,默認填了addr, handler兩個參數。如下是http.ListenAndServe定義tcp

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

複製代碼

顯示指定http.Server,能夠靈活選擇更多配置。好比超時時間。函數

小結

四種寫法本質上是同樣的,建議使用最完整的方法4。 由於其能提供更完整,靈活的配置,同時也並不複雜。ui

流程分析

經過對http包的分析以後,如今讓咱們來梳理一下整個的代碼執行過程。

HandleFunc 流程

若是是Http.HandleFunc,首先會調用調用了DefaultServeMux的HandleFunc。

進入實際流程:

  • 調用了DefaultServeMux的Handle
  • 往路由映射表map[string]muxEntry,添加對應的handler和路由規則
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}
複製代碼

ListenAndServe流程

若http.ListenAndServe(":9090", nil),首先實例化Server

server := &Server{Addr: addr, Handler: handler}

type Server struct {
    Addr    string  // TCP address to listen on, ":http" if empty
    Handler Handler // handler to invoke, http.DefaultServeMux if nil

    TLSConfig *tls.Config
    ReadTimeout time.Duration
    ReadHeaderTimeout time.Duration
    WriteTimeout time.Duration
    IdleTimeout time.Duration
    MaxHeaderBytes int
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
    ConnState func(net.Conn, ConnState)
    ErrorLog *log.Logger

    disableKeepAlives int32     // accessed atomically.
    inShutdown        int32     
    nextProtoOnce     sync.Once // guards setupHTTP2_* init
    nextProtoErr      error     

    mu         sync.Mutex
    listeners  map[*net.Listener]struct{}
    activeConn map[*conn]struct{}
    doneChan   chan struct{}
    onShutdown []func()
}
複製代碼

Server結構比較複雜,關注ReadTimeout,WriteTimeout,Handler幾個經常使用字段便可。

實際流程:

  • 調用Server的ListenAndServe()
  • 調用net.Listen("tcp", addr)監聽端口,拿到Listen的實例
  • srv.Serve 傳入上面的Listen示例
  • Accept請求,每收到一個請求,開啓一個協程處理
  • 協程中會根據路由和handler的映射,選擇handler來處理請求

處理函數如何拿到參數

the http.Request 包含了請求的全部信息,咱們能夠從中拿出請求的參數:

  • 針對Get 請求: vars := r.URL.Query(); vars["username"][0]
  • 針對Post 表單請求,r.ParseForm(); r.Form("username")
  • 針對Post json請求: 解析r.Body
// deal get
func SayHello(w http.ResponseWriter, r *http.Request) {
    vars := r.URL.Query()
    username := vars["username"][0]
	io.WriteString(w, "hello world " + username)
}

// 處理表單
func SayHello(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()
    username, err :=  request.Form["username"]
	io.WriteString(w, "hello world " + username)
}

//deal json
type User struct {
    UserName string
}
func SayHello(w http.ResponseWriter, r *http.Request) {
        err := json.NewDecoder(r.Body).Decode(&u)
        if err != nil {
            http.Error(w, err.Error(), 400)
            return
        }
        io.WriteString(w, "hello world " + u.Username)
}
複製代碼

中間件抽象

若是須要計算接口處理時間,那麼咱們能夠這麼寫

func SayHello(w http.ResponseWriter, r *http.Request) {
        timeStart := time.Now()
        err := json.NewDecoder(r.Body).Decode(&u)
        if err != nil {
            http.Error(w, err.Error(), 400)
            return
        }
        io.WriteString(w, "hello world " + u.Username)
        timeElapsed := time.Since(timeStart)
        fmt.Println(timeElapsed)
}

複製代碼

存在一個問題,就是若是有二十個接口,那一樣的代碼得寫20次,當有相似需求增長,或者修改時。須要改幾十處。這些一樣的代碼,咱們要想辦法抽象起來。

func timeMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
        timeStart := time.Now()

        // next handler
        next.ServeHTTP(wr, r)

        timeElapsed := time.Since(timeStart)
        fmt.Println(timeElapsed)
    })
}

複製代碼

如此就能夠經過中間件的思想,抽象出公用代碼。並且這種中間件思想,還能夠嵌套使用。擁有很強的擴展性。

更優雅的寫法

// 示例來自《go語言高級編程》
r = NewRouter()
r.Use(timeMiddleware)
r.Use(ratelimitMiddleware)
r.Add("/", SayHello)
複製代碼
type middleware func(http.Handler) http.Handler

type Router struct {
    middlewareChain [] middleware
    mux map[string] http.Handler
}

func NewRouter() *Router{
    return &Router{}
}

func (r *Router) Use(m middleware) {
    r.middlewareChain = append(r.middlewareChain, m)
}

func (r *Router) Add(route string, h http.Handler) {
    var mergedHandler = h

    for i := len(r.middlewareChain) - 1; i >= 0; i-- {
        mergedHandler = r.middlewareChain[i](mergedHandler)
    }

    r.mux[route] = mergedHandler
}
複製代碼

web框架

在實際開發中,基本上都會用到一些完善的框架,好比gin, beego, echo等。 均提供了比較方便,簡單的編程架子。而且擁有良好的程序結構。 後續會針對其中一些進行單獨介紹。

相關文章
相關標籤/搜索