Go 語言 HTTP Server 源碼分析
Go語言中HTTP Server:
HTTP server,顧名思義,支持http協議的伺服器,HTTP是一個簡單的請求-響應協議,通常運行在TCP之上。通過客戶端發送請求給伺服器得到對應的響應。
HTTP服務簡單實現
package mainimport("fmt""net/http")//處理請求,返回結果funcHello(w http.ResponseWriter, r *http.Request){ fmt.Fprintln(w,"hello world")}funcmain(){//路由註冊http.HandleFunc("/",Hello)//服務監聽http.ListenAndServe(":8080",nil)}
你以為這樣就結束了嗎,不才剛剛開始。
源碼分析
路由註冊
http中的HandleFunc方法,主要用來註冊路由
funcHandleFunc(pattern string, handlerfunc(ResponseWriter, *Request)) {DefaultServeMux.HandleFunc(pattern, handler)}
DefaultServeMux是什麼?
DefaultServeMux是ServeMux的一個實例。
ServeMux又是什麼?
// DefaultServeMux is the default ServeMux used by Serve.varDefaultServeMux = &defaultServeMuxvardefaultServeMux ServeMuxtype ServeMuxstruct{ mu sync.RWMutex m map[string]muxEntry hostsbool}type muxEntrystruct{explicitboolh Handler patternstring}
很多地方都涉及到了Handler,那麼Handler是什麼?
type Handlerinterface{ ServeHTTP(ResponseWriter, *Request)}
此介面可以算是HTTP Server一個樞紐
func(mux *ServeMux)HandleFunc(pattern string, handlerfunc(ResponseWriter, *Request)) { mux.Handle(pattern,HandlerFunc(handler))}typeHandlerFuncfunc(ResponseWriter, *Request)func(f HandlerFunc)ServeHTTP(wResponseWriter, r *Request) { f(w, r)}
從代碼中可以看出HandlerFunc是一個函數類型,並實現了Handler介面。當通過調用HandleFunc(),把Hello強轉為HandlerFunc類型時,就意味著 Hello函數也實現ServeHTTP方法。
ServeMux的Handle方法:
func (mux *ServeMux) Handle(patternstring, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock()ifpattern ==""{ panic("http: invalid pattern "+ pattern) }ifhandler == nil { panic("http: nil handler") }ifmux.m[pattern].explicit{ panic("http: multiple registrations for "+ pattern) }ifmux.m == nil { mux.m = make(map[string]muxEntry) }//把handler和pattern模式綁定到//map[string]muxEntry的map上mux.m[pattern] = muxEntry{explicit:true, h: handler, pattern: pattern}ifpattern[] != / { mux.hosts =true}//這裡是綁定靜態目錄,不作為本片重點。n := len(pattern)ifn >&& pattern[n-1] == / && !mux.m[pattern[:n-1]].explicit{ path := patternifpattern[] != / { path = pattern[strings.Index(pattern,"/"):] } url := &url.URL mux.m[pattern[:n-1]] = muxEntry }}
上面的流程就完成了路由註冊。
服務監聽
typeServerstruct{AddrstringHandlerHandlerReadTimeouttime.DurationWriteTimeouttime.DurationTLSConfig*tls.ConfigMaxHeaderBytesintTLSNextProtomap[string]func(*Server, *tls.Conn, Handler)ConnStatefunc(net.Conn, ConnState)ErrorLog*log.LoggerdisableKeepAlives int32 nextProtoOnce sync.OncenextProtoErr error }funcListenAndServe(addr string, handler Handler)error { server := &Server{Addr: addr,Handler: handler}returnserver.ListenAndServe()}//初始化監聽地址Addr,同時調用Listen方法設置監聽。//最後將監聽的TCP對象傳入Serve方法:func(srv *Server)ListenAndServe() error { addr := srv.Addrifaddr ==""{ addr =":http"} ln, err := net.Listen("tcp", addr)iferr !=nil{returnerr }returnsrv.Serve(tcpKeepAliveListener) }
Serve(l net.Listener)為每個請求開啟goroutine的設計,保證了go的高並發。
func(srv *Server)Serve(l net.Listener) error {deferl.Close()iffn := testHookServerServe; fn !=nil{ fn(srv, l) }vartempDelay time.Duration// how long to sleep on accept failureiferr := srv.setupHTTP2_Serve(); err !=nil{returnerr } srv.trackListener(l,true)defersrv.trackListener(l,false) baseCtx := context.Background()// base is always background, per Issue 16220ctx := context.WithValue(baseCtx,ServerContextKey, srv) ctx = context.WithValue(ctx,LocalAddrContextKey, l.Addr())//開啟循環進行監聽for{//通過Listener的Accept方法用來獲取連接數據rw, e := l.Accept()ife !=nil{ select {casemax{ tempDelay =max} srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay) time.Sleep(tempDelay)continue}returne } tempDelay =//通過獲得的連接數據,創建newConn連接對象c:= srv.newConn(rw)c.setState(c.rwc,StateNew)// before Serve can return//開啟goroutine發送連接請求goc.serve(ctx) }}
serve()為核心,讀取對應的連接數據進行分配
func(c*conn)serve(ctx context.Context) {c.remoteAddr =c.rwc.RemoteAddr().String()//連接關閉相關的處理deferfunc(){iferr := recover(); err !=nil&& err !=ErrAbortHandler{ const size =64
處理請求,返回結果
serverHandler 主要初始化路由多路復用器。如果server對象沒有指定Handler,則使用默認的DefaultServeMux作為路由多路復用器。並調用初始化Handler的ServeHTTP方法。
type serverHandlerstruct{ srv *Server}func(sh serverHandler)ServeHTTP(rwResponseWriter, req *Request) { handler := sh.srv.Handlerifhandler ==nil{ handler =DefaultServeMux}ifreq.RequestURI=="*"&& req.Method=="OPTIONS"{ handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req)}
這裡就是之前提到的匹配路由的具體代碼
func(mux *ServeMux)ServeHTTP(wResponseWriter, r *Request) {ifr.RequestURI=="*"{ifr.ProtoAtLeast(1,1) { w.Header().Set("Connection","close") } w.WriteHeader(StatusBadRequest)return}//匹配註冊到路由上的handler函數h,_:= mux.Handler(r)//調用handler函數的ServeHTTP方法//即Hello函數,然後把數據寫到http.ResponseWriter//對象中返回給客戶端。h.ServeHTTP(w, r)}func(mux *ServeMux)Handler(r *Request) (hHandler, pattern string) {ifr.Method!="CONNECT"{ifp := cleanPath(r.URL.Path); p != r.URL.Path{_, pattern = mux.handler(r.Host, p) url := *r.URLurl.Path= preturnRedirectHandler(url.String(),StatusMovedPermanently), pattern } }returnmux.handler(r.Host, r.URL.Path)}func(mux *ServeMux)handler(host, path string) (hHandler, pattern string) { mux.mu.RLock()defermux.mu.RUnlock()// Host-specific pattern takes precedence over generic onesifmux.hosts {//如 127.0.0.1/helloh, pattern = mux.match(host + path) }ifh ==nil{// 如 /helloh, pattern = mux.match(path) }ifh ==nil{ h, pattern =NotFoundHandler(),""}return}func(mux *ServeMux)match(path string) (hHandler, pattern string) {varn =fork, v := range mux.m {if!pathMatch(k, path) {continue}//通過迭代m尋找出註冊路由的patten模式//與實際url匹配的handler函數並返回。ifh ==nil|| len(k) > n { n = len(k) h = v.h pattern = v.pattern } }return}funcpathMatch(pattern, path string)bool {iflen(pattern) =={// should not happenreturnfalse} n := len(pattern)//如果註冊模式與請求uri一樣返回true,否則falseifpattern[n-1] != / {returnpattern == path }//靜態文件匹配returnlen(path) >= n && path[:n] == pattern}
將數據寫給客戶端
//主要代碼,通過層層封裝才走到這一步func(w checkConnErrorWriter)Write(p []byte) (n int, err error) { n, err = w.c.rwc.Write(p)iferr !=nil&& w.c.werr ==nil{ w.c.werr = err w.c.cancelCtx() }return}
serverHandler.ServeHTTP(w, w.req)當請求結束後,就開始執行連接斷開的相關邏輯。
總結
Go語言通過一個ServeMux實現了的路由多路復用器來管理路由。同時提供一個Handler介面提供ServeHTTP方法,實現handler介面的函數,可以處理實際request並返回response。
ServeMux和handler函數的連接橋樑就是Handler介面。ServeMux的ServeHTTP方法實現了尋找註冊路由的handler的函數,並調用該handler的ServeHTTP方法。
所以說Handler介面是一個重要樞紐。
簡單梳理下整個請求響應過程,如下圖
最近熱文:
1、邀你參加11期 程序員專場相親活動
2、PHP高工/架構師,18-50K,深圳南山
3、JAVA/Web前端,深圳前海,15-30K
4、如何判斷是否到了該辭職的時候?
※當寫爛代碼的人離職之後
※推薦大家使用的CSS書寫規範、順序
※堆排序以及最大優先隊列
※Hibernate使用原生SQL適應複雜數據查詢
※產品經理如何不被程序員嫌棄?
TAG:程序源 |
※MFC TabSheet 源碼
※PHP源碼分析之parse
※Storm源碼分析之Trident源碼分析
※JsBridge 源碼分析
※APT組織Carbanak源碼泄露
※JDK 源碼閱讀 Reference
※LinkedList源碼分析
※從JDK源碼看StringBuffer
※Drill-on-YARN之源碼解析
※PopupWindow源碼分析
※Apache Storm流計算模型 及WordCount源碼實踐
※JDK源碼閱讀:InterruptibleChannel 與可中斷 IO
※Spark 源碼分析之ShuffleMapTask內存數據Spill和合併
※React Native BackHandler exitApp 源碼分析
※Thread源碼剖析
※Android-IMX6Q源碼編譯
※JDK 源碼閱讀 : DirectByteBuffer
※HashMap源碼分析
※《跟隨霄,LAMMPS源碼學習06》Atom:grow
※小米MIX 3 Android P內核源碼公布