golang http server分析(一)


golang中使用的http協議版本是RFC2616

對於一個http服務來講,需要兼容新舊版本的http協議,http1.0/2.0,以及https的支持,http的通信是建立在tcp連接基礎上的通信。

現在協議有了,連接通信也有了,還剩一個問題就是如何處理client request請求,這個問題可以分為路由和具體邏輯實現,下面看看在golang中是如何解決這些問題的。

 

路由部分 

在golang中有個Handler的概念,一個URL對應一個Handler,在Handler中處理request的具體邏輯,對應關系保存在一個map結構中

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry //key是URL匹配字符串,muxEntry是對應的處理handler hosts bool // 路由匹配時,是否包含host }

Handler分為一般類型Handler和特殊類型Handler,特殊類型Handler是指包含特定功能處理的Handler,

比如redirectHandler用來處理302跳轉、NotFoundHandler用來處理404請求等。

Handler定義如下:

// Handler類型定義
type Handler interface {
    ServeHTTP(ResponseWriter, *Request) } //一般Handler是一函數體,實現了Handler接口,通過該函數可以將一個自定義函數轉換為Handler type HandlerFunc func(ResponseWriter, *Request) // 綁定對象就是自定義函數本身,通過在ServerHTTP中調用函數本身,實現了鈎子功能。 // 也就是說,當程序調用Handler.ServerHTTP()方法的時候,實際上是調用的跟Handler綁定的自定義函數 func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { // 調用綁定對象函數  f(w, r) }

 

明確了Handler的定義,接下來就要看看如何注冊Handler了,Handler的注冊是通過HandleFunc()函數實現的,在HandleFunc中調用ServerMux的HandleFunc()

方法將一個自定義的方法轉換為一個一般Handler,最后再調用Server.Mux的handle()方法,完成URL與Handler的綁定,下面詳細看看handle()的實現,

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    // 加一個寫鎖
 mux.mu.Lock() defer mux.mu.Unlock() // url匹配字符串不能為空 if pattern == "" { panic("http: invalid pattern " + pattern) } // handler不能為空 if handler == nil { panic("http: nil handler") } // 對應關系沒有被注冊過 if mux.m[pattern].explicit { panic("http: multiple registrations for " + pattern) } // 添加到map mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} // 判斷是否以hosts開頭的url if pattern[0] != '/' { mux.hosts = true } // 如果URL以字符/結尾,則多注冊注冊一個redirectHandler,訪問/tree時重定向到/tree/ n := len(pattern) if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit { path := pattern if pattern[0] != '/' { path = pattern[strings.Index(pattern, "/"):] } url := &url.URL{Path: path} mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern} } }

 

redirectHandle定義:

// redirectHandler是一個結構體,實現了Handler接口
type redirectHandler struct {
    url  string code int } func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) { // 重定向,設置location,status  Redirect(w, r, rh.url, rh.code) }

 

通信部分

http請求過程實質上是一個tcp連接通信,具體通過socket接口編碼實現,socket部分另起文章詳細說明,這里只做簡單介紹,socket操作流程如下:

在golang中的使用,通過listenAndServer()函數一步完成

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr if addr == "" { addr = ":http" } // 創建socket文件描述符,綁定ip:port,改變socket狀態為監聽狀態 ln, err := net.Listen("tcp", addr) if err != nil { return err } // 啟動服務,處理連接請求,tcpKeepAliveListener是一個長連接 return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) }

 在svr.Server()函數中會循環Accept() tcp 連接請求,每當讀取到一個tcp conn就啟動一個goroutine去處理連接信息,

 對於http服務來講,在goroutine中完成了http request的處理流程如下

 

readRequest():從tcp conn中讀取一個http request,完成了http請求的heand以及body的解析依據http協議對請求信息的校驗,詳細過程如下

1. 設置tcp conn讀取數據超時時間,設置請求頭數據大小限制,過濾http1.0 post請求后多添加的空格

2. 讀取請求信息並格式化

3. 校驗請求頭信息是否合法

4. 封裝成一個response返回,下面詳細介紹

 

獲取到請求信息,接下來就該調用URL對應的具體邏輯了,通過Handler.ServerHTTP()完成了對Handler的調用,主要操作如下:

1. 根據請求信息host,path在serverMux中查找對應的Handler

2. 如果找不到對應Handler,會返回一個NotFoundHandler

3. 調用handler.ServerHttp()

 

finishRequest():關閉http request請求處理,主要操作如下

1. 刷掉bufio.writer里的數據

2. 關閉chunkWriter()寫入流

3. 刷掉conn緩沖流里的數據

4. 關閉tcp連接

 

接下來介紹下http是如何完成數據的讀寫的,對數據的讀寫操作本質上都是在對socket fd進行操作,通過connReader結構體包裝好了對net.Con的操作,

這樣就可以通過對connReader的操作最終作用到net.Con,而在net.Con中完成了fd的讀寫操作,golang中io部分很多地方都用來這種思想,通過包裝器包裝好需要操作的對象,在調用過程中只需要操作包裝器就可以了。

下面看看golang中是如何實現的,拿解析請求函數舉例來看看讀取數據的過程,先看看函數定義:

func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) {
    tp := newTextprotoReader(b) req = new(Request) var s string if s, err = tp.ReadLine(); err != nil { return nil, err } ... }

沒錯請求的處理是通過bufio.Reader.ReadLine()一行一行的從tcp conn中讀取出來的,在看看這個函數是怎么被調用的

c.r = &connReader{r: c.rwc} // connReader就是上面提到的tcp連接包裝器
c.bufr = newBufioReader(c.r) // 定義一個讀緩存io流
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10) //定義一個寫緩存io流
// 調用解析請求函數
req, err := readRequest(c.bufr, keepHostHeader)

接下來看看寫操作過程,http中通過response對象將數據寫入tcp連接的,並對寫入流做了優化操作,對response的使用如下

w = &response{
    conn:          c, //當前tcp conn
    req:           req, // 當前請求對應的request對象
 reqBody: req.Body, handlerHeader: make(Header), //初始化header頭存儲塊 contentLength: -1, // 內容長度 } w.cw.res = w // 第二層緩沖流,通過chunkWriter將內容寫入conn緩沖區 w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize) // 第一層緩沖流,通過buio writer將數據寫入chunkWriter

封裝好response對象后,通過serverHandler{c.server}.ServeHTTP(w, w.req)方法將對象傳入Handler中,最終裝換為一個http.ResponseWriter對象執行,本質上

都是io.Writer對象

總結:

這篇文字主要介紹了http包中的路由和通信部分,由於通信部分涉及到一些socket通信的問題,只是簡單的提了一下,后面會專門針對socket總結一篇文章,http包中

Request和Response沒有詳細展開介紹,基本上對http協議規范的實現都在這兩個里面體現,還有前文提到的http服務需要支持的一些特性也都沒有提及到,后面陸續來吧。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM