Go Programming Language http

Go 因為其性能被很多人青睞,當然和所有語言一樣難免也會有讓人深惡痛絕的地方。最近在學習這塊的東西,鑒於之前仔細了解過Python 的TCPServer以及HTTPServer 的實現,本文打算對Go的http 模塊做一些簡單梳理,以便了解Go語言的一些特性,主要是想從Go的源碼中吸收一些代碼組織的方法和思路。

知乎不支持代碼高亮,可以去我的博客查看原文Go Programming Language http

Python HTTPServer 的簡單邏輯

我們知道HTTP 協議是建立在TCP 之上的,Python 很好的利用了傳統的面向對像的編程思想。HTTPServer 是對SocketServer裡面TCPServer 的繼承。在Python 的源代碼里最最基礎的HTTPServer 叫BaseHTTPServer。

先來看如何啟動一個Server簡單的HTTPServer

import BaseHTTPServerdef run(server_class=BaseHTTPServer.HTTPServer,handler_class=BaseHTTPServer.BaseHTTPRequestHandler): server_address = (, 8000) httpd = server_class(server_address, handler_class) httpd.serve_forever()run()

下面對Python 的HTTPServer 的啟動到請求處理過程中發生的事情做簡單說明。

  1. 啟動一個Server首先調用Server 的server_forever() 方法,源碼,就會發現這其實就是一個死循環,不斷監聽著埠等待請求的到來。

def serve_forever(self, poll_interval=0.5): self.__is_shut_down.clear() try: while not self.__shutdown_request: r, w, e = _eintr_retry(select.select, [self], [], [], poll_interval) if self in r: self._handle_request_noblock() finally: self.__shutdown_request = False self.__is_shut_down.set()

當收到一個請求的時候,Python 會利用系統的select 或者thread的方式來實現非阻塞服務。具體是哪種方式就看Handler 的實現方式。為什麼又跳出來一個Handler .我們知道所有請求最終處理的邏輯都在Handler 里。那是用什麼方式來組織Server 和Handler 的?這就要說到混入(MixIn)。這種代碼的組織的好處在於,把變化的東西(對請求的處理邏輯)獨立出來,相同的東西都在Server 內部。簡單說下混入,我們看到上面啟動一個HTTPServer 的時候傳了兩個參數

httpd = server_class(server_address, handler_class)

第一個是server 監聽地址,第二個是handler ,下面的函數是最底層的Server 定義,可以看到最最基礎的Server 的初始化方法:

def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False

在Server 裡面會調用RequestHandlerClass 的process_request方法,當你在自己的Handler 的實現裡面傳入不同的Handler 自然就會有不同的邏輯。

Go http 模塊解讀

上面介紹的Python的實現方法,Go的實現邏輯又會怎樣,而且Go實現了一個更加強大的功能,相當於做好了一個類似Tornado,Flask 這樣的框架。

在看之前有下面幾個問題:

  1. Go 是如何組織Server 和Handler 的。這個問題主要是由於Go的語言特性,說MixIn這種東西好像不存在。
  2. Go 原生的路由機制如何?Go,在Python 最 基礎的實現根本就沒有看到路由的設計。
  3. Go的非阻塞實現機制是什麼?我們都知道Go的性能主要是有其獨特的goroutine ,goroutine 的細節我可以不知道,大致機理能否有簡單了解?

先來實現一個簡單的RestFul 的Server

package mainimport ( "net/http" "fmt" "log") func sayHelloName(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello astaxie!") } func main() { http.HandleFunc("/", sayHelloName) err := http.ListenAndServe(":9090", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } }

  1. 啟動一個Server 的時候調用的是http 模塊下的ListenAndServer函數,這個函數源碼如下,看到它會new 一個Server 然後調用Server 的ListenAndServer方法

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

  2. ListenAndServer 方法又會發生什麼?它會調用自己的Server方法(主要代碼如下),這個方法有點類似Python 的server_forever方法,裡面是個死循環,當收到請求就會新建一個conn,然後啟動一個goroutine 調用conn的serve方法,來處理請求,serve就收的參數是一個context(什麼是context後面解釋)。

baseCtx := context.Background() ctx := context.WithValue(baseCtx, ServerContextKey, srv) ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr()) 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 } srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay) time.Sleep(tempDelay) continue } return e } tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve(ctx)

  1. 那conn 是什麼?其server 方法邏輯如何。server 方法會調用下面這句,意思是用conn中的server 來產生了一個serverHandler 同時調用其ServerHTTP方法。

    serverHandler{c.server}.ServeHTTP(w, w.req)

我們先來看serrverHandler 的定義:

type serverHandler struct { srv *Server}

就是有個srv 是Server 類型。其ServerHTTP方法的實現如下:

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req)}

會看到它或從Server 的Handler 方法來找對應url的handler ,它會判斷handler 是否為空,如果為空就使用默認的DefaultServeMux,然後調用handler 的ServerHTTP方法

  1. 我們自己定義的sayHelloName方法去哪裡呢?玄機都在DefaultServeMux裡面了。這就要涉及到路由了,看DefaultServerMux的ServerHttp方法,

    func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { if r.ProtoAtLeast(1, 1) { w.Header().Set("Connection", "close") } w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) h.ServeHTTP(w, r)}

h, _ := mux.Handler(r)一句會會根據請求路徑從Map里找一個handler 來調用其ServerHTTP方法。

  1. 問題又來了,我們只定義了一個sayHelloName函數,然後調用了下面這句

    http.HandleFunc("/", sayHelloName)

    玄機就在裡面,它會把sayHelloName 函數轉化成HandlerFunc,而HandlerFunc實現了ServerHTTP方法

    type HandlerFunc func(ResponseWriter, *Request)func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r)}

    上面這個感覺很奇怪,HandlerFunc 是個函數……自行體會吧

    我們定義的路由怎麼會被Server 訪問到呢?,全是因為這個DefaultSerMux 他是http 模塊下的全局變數,定義的路由都會被 他保存。

    var DefaultServeMux = &defaultServeMux

    我們來看看build web applicatin with golang這本書提供的流程圖,在我眼裡,簡直哎…..,你不會懂的。

總的感覺Go對Server, conn, handler, mux 這些對象直接的關係沒有Python 那樣清晰,他們直接的關係總是感覺有點微妙,對Go的特性比較屬性了或許就會很好吧。

那麼問題來了,如果我要實現自己的路由機制,該如何做?也就是我要實現一個第三方路由該如何寫?又該如何實現自己的HTTP 框架?

參考:build-web-application-with-golang


推薦閱讀:

TAG:Go語言 | Python | HTTP |