乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Golang構(gòu)建HTTP服務(wù)(一)

       落塵伊人 2019-10-25

      實(shí)現(xiàn)一個(gè)最簡(jiǎn)單HTTP server需要多少代碼?只需要一行,Python2的python -m SimpleHTTPServer,ruby的ruby -run -e httpd . -p 8888。對(duì)于Golang,實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的http server也用不著幾行,卻能帶來更具殺傷力的性能。

      一個(gè)Go最簡(jiǎn)單的http服務(wù)器:

      package main
      
      import (
          "fmt"
          "net/http"
      )
      
      func IndexHandler(w http.ResponseWriter, r *http.Request) {
          fmt.Fprintln(w, "hello world")
      }
      
      func main() {
          http.HandleFunc("/", IndexHandler)
          http.ListenAndServe("127.0.0.0:8000", nil)
      }

      上述代碼究竟有什么魔法呢?下面將會(huì)解密go的net/http實(shí)現(xiàn)http服務(wù)的原理。學(xué)習(xí)net/http源碼不僅可以學(xué)習(xí)網(wǎng)絡(luò)編程中常見范式,也是學(xué)習(xí)Golang接口設(shè)計(jì)哲學(xué)。

      HTTP

      網(wǎng)絡(luò)發(fā)展,很多網(wǎng)絡(luò)應(yīng)用都是構(gòu)建再 HTTP 服務(wù)基礎(chǔ)之上。HTTP 協(xié)議從誕生到現(xiàn)在,發(fā)展從1.0,1.1到2.0也不斷再進(jìn)步。除去細(xì)節(jié),理解 HTTP 構(gòu)建的網(wǎng)絡(luò)應(yīng)用只要關(guān)注兩個(gè)端---客戶端(clinet)和服務(wù)端(server),兩個(gè)端的交互來自 clinet 的 request,以及server端的response。所謂的http服務(wù)器,主要在于如何接受 clinet 的 request,并向client返回response。

      接收request的過程中,最重要的莫過于路由(router),即實(shí)現(xiàn)一個(gè)Multiplexer器。Go中既可以使用內(nèi)置的mutilplexer --- DefautServeMux,也可以自定義。Multiplexer路由的目的就是為了找到處理器函數(shù)(handler),后者將對(duì)request進(jìn)行處理,同時(shí)構(gòu)建response。

      簡(jiǎn)單總結(jié)就是這個(gè)流程為:

      Clinet -> Requests ->  [Multiplexer(router) -> handler  -> Response -> Clinet

      因此,理解go中的http服務(wù),最重要就是要理解Multiplexer和handler,Golang中的Multiplexer基于ServeMux結(jié)構(gòu),同時(shí)也實(shí)現(xiàn)了Handler接口。

      對(duì)于handler的其實(shí)沒有合適的中文詞語,只可意會(huì),不可言傳的感覺。為了更好的說明問題,本文約定了如下規(guī)則:

      • hander函數(shù): 具有func(w http.ResponseWriter, r *http.Requests)簽名的函數(shù)

      • handler處理器(函數(shù)): 經(jīng)過HandlerFunc結(jié)構(gòu)包裝的handler函數(shù),它實(shí)現(xiàn)了ServeHTTP接口方法的函數(shù)。調(diào)用handler處理器的ServeHTTP方法時(shí),即調(diào)用handler函數(shù)本身。

      • handler對(duì)象:實(shí)現(xiàn)了Handler接口ServeHTTP方法的結(jié)構(gòu)。

      handler處理器和handler對(duì)象的差別在于,一個(gè)是函數(shù),另外一個(gè)是結(jié)構(gòu),它們都有實(shí)現(xiàn)了ServeHTTP方法。很多情況下它們的功能類似,下文就使用統(tǒng)稱為handler。這算是Golang通過接口實(shí)現(xiàn)的類動(dòng)態(tài)類型吧。

      hander-handler處理器.jpeg

      Golang的http處理流程可以用下面一張圖表示,后面內(nèi)容是針對(duì)圖進(jìn)行說明:

      go-http.png

      Handler

      Golang沒有繼承,類多態(tài)的方式可以通過接口實(shí)現(xiàn)。所謂接口則是定義聲明了函數(shù)簽名,任何結(jié)構(gòu)只要實(shí)現(xiàn)了與接口函數(shù)簽名相同的方法,就等同于實(shí)現(xiàn)了接口。go的http服務(wù)都是基于handler進(jìn)行處理。

      type Handler interface {
          ServeHTTP(ResponseWriter, *Request)
      }

      任何結(jié)構(gòu)體,只要實(shí)現(xiàn)了ServeHTTP方法,這個(gè)結(jié)構(gòu)就可以稱之為handler對(duì)象。ServeMux會(huì)使用handler并調(diào)用其ServeHTTP方法處理請(qǐng)求并返回響應(yīng)。

      ServeMux

      了解了Handler之后,再看ServeMux。ServeMux的源碼很簡(jiǎn)單:

      type ServeMux struct {
          mu    sync.RWMutex
          m     map[string]muxEntry
          hosts bool 
      }
      
      type muxEntry struct {
          explicit bool
          h        Handler
          pattern  string
      }

      ServeMux結(jié)構(gòu)中最重要的字段為m,這是一個(gè)map,key是一些url模式,value是一個(gè)muxEntry結(jié)構(gòu),后者里定義存儲(chǔ)了具體的url模式和handler。

      當(dāng)然,所謂的ServeMux也實(shí)現(xiàn)了ServeHTTP接口,也算是一個(gè)handler,不過ServeMux的ServeHTTP方法不是用來處理request和respone,而是用來找到路由注冊(cè)的handler,后面再做解釋。

      Server

      除了ServeMux和Handler,還有一個(gè)結(jié)構(gòu)Server需要了解。從http.ListenAndServe的源碼可以看出,它創(chuàng)建了一個(gè)server對(duì)象,并調(diào)用server對(duì)象的ListenAndServe方法:

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

      查看server的結(jié)構(gòu)如下:

      type Server struct {
          Addr         string        
          Handler      Handler       
          ReadTimeout  time.Duration 
          WriteTimeout time.Duration 
          TLSConfig    *tls.Config   
      
          MaxHeaderBytes int
      
          TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
      
          ConnState func(net.Conn, ConnState)
          ErrorLog *log.Logger
          disableKeepAlives int32     nextProtoOnce     sync.Once 
          nextProtoErr      error     
      }

      server結(jié)構(gòu)存儲(chǔ)了服務(wù)器處理請(qǐng)求常見的字段。其中Handler字段也保留Handler接口。如果Server接口沒有提供Handler結(jié)構(gòu)對(duì)象,那么會(huì)使用DefautServeMux做multiplexer,后面再做分析。

      創(chuàng)建HTTP服務(wù)

      創(chuàng)建一個(gè)http服務(wù),大致需要經(jīng)歷兩個(gè)過程,首先需要注冊(cè)路由,即提供url模式和handler函數(shù)的映射,其次就是實(shí)例化一個(gè)server對(duì)象,并開啟對(duì)客戶端的監(jiān)聽。

      再看gohttp服務(wù)的代碼

      http.HandleFunc("/", indexHandler)

      即是注冊(cè)路由。

      http.ListenAndServe("127.0.0.1:8000", nil)
      
      或者:
      
      server := &Server{Addr: addr, Handler: handler}
      
      server.ListenAndServe()

      注冊(cè)路由

      閱讀框架源碼是學(xué)習(xí)的好方式,通常閱讀也有兩個(gè)方法,一是不求甚解,框架的主要流程要清晰,別的細(xì)枝末節(jié),如果尚不能理解作者的用意,可以先忽略,不必馬上深究;其次,庖丁解牛,對(duì)于作者想要表達(dá)的主要流程,一定要明確,執(zhí)行的邏輯和結(jié)構(gòu)。兩者看起來略矛盾,其實(shí)不然。大體而言就是對(duì)主流程要清晰,主流程以外的細(xì)節(jié)需要先忽略。最簡(jiǎn)單實(shí)踐方式就是,看不懂的就先放一邊。直到所有的都看不懂,再回去看以前不懂的部分,搞懂為止。下面就查看http是如何注冊(cè)路由。

      net/http包暴露的注冊(cè)路由的api很簡(jiǎn)單,http.HandleFunc選取了DefaultServeMux作為multiplexer:

      func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
          DefaultServeMux.HandleFunc(pattern, handler)
      }

      那么什么是DefaultServeMux呢?實(shí)際上,DefaultServeMux是ServeMux的一個(gè)實(shí)例。當(dāng)然http包也提供了NewServeMux方法創(chuàng)建一個(gè)ServeMux實(shí)例,默認(rèn)則創(chuàng)建一個(gè)DefaultServeMux:

      // NewServeMux allocates and returns a new ServeMux.
      func NewServeMux() *ServeMux { return new(ServeMux) }
      
      // DefaultServeMux is the default ServeMux used by Serve.
      var DefaultServeMux = &defaultServeMux
      
      var defaultServeMux ServeMux

      注意,go創(chuàng)建實(shí)例的過程中,也可以使用指針方式,即
      type Server struct{}
      server := Server{}
      和下面的一樣都可以創(chuàng)建Server的實(shí)例
      var DefalutServer Server
      var server = &DefalutServer

      因此DefaultServeMux的HandleFunc(pattern, handler)方法實(shí)際是定義在ServeMux下的:

      func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
      mux.Handle(pattern, HandlerFunc(handler))
      }

      上述代碼中,HandlerFunc是一個(gè)函數(shù)類型。同時(shí)實(shí)現(xiàn)了Handler接口的ServeHTTP方法。使用HandlerFunc類型包裝一下路由定義的indexHandler函數(shù),其目的就是為了讓這個(gè)函數(shù)也實(shí)現(xiàn)ServeHTTP方法,即轉(zhuǎn)變成一個(gè)handler處理器(函數(shù))。

      type HandlerFunc func(ResponseWriter, *Request)

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

      一旦這樣做了,就意味著我們的 indexHandler 函數(shù)也有了ServeHTTP方法。
      
      此外,ServeMux的Handle方法,將會(huì)對(duì)pattern和handler函數(shù)做一個(gè)map映射:

      func (mux *ServeMux) Handle(pattern string, handler Handler) {
      mux.mu.Lock()
      defer mux.mu.Unlock()

      if pattern == "" {
          panic("http: invalid pattern " + pattern)
      }
      if handler == nil {
          panic("http: nil handler")
      }
      if mux.m[pattern].explicit {
          panic("http: multiple registrations for " + pattern)
      }
      
      if mux.m == nil {
          mux.m = make(map[string]muxEntry)
      }
      mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
      
      if pattern[0] != '/' {
          mux.hosts = true
      }
      
      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}
      }

      }

      由此可見,Handle函數(shù)的主要目的在于把handler和pattern模式綁定到map[string]muxEntry的map上,其中muxEntry保存了更多pattern和handler的信息,還記得前面討論的Server結(jié)構(gòu)嗎?Server的m字段就是map[string]muxEntry這樣一個(gè)map。
      
      此時(shí),pattern和handler的路由注冊(cè)完成。接下來就是如何開始server的監(jiān)聽,以接收客戶端的請(qǐng)求。
      
      #### 開啟監(jiān)聽
      
      注冊(cè)好路由之后,啟動(dòng)web服務(wù)還需要開啟服務(wù)器監(jiān)聽。http的ListenAndServer方法中可以看到創(chuàng)建了一個(gè)Server對(duì)象,并調(diào)用了Server對(duì)象的同名方法:

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

      func (srv Server) ListenAndServe() error {
      addr := srv.Addr
      if addr == "" {
      addr = ":http"
      }
      ln, err := net.Listen("tcp", addr)
      if err != nil {
      return err
      }
      return srv.Serve(tcpKeepAliveListener{ln.(
      net.TCPListener)})
      }

      Server的ListenAndServe方法中,會(huì)初始化監(jiān)聽地址Addr,同時(shí)調(diào)用Listen方法設(shè)置監(jiān)聽。最后將監(jiān)聽的TCP對(duì)象傳入Serve方法:

      func (srv *Server) Serve(l net.Listener) error {
      defer l.Close()
      ...

      baseCtx := context.Background()
      ctx := context.WithValue(baseCtx, ServerContextKey, srv)
      ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
      for {
          rw, e := l.Accept()
          ...
          c := srv.newConn(rw)
          c.setState(c.rwc, StateNew) // before Serve can return
          go c.serve(ctx)
      }

      }

      #### 處理請(qǐng)求
      
      監(jiān)聽開啟之后,一旦客戶端請(qǐng)求到底,go就開啟一個(gè)協(xié)程處理請(qǐng)求,主要邏輯都在serve方法之中。
      
      serve方法比較長(zhǎng),其主要職能就是,創(chuàng)建一個(gè)上下文對(duì)象,然后調(diào)用Listener的Accept方法用來 獲取連接數(shù)據(jù)并使用newConn方法創(chuàng)建連接對(duì)象。最后使用goroutein協(xié)程的方式處理連接請(qǐng)求。因?yàn)槊恳粋€(gè)連接都開起了一個(gè)協(xié)程,請(qǐng)求的上下文都不同,同時(shí)又保證了go的高并發(fā)。serve也是一個(gè)長(zhǎng)長(zhǎng)的方法:

      func (c *conn) serve(ctx context.Context) {
      c.remoteAddr = c.rwc.RemoteAddr().String()
      defer func() {
      if err := recover(); err != nil {
      const size = 64 << 10
      buf := make([]byte, size)
      buf = buf[:runtime.Stack(buf, false)]
      c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
      }
      if !c.hijacked() {
      c.close()
      c.setState(c.rwc, StateClosed)
      }
      }()

      ...
      
      for {
          w, err := c.readRequest(ctx)
          if c.r.remain != c.server.initialReadLimitSize() {
              // If we read any bytes off the wire, we're active.
              c.setState(c.rwc, StateActive)
          }
          ...
          
          }
          
          ...
        
          serverHandler{c.server}.ServeHTTP(w, w.req)
          w.cancelCtx()
          if c.hijacked() {
              return
          }
          w.finishRequest()
          if !w.shouldReuseConnection() {
              if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                  c.closeWriteAndWait()
              }
              return
          }
          c.setState(c.rwc, StateIdle)
      }

      }

      盡管serve很長(zhǎng),里面的結(jié)構(gòu)和邏輯還是很清晰的,使用defer定義了函數(shù)退出時(shí),連接關(guān)閉相關(guān)的處理。然后就是讀取連接的網(wǎng)絡(luò)數(shù)據(jù),并處理讀取完畢時(shí)候的狀態(tài)。接下來就是調(diào)用`serverHandler{c.server}.ServeHTTP(w, w.req)`方法處理請(qǐng)求了。最后就是請(qǐng)求處理完畢的邏輯。serverHandler是一個(gè)重要的結(jié)構(gòu),它近有一個(gè)字段,即Server結(jié)構(gòu),同時(shí)它也實(shí)現(xiàn)了Handler接口方法ServeHTTP,并在該接口方法中做了一個(gè)重要的事情,初始化multiplexer路由多路復(fù)用器。如果server對(duì)象沒有指定Handler,則使用默認(rèn)的DefaultServeMux作為路由Multiplexer。并調(diào)用初始化Handler的ServeHTTP方法。

      type serverHandler struct {
      srv *Server
      }

      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)
      }

      這里DefaultServeMux的ServeHTTP方法其實(shí)也是定義在ServeMux結(jié)構(gòu)中的,相關(guān)代碼如下:

      func (mux *ServeMux) (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)
      }

      func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
      if r.Method != "CONNECT" {
      if p := cleanPath(r.URL.Path); p != r.URL.Path {
      _, pattern = mux.handler(r.Host, p)
      url := *r.URL
      url.Path = p
      return RedirectHandler(url.String(), StatusMovedPermanently), pattern
      }
      }
      return mux.handler(r.Host, r.URL.Path)
      }

      func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
      mux.mu.RLock()
      defer mux.mu.RUnlock()

      // Host-specific pattern takes precedence over generic ones
      if mux.hosts {
          h, pattern = mux.match(host + path)
      }
      if h == nil {
          h, pattern = mux.match(path)
      }
      if h == nil {
          h, pattern = NotFoundHandler(), ""
      }
      return

      }

      func (mux *ServeMux) match(path string) (h Handler, pattern string) {
      var n = 0
      for k, v := range mux.m {
      if !pathMatch(k, path) {
      continue
      }
      if h == nil || len(k) > n {
      n = len(k)
      h = v.h
      pattern = v.pattern
      }
      }
      return
      }

      mux的ServeHTTP方法通過調(diào)用其Handler方法尋找注冊(cè)到路由上的handler函數(shù),并調(diào)用該函數(shù)的ServeHTTP方法,本例則是IndexHandler函數(shù)。
      
      mux的Handler方法對(duì)URL簡(jiǎn)單的處理,然后調(diào)用handler方法,后者會(huì)創(chuàng)建一個(gè)鎖,同時(shí)調(diào)用match方法返回一個(gè)handler和pattern。
      
      在match方法中,mux的m字段是map[string]muxEntry圖,后者存儲(chǔ)了pattern和handler處理器函數(shù),因此通過迭代m尋找出注冊(cè)路由的patten模式與實(shí)際url匹配的handler函數(shù)并返回。
      
      返回的結(jié)構(gòu)一直傳遞到mux的ServeHTTP方法,接下來調(diào)用handler函數(shù)的ServeHTTP方法,即IndexHandler函數(shù),然后把response寫到http.RequestWirter對(duì)象返回給客戶端。
      
      上述函數(shù)運(yùn)行結(jié)束即`serverHandler{c.server}.ServeHTTP(w, w.req)`運(yùn)行結(jié)束。接下來就是對(duì)請(qǐng)求處理完畢之后上希望和連接斷開的相關(guān)邏輯。
      
      至此,Golang中一個(gè)完整的http服務(wù)介紹完畢,包括注冊(cè)路由,開啟監(jiān)聽,處理連接,路由處理函數(shù)。
      
      ### 總結(jié)
      
      多數(shù)的web應(yīng)用基于HTTP協(xié)議,客戶端和服務(wù)器通過request-response的方式交互。一個(gè)server并不可少的兩部分莫過于路由注冊(cè)和連接處理。Golang通過一個(gè)ServeMux實(shí)現(xiàn)了的multiplexer路由多路復(fù)用器來管理路由。同時(shí)提供一個(gè)Handler接口提供ServeHTTP用來實(shí)現(xiàn)handler處理其函數(shù),后者可以處理實(shí)際request并構(gòu)造response。
      
      ServeMux和handler處理器函數(shù)的連接橋梁就是Handler接口。ServeMux的ServeHTTP方法實(shí)現(xiàn)了尋找注冊(cè)路由的handler的函數(shù),并調(diào)用該handler的ServeHTTP方法。ServeHTTP方法就是真正處理請(qǐng)求和構(gòu)造響應(yīng)的地方。
      
      回顧go的http包實(shí)現(xiàn)http服務(wù)的流程,可見大師們的編碼設(shè)計(jì)之功力。學(xué)習(xí)有利提高自身的代碼邏輯組織能力。更好的學(xué)習(xí)方式除了閱讀,就是實(shí)踐,接下來,我們將著重討論來構(gòu)建http服務(wù)。尤其是構(gòu)建http中間件函數(shù)。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多