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服務需要支持的一些特性也都沒有提及到,后面陸續來吧。