golang http server分析(一)

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

對於一個http服務來說,須要兼容新舊版本的http協議,http1.0/2.0,以及https的支持,http的通訊是創建在tcp鏈接基礎上的通訊。緩存

如今協議有了,鏈接通訊也有了,還剩一個問題就是如何處理client request請求,這個問題能夠分爲路由和具體邏輯實現,下面看看在golang中是如何解決這些問題的。socket

 

路由部分 tcp

在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,post

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

Handler定義以下:ui

// 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()的實現,url

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服務須要支持的一些特性也都沒有說起到,後面陸續來吧。

相關文章
相關標籤/搜索