亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

淺析Golang中的net/http路由注冊(cè)與請(qǐng)求處理

 更新時(shí)間:2023年12月17日 15:28:19   作者:小唐0101  
這篇文章主要為大家詳細(xì)介紹了Golang中的net/http路由注冊(cè)與請(qǐng)求處理的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

注冊(cè)路由

初學(xué)web時(shí),我們常用http.HandleFunc()來(lái)注冊(cè)路由,然后用http.ListenAndServe()來(lái)啟動(dòng)服務(wù),接下來(lái)讓我們分析一下http包內(nèi)部是如何注冊(cè)路由的。

除了常用的http.HandleFunc()可以注冊(cè)路由,還有http.Handle可以注冊(cè),先看一下源碼。

func Handle(pattern string, handler Handler) { 
    DefaultServeMux.Handle(pattern, handler) 
}

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

對(duì)比一下兩個(gè)函數(shù)的不同:

1、分別調(diào)用了DefaultServeMux的Handle和HandleFunc方法。

2、handler參數(shù)類型分別為http.Handler接口,和func(ResponseWriter, *Request)類型。說(shuō)明一下,DefaultServerMux是http包的全局變量,如果不使用默認(rèn)的復(fù)用器

接下來(lái)看一下Handle和HandleFunc的主要源碼,和Handler接口

// 注冊(cè)路由
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()
    ...
    
    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
	
    ...
}

// 調(diào)用Handle注冊(cè)路由
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

// 實(shí)現(xiàn)了Handler的函數(shù)類型
type HandlerFunc func(ResponseWriter, *Request)

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

可以看到ServeMux.Handle最終實(shí)現(xiàn)了路由的注冊(cè),mux.m記錄了路由與處理器的映射;而ServeMux.HandleFunc將handler參數(shù)轉(zhuǎn)換成了HandlerFunc類型,然后調(diào)用ServeMux.Handle。

那么問(wèn)題來(lái)了,ServeMux.Handle的handler參數(shù)是Handler接口類型,我們調(diào)用http.HandleFunc()傳入的處理器函數(shù)簽名是func(ResponseWriter, *Request),我們傳入的函數(shù)咋就實(shí)現(xiàn)了Handler接口?

答案就在于HandlerFunc類型,它實(shí)現(xiàn)了Handler接口。我們傳入的處理器函數(shù)與HandlerFunc類型函數(shù)簽名是一致的,如果沒(méi)有HandlerFunc,要注冊(cè)函數(shù)的話,我們就要自己定義結(jié)構(gòu)體,寫ServeHTTP方法,實(shí)現(xiàn)Handler接口,而有了HandlerFunc我們就可以把這一步省去了,在設(shè)計(jì)模式中,這叫裝飾器模式。

處理請(qǐng)求

ServerMux

使用http.HandleFunc和http.Handle注冊(cè)的路由都注冊(cè)到了DefaultServerMux,它也實(shí)現(xiàn)了handler接口,那讓我們來(lái)看一下ServerMux的ServeHTTP方法。

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

mux.Handler()中會(huì)調(diào)用mux.match(),從ServeMux.m(map[string]muxEntry類型),獲取路由和對(duì)應(yīng)處理器函數(shù)(我們傳入的),然后就可以調(diào)用h.ServeHTTP(w,r)來(lái)處理對(duì)應(yīng)的請(qǐng)求。

現(xiàn)在已得知我們傳入的處理函數(shù)是被ServeMux.ServeHTTP()調(diào)用,那ServerMus.ServeHTTP()又是怎么被調(diào)用的呢?接下來(lái)跟蹤一下http.ListenAndServe()的源碼。

Server

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

用http.ListenAndServe()啟動(dòng)服務(wù)的話,handler參數(shù)一般傳nil,使用DefaultServerMux做處理器,下面的分析都以此為前提。

在函數(shù)內(nèi)部幫我們創(chuàng)建了一個(gè)Server結(jié)構(gòu)體,如果想更靈活地使用,可以自己創(chuàng)建Server結(jié)構(gòu)體,調(diào)用server.ListenAndServe()。

func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)	// 監(jiān)聽地址
    if err != nil {
        return err
    }
    return srv.Serve(ln)
}

Server.ListenAndServe()中完成了監(jiān)聽地址的綁定,然后再調(diào)用Server.Serve()

func (srv *Server) Serve(l net.Listener) error {
    
	...
    
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        rw, err := l.Accept()

        ...
            
        connCtx := ctx
        if cc := srv.ConnContext; cc != nil {
            connCtx = cc(connCtx, rw)
            if connCtx == nil {
                panic("ConnContext returned nil")
            }
        }
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew, runHooks)
        go c.serve(connCtx)
    }
}

Server.Serve中開啟了一個(gè)for循環(huán)來(lái)接收連接,并為每一個(gè)連接創(chuàng)建contexxt,開一個(gè)協(xié)程繼續(xù)處理。

func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    var inFlightResponse *response
    defer func() {
        if err := recover(); err != nil && err != ErrAbortHandler {
            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 inFlightResponse != nil {
            inFlightResponse.cancelCtx()
        }
        if !c.hijacked() {
            if inFlightResponse != nil {
                inFlightResponse.conn.r.abortPendingRead()
                inFlightResponse.reqBody.Close()
            }
            c.close()
            c.setState(c.rwc, StateClosed, runHooks)
        }
    }()

	...
    
    for {
        w, err := c.readRequest(ctx)

        ...

        inFlightResponse = w
        serverHandler{c.server}.ServeHTTP(w, w.req)  // 這里并不是Handler接口的ServeHTTP方法
        inFlightResponse = nil
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        
      	...
    }

    ...
}

在http包server.go 1991行,嵌套了Server結(jié)構(gòu)體的serverHandler結(jié)構(gòu)體調(diào)用ServeHTTP方法,需要注意的是這并不是Handler接口的ServeHTTP方法,我們傳入的處理器函數(shù)的調(diào)用還在其內(nèi)部。

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux	// 如果在創(chuàng)建Server時(shí),Handler參數(shù)為nil,就使用DefaultServeMux
                                                                    //  通過(guò)http.ListenAndServe開啟服務(wù)一般都用DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }

    if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") {
        var allowQuerySemicolonsInUse int32
        req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() {
            atomic.StoreInt32(&allowQuerySemicolonsInUse, 1)
        }))
        defer func() {
            if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 {
                sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192")
            }
        }()
    }

    handler.ServeHTTP(rw, req)	// 調(diào)用DefaultServeMux的ServeHTTP
}

最后一行調(diào)用DefaultServeMux的ServeHTTP,上文已介紹過(guò),它的作用就是獲取到請(qǐng)求對(duì)應(yīng)的處理器函數(shù)并執(zhí)行。

延伸

gin框架是基于http包封裝的,gin匹配路由的方式不同于原生包,使用前綴路由樹來(lái)匹配路由,通過(guò)engin.Run啟動(dòng)服務(wù),其內(nèi)部也是調(diào)用的http.ListenAndServe(),那gin是如何應(yīng)用的自定義匹配方式呢?其實(shí)很簡(jiǎn)單,上文提到,調(diào)用http.ListenAndServe()時(shí)第二個(gè)參數(shù)handler是nil的話,會(huì)使用DefaultServeMux當(dāng)做復(fù)用器,那engin.Run()中調(diào)用時(shí)傳入gin定義的復(fù)用器就好了。

func (engine *Engine) Run(addr ...string) (err error) {
    defer func() { debugPrintError(err) }()

    if engine.isUnsafeTrustedProxies() {
        debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
            "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
    }

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine.Handler())	// engin.Handler()返回gin的自定義處理器
    return
}

以上就是淺析Golang中的net/http路由注冊(cè)與請(qǐng)求處理的詳細(xì)內(nèi)容,更多關(guān)于go net/http的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 解析GOROOT、GOPATH、Go-Modules-三者的關(guān)系

    解析GOROOT、GOPATH、Go-Modules-三者的關(guān)系

    這篇文章主要介紹了解析GOROOT、GOPATH、Go-Modules-三者的關(guān)系,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Go經(jīng)典面試題匯總(填空+判斷)

    Go經(jīng)典面試題匯總(填空+判斷)

    這篇文章主要介紹了Go經(jīng)典面試題匯總(填空+判斷),本文章內(nèi)容詳細(xì),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,需要的朋友可以參考下
    2023-01-01
  • 淺談Golang的方法傳遞值應(yīng)該注意的地方

    淺談Golang的方法傳遞值應(yīng)該注意的地方

    這篇文章主要介紹了淺談Golang的方法傳遞值應(yīng)該注意的地方,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • Go語(yǔ)言實(shí)現(xiàn)讀取文件的方式總結(jié)

    Go語(yǔ)言實(shí)現(xiàn)讀取文件的方式總結(jié)

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言實(shí)現(xiàn)讀取文件的幾個(gè)方式,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,感興趣的小伙伴可以收藏一下
    2023-04-04
  • Go實(shí)現(xiàn)分布式唯一ID的生成之雪花算法

    Go實(shí)現(xiàn)分布式唯一ID的生成之雪花算法

    本文主要介紹了Go實(shí)現(xiàn)分布式唯一ID的生成之雪花算法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • Go語(yǔ)言中for循環(huán)的經(jīng)典案例分析

    Go語(yǔ)言中for循環(huán)的經(jīng)典案例分析

    for循環(huán)問(wèn)題,在面試中經(jīng)常都會(huì)被問(wèn)到,并且在實(shí)際業(yè)務(wù)項(xiàng)目中也經(jīng)常用到for循環(huán),要是沒(méi)用好,一不下心就掉坑。本文為大家挑選了幾個(gè)經(jīng)典的案例,一塊來(lái)探討下,看看如何避免掉坑,多積累積累采坑經(jīng)驗(yàn)
    2023-02-02
  • Go中sync.Once源碼的深度講解

    Go中sync.Once源碼的深度講解

    sync.Once是Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的一個(gè)同步原語(yǔ),用于確保某個(gè)操作只執(zhí)行一次,本文將從源碼出發(fā)為大家詳細(xì)介紹一下sync.Once的具體使用,x希望對(duì)大家有所幫助
    2025-01-01
  • 一文帶你了解Go語(yǔ)言中鎖特性和實(shí)現(xiàn)

    一文帶你了解Go語(yǔ)言中鎖特性和實(shí)現(xiàn)

    Go語(yǔ)言中的sync包主要提供的對(duì)并發(fā)操作的支持,標(biāo)志性的工具有cond(條件變量)?once?(原子性)?還有?鎖,本文會(huì)主要向大家介紹Go語(yǔ)言中鎖的特性和實(shí)現(xiàn),感興趣的可以了解下
    2024-03-03
  • 詳解Go?flag實(shí)現(xiàn)二級(jí)子命令的方法

    詳解Go?flag實(shí)現(xiàn)二級(jí)子命令的方法

    這篇文章主要介紹了Go?flag?詳解,實(shí)現(xiàn)二級(jí)子命令,本文就探討一下?Go?語(yǔ)言中如何寫一個(gè)擁有類似特性的命令行程序,需要的朋友可以參考下
    2022-07-07
  • Go html/template 模板的使用實(shí)例詳解

    Go html/template 模板的使用實(shí)例詳解

    這篇文章主要介紹了Go html/template 模板的使用實(shí)例詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-05-05

最新評(píng)論