GO隨筆-搭建一個Web伺服器

HammerMax發表於2019-02-16

用GO搭建一個Web伺服器

有人曾和我說過,一門語言應該能夠自己實現一個HTTP服務。PHP做不到,但GO卻輕而易舉。
只需要幾行程式碼,GO就可以實現一個簡單的HTTP服務。

package main

import (
    "fmt"
    "net/http"
)

func IndexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello world")
}

func main() {
    http.HandleFunc("/", IndexHandler)
    http.ListenAndServe(":8001", nil)
}

這樣,即可監聽8001埠,並且當有請求訪問的時候完成響應。(這幾乎就完成了nginx的主要功能,不過沒有做負載平衡)
觀察這段程式碼,我們基本能夠從命名大概瞭解每個函式的作用。

http.HandleFunc("/", IndexHandler)

這相當於繫結url的函式,當請求的url是”/”時,那麼將這個請求交給IndexHandler來處理。

http.ListenAndServe(":8001", nil)

顧名思義,監聽8001埠並且啟動服務。

func IndexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello world")
}

處理函式,當有請求的時候輸出hello world。
通過以上分析,GO搭建一個HTTP服務只需用到一個net/http包即可。同時結合上篇文章,要完成http服務需要以下幾點

  • Request 使用者請求的資訊,用來解析使用者的請求資訊,包括post、get、cookie、url等資訊
  • Response 服務端反饋給客戶端的資訊
  • Conn 使用者每次請求的連線
  • Handler 處理請求和生產返回資訊的處理邏輯

前3點是一個HTTP服務必須要的結構,第4點是每一個HTTP服務的核心所在。
我們只需要瞭解3個問題,就知道GO是如何將Web服務運作起來。

  • 如何監聽介面
  • 如何接受客戶端請求
  • 如何分配handle

如何監聽介面

GO是通過一個函式 ListenAndServe做到的。
首先初始化一個server物件,然後呼叫net.Listen("tcp", addr)底層建立TCP連線,監聽我們設定的埠。

如何接收客戶端請求

這塊的原始碼

func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    for {
        rw, e := l.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c, err := srv.newConn(rw)
        if err != nil {
            continue
        }
        go c.serve()
    }
}

這段程式碼首先起了一個for()。接收請求Accept(),若有請求,則建立一個Conn連線,並用協程啟動服務。這時再次進入下一個迴圈,若有請求則新建立一個連線並啟動一個協程服務。若沒有請求,則休息一段時間後再次迴圈。正是利用了go協程特性,使用者的每一個請求都有一個新的goroutine去服務,互不影響,達到了天然支援高併發特性。

如何分配handle

在建立conn之後,conn會首先解析request,c.readRequest(),然後獲得相應的handle:handler := c.server.Handler,也就是我們剛才在呼叫函式ListenAndServe時候的第二個引數,我們前面例子傳遞的是nil,也就是為空,那麼預設獲取handler = DefaultServeMux,那麼這個變數用來做什麼的呢?對,這個變數就是一個路由器,它用來匹配url跳轉到其相應的handle函式,那麼這個我們有設定過嗎?有,我們呼叫的程式碼裡面第一句不是呼叫了http.HandleFunc("/", IndexHandler)嘛。這個作用就是註冊了請求/的路由規則,當請求uri為”/”,路由就會轉到函式IndexHandlerDefaultServeMux會呼叫ServeHTTP方法,這個方法內部其實就是呼叫IndexHandler本身,最後通過寫入response的資訊反饋到客戶端。

相關文章