最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文帶你分析Go語(yǔ)言中的http請(qǐng)求過(guò)程

 更新時(shí)間:2026年02月26日 08:42:46   作者:Nyarlathotep0113  
在go中開(kāi)發(fā)后端,最基礎(chǔ)的就是使用net/http包,本文將使用一個(gè)hello,world程序來(lái)進(jìn)行debug,來(lái)探究在Go語(yǔ)言中http請(qǐng)求的過(guò)程,希望對(duì)大家有所幫助

net/http包

在go中開(kāi)發(fā)后端,最基礎(chǔ)的就是使用net/http包,本文我將使用一個(gè)hello,world程序來(lái)進(jìn)行debug,來(lái)探究在代碼內(nèi)部究竟發(fā)生了什么。

package main

import (
    "fmt"
    "log"
    "net/http"
)

func HelloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World")
}
func main() {
    http.HandleFunc("/hello", HelloHandler)
    log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

Debug

http.ListenAndServe()

整個(gè)程序的啟動(dòng)在于http.ListenAndServe("localhost:8080", nil)這行代碼: 傳入地址localhost:8080以及nil

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

可以看出這個(gè)函數(shù)的作用就是封裝Server對(duì)象的創(chuàng)建并調(diào)用server.ListenAndServe(), Server對(duì)象代表著一個(gè)http服務(wù)器,內(nèi)部封裝了很多http相關(guān)的參數(shù)。

從這里可以看出,如果是需要一個(gè)快速簡(jiǎn)單的的http服務(wù)器,直接使用http.ListenAndServe("localhost:8080", nil),如果需要更加精細(xì)化參數(shù)的http服務(wù)器,可以這樣寫(xiě):

    // 創(chuàng)建并配置Server
    server := &http.Server{
        Addr:    ":8080",  // 監(jiān)聽(tīng)所有接口的8080端口
        Handler: mux,      // 使用自定義多路復(fù)用器
        
        // 重要的超時(shí)設(shè)置
        ReadHeaderTimeout: 5 * time.Second,  // 5秒內(nèi)必須讀完請(qǐng)求頭
        ReadTimeout:       10 * time.Second, // 10秒內(nèi)必須讀完整個(gè)請(qǐng)求
        WriteTimeout:      10 * time.Second, // 10秒內(nèi)必須寫(xiě)完響應(yīng)
        IdleTimeout:       120 * time.Second, // 空閑連接2分鐘后關(guān)閉
        
        MaxHeaderBytes: 1 << 20, // 請(qǐng)求頭最大1MB
    }

    fmt.Println("服務(wù)器正在 8080 端口監(jiān)聽(tīng)...")
    if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        fmt.Printf("服務(wù)器錯(cuò)誤: %v\n", err)
    }

server.ListenAndServe()

func (s *Server) ListenAndServe() error {
    if s.shuttingDown() {
       return ErrServerClosed
    }
    addr := s.Addr
    if addr == "" {
       addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
       return err
    }
    return s.Serve(ln)
}

既然方法叫ListenAndServe,自然便有ListenServe的部分,從源碼可以看出ln, err := net.Listen("tcp", addr)在對(duì)應(yīng)的主機(jī)端口上啟動(dòng)一個(gè)TCP監(jiān)聽(tīng)器,最后調(diào)用s.Serve(ln)

TCP端口監(jiān)聽(tīng):Serve(ln)

func (s *Server) Serve(l net.Listener) error {
    if fn := testHookServerServe; fn != nil {
       fn(s, l) // call hook with unwrapped listener
    }

    origListener := l
    l = &onceCloseListener{Listener: l}
    defer l.Close()

    if err := s.setupHTTP2_Serve(); err != nil {
       return err
    }

    if !s.trackListener(&l, true) {
       return ErrServerClosed
    }
    defer s.trackListener(&l, false)

    baseCtx := context.Background()
    if s.BaseContext != nil {
       baseCtx = s.BaseContext(origListener)
       if baseCtx == nil {
          panic("BaseContext returned a nil context")
       }
    }

    var tempDelay time.Duration // how long to sleep on accept failure

    ctx := context.WithValue(baseCtx, ServerContextKey, s)
    for {
       rw, err := l.Accept()
       if err != nil {
          if s.shuttingDown() {
             return ErrServerClosed
          }
          if ne, ok := err.(net.Error); ok && ne.Temporary() {
             if tempDelay == 0 {
                tempDelay = 5 * time.Millisecond
             } else {
                tempDelay *= 2
             }
             if max := 1 * time.Second; tempDelay > max {
                tempDelay = max
             }
             s.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
             time.Sleep(tempDelay)
             continue
          }
          return err
       }
       connCtx := ctx
       if cc := s.ConnContext; cc != nil {
          connCtx = cc(connCtx, rw)
          if connCtx == nil {
             panic("ConnContext returned nil")
          }
       }
       tempDelay = 0
       c := s.newConn(rw)
       c.setState(c.rwc, StateNew, runHooks) // before Serve can return
       go c.serve(connCtx)
    }
}

源碼過(guò)長(zhǎng),因此下面的分析中省略不重要的代碼

origListener := l
l = &onceCloseListener{Listener: l}
defer l.Close()
...
if !s.trackListener(&l, true) {
    return ErrServerClosed
}
defer s.trackListener(&l, false)

這部分代碼先是備份了先前創(chuàng)建的TCP監(jiān)聽(tīng)器,然后將其封裝為oneCloseListener,隨后使用s.trackListener(&l, true)將監(jiān)聽(tīng)器注冊(cè)到http服務(wù)器中,退出方法時(shí)調(diào)用s.trackListener(&l, false)將監(jiān)聽(tīng)器注銷(xiāo)。

baseCtx := context.Background()
if s.BaseContext != nil {
    baseCtx = s.BaseContext(origListener)
    if baseCtx == nil {
       panic("BaseContext returned a nil context")
    }
}

http服務(wù)器中的BaseContext是一個(gè)回調(diào)函數(shù),用于為整個(gè)http服務(wù)器設(shè)置服務(wù)器級(jí)別的基礎(chǔ)上下文。 用法示例如下:

ctx, cancel := context.WithCancel(context.Background())
server := &http.Server{
    BaseContext: func(l net.Listener) context.Context {
        // 可以在這里為每個(gè)連接添加上下文值
        return context.WithValue(ctx, "listener_addr", l.Addr().String())
    },
}
// 將cancel函數(shù)注冊(cè)到實(shí)際server的關(guān)閉鉤子
server.RegisterOnShutdown(cancel)

最后是持續(xù)的監(jiān)聽(tīng)環(huán)節(jié)

for {
    rw, err := l.Accept()
    if err != nil {
       if s.shuttingDown() {
          return ErrServerClosed
       }
       if ne, ok := err.(net.Error); ok && ne.Temporary() {
          if tempDelay == 0 {
             tempDelay = 5 * time.Millisecond
          } else {
             tempDelay *= 2
          }
          if max := 1 * time.Second; tempDelay > max {
             tempDelay = max
          }
          s.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
          time.Sleep(tempDelay)
          continue
       }
       return err
    }
    connCtx := ctx
    if cc := s.ConnContext; cc != nil {
       connCtx = cc(connCtx, rw)
       if connCtx == nil {
          panic("ConnContext returned nil")
       }
    }
    tempDelay = 0
    c := s.newConn(rw)
    c.setState(c.rwc, StateNew, runHooks) // before Serve can return
    go c.serve(connCtx)
}

當(dāng)l.Accept()接收到請(qǐng)求后,通過(guò)http服務(wù)器的ConnContext在原有的上下文的基礎(chǔ)上再次設(shè)置上下文,之后封裝TCPConnhttp連接,并設(shè)置狀態(tài)為StateNew,并允許http服務(wù)器中的ConnState鉤子觸發(fā)。最后啟動(dòng)一個(gè)協(xié)程開(kāi)始該連接的服務(wù)。

trackListener()

以下是trackListener()的源碼

func (s *Server) trackListener(ln *net.Listener, add bool) bool {
    s.mu.Lock()
    defer s.mu.Unlock()
    if s.listeners == nil {
       s.listeners = make(map[*net.Listener]struct{})
    }
    if add {
       if s.shuttingDown() {
          return false
       }
       s.listeners[ln] = struct{}{}
       s.listenerGroup.Add(1)
    } else {
       delete(s.listeners, ln)
       s.listenerGroup.Done()
    }
    return true
}

先判斷http服務(wù)器中的監(jiān)聽(tīng)器容器是否已經(jīng)初始化了,如果沒(méi)有則分配一個(gè)map對(duì)象,利用add來(lái)判斷是增加監(jiān)聽(tīng)器還是移除監(jiān)聽(tīng)器。從這份源碼可以看出,一個(gè)http服務(wù)器是支持監(jiān)聽(tīng)多個(gè)端口的。只需要同時(shí)運(yùn)行多個(gè)s.Serve(ln)

HTTP請(qǐng)求處理serve()

因?yàn)檫@部分源碼實(shí)在過(guò)多,因此只展示部分代碼

for{
    w, err := c.readRequest(ctx)
    ...
    serverHandler{c.server}.ServeHTTP(w, w.req)
}

方法中有一段循環(huán),實(shí)現(xiàn)在同一個(gè)TCP連接上用于處理多個(gè)HTTP請(qǐng)求,實(shí)現(xiàn)HTTP/1.1Keep-Alive特性。w, err := c.readRequest(ctx)返回一個(gè)response,request被包含在這個(gè)response對(duì)象中,后續(xù)通過(guò)serverHandler{c.server}.ServeHTTP(w, w.req)來(lái)處理http請(qǐng)求

serverHandler的ServeHTTP()

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
       handler = DefaultServeMux
    }
    if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {
       handler = globalOptionsHandler{}
    }

    handler.ServeHTTP(rw, req)
}

此處會(huì)取出最早的時(shí)候創(chuàng)建http服務(wù)器時(shí)傳入的Handler,如果這個(gè)接口對(duì)象是nil,將使用默認(rèn)的DefaultServeMux,最終使用這個(gè)來(lái)處理http請(qǐng)求

handler的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
    }
    var h Handler
    if use121 {
       h, _ = mux.mux121.findHandler(r)
    } else {
       h, r.Pattern, r.pat, r.matches = mux.findHandler(r)
    }
    h.ServeHTTP(w, r)
}

核心邏輯是根據(jù)use121的值來(lái)使用新版的findHandler還是舊版的mux121.findHandler,最終會(huì)根據(jù)請(qǐng)求的URI來(lái)尋找到對(duì)應(yīng)的Handler接口對(duì)象,本例中是HelloHandler,它是一個(gè)HandlerFunc對(duì)象 因此調(diào)用

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

看起來(lái)這段代碼似乎很多余,為什么不直接在原本的代碼轉(zhuǎn)成HnadlerFunc直接調(diào)用?

因?yàn)檫@樣我們可以不使用HandlerFunc來(lái)處理邏輯,比如以下的代碼

type LogMiddler struct{
    next Handler
}
func (logMiddler *LogMiddler) ServeHTTP(w ResponseWriter, r *Request){
    fmt.Println("處理的日期是....")
    logMiddler.next(w,r)
}

LogMiddler實(shí)現(xiàn)了Handler接口,因此上述的h可以是這個(gè)LogMiddler對(duì)象,將原本的HelloHandler封裝在LogMiddler中,就實(shí)現(xiàn)了一種鏈?zhǔn)秸{(diào)用,增強(qiáng)原本的HelloHandler。 到目前為止,自定義的路徑處理邏輯已經(jīng)執(zhí)行,后續(xù)都是一些善后處理。

總結(jié)

1.http.HandleFunc("/hello", HelloHandler)將處理邏輯注冊(cè)到DefaultServeMux

2.創(chuàng)建一個(gè)http服務(wù)器

3.創(chuàng)建一個(gè)TCP監(jiān)聽(tīng)器,并使用http服務(wù)器的BaseContext創(chuàng)建初始的context并設(shè)置一些其他值

4.當(dāng)TCP請(qǐng)求到達(dá)時(shí),使用http服務(wù)器的ConnContextcontext基礎(chǔ)上創(chuàng)建當(dāng)此次TCP連接的context,并設(shè)置當(dāng)前的連接狀態(tài)為StateNew,如果http服務(wù)器設(shè)置了ConnState鉤子函數(shù),在連接狀態(tài)變動(dòng)時(shí)會(huì)觸發(fā)。最后開(kāi)啟一個(gè)新的協(xié)程來(lái)處理這個(gè)TCP連接

5.使用一個(gè)循環(huán)不斷讀取和處理http請(qǐng)求

6.處理http請(qǐng)求階段,如果創(chuàng)建http服務(wù)器時(shí)傳入nil,則使用DefaultServeMux,然后根據(jù)RequestURI來(lái)尋找對(duì)應(yīng)的Handler接口對(duì)象,最終調(diào)用它的ServeHTTP進(jìn)行處理

以上就是一文帶你分析Go語(yǔ)言中的http請(qǐng)求過(guò)程的詳細(xì)內(nèi)容,更多關(guān)于Go語(yǔ)言http請(qǐng)求的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 從前端轉(zhuǎn)go的一些感悟?qū)Ρ?附詳細(xì)代碼)

    從前端轉(zhuǎn)go的一些感悟?qū)Ρ?附詳細(xì)代碼)

    從前端開(kāi)發(fā)轉(zhuǎn)向 Go(Golang)后端開(kāi)發(fā),是一個(gè)非常可行也很實(shí)用的方向,特別是在做高性能微服務(wù)、分布式系統(tǒng)、云原生(如Kubernetes)等方面,這篇文章主要介紹了從前端轉(zhuǎn)go的一些感悟?qū)Ρ?需要的朋友可以參考下
    2026-04-04
  • 在Mac中搭建go語(yǔ)言開(kāi)發(fā)環(huán)境的操作步驟

    在Mac中搭建go語(yǔ)言開(kāi)發(fā)環(huán)境的操作步驟

    go語(yǔ)言在開(kāi)發(fā)效率和運(yùn)行效率中的優(yōu)勢(shì)讓很多人青睞,所以有傾向打算轉(zhuǎn)向go語(yǔ)言的開(kāi)發(fā)。下面介紹在Mac中g(shù)olang的開(kāi)發(fā)環(huán)境配置。有需要的可以參考借鑒。
    2016-08-08
  • Golang單元測(cè)試、go協(xié)程和管道示例詳解

    Golang單元測(cè)試、go協(xié)程和管道示例詳解

    在Go語(yǔ)言中并發(fā)編程是一種核心特性,這篇文章主要介紹了Golang單元測(cè)試、go協(xié)程和管道的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-11-11
  • Go語(yǔ)言開(kāi)發(fā)k8s之Deployment操作解析

    Go語(yǔ)言開(kāi)發(fā)k8s之Deployment操作解析

    這篇文章主要為大家介紹了Go語(yǔ)言開(kāi)發(fā)k8s之Deployment操作解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • Golang時(shí)間處理庫(kù)go-carbon?v2.2.13發(fā)布細(xì)則

    Golang時(shí)間處理庫(kù)go-carbon?v2.2.13發(fā)布細(xì)則

    這篇文章主要為大家介紹了Golang?時(shí)間處理庫(kù)go-carbon?v2.2.13發(fā)布細(xì)則,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • golang利用不到20行代碼實(shí)現(xiàn)路由調(diào)度詳解

    golang利用不到20行代碼實(shí)現(xiàn)路由調(diào)度詳解

    這篇文章主要給大家介紹了關(guān)于golang利用不到20行代碼實(shí)現(xiàn)路由調(diào)度的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08
  • golang實(shí)現(xiàn)命令行程序的使用幫助功能

    golang實(shí)現(xiàn)命令行程序的使用幫助功能

    這篇文章介紹了golang實(shí)現(xiàn)命令行程序使用幫助的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • 一文帶你學(xué)會(huì)Go?select語(yǔ)句輕松實(shí)現(xiàn)高效并發(fā)

    一文帶你學(xué)會(huì)Go?select語(yǔ)句輕松實(shí)現(xiàn)高效并發(fā)

    這篇文章主要為大家詳細(xì)介紹了Golang中select語(yǔ)句的用法,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Golang有一定的幫助,需要的可以參考一下
    2023-03-03
  • GoLang BoltDB數(shù)據(jù)庫(kù)詳解

    GoLang BoltDB數(shù)據(jù)庫(kù)詳解

    這篇文章主要介紹了GoLang BoltDB數(shù)據(jù)庫(kù),boltdb是使用Go語(yǔ)言編寫(xiě)的開(kāi)源的鍵值對(duì)數(shù)據(jù)庫(kù),boltdb存儲(chǔ)數(shù)據(jù)時(shí) key和value都要求是字節(jié)數(shù)據(jù),此處需要使用到 序列化和反序列化
    2023-02-02
  • go:gin輸出日志文件方式

    go:gin輸出日志文件方式

    這篇文章主要介紹了go:gin輸出日志文件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01

最新評(píng)論

仪征市| 舟山市| 仁怀市| 石屏县| 阿克陶县| 辽源市| 哈尔滨市| 北票市| 陵水| 泗洪县| 大新县| 芮城县| 晴隆县| 密山市| 六盘水市| 武宁县| 保康县| 盐池县| 广平县| 沭阳县| 牙克石市| 胶南市| 弥勒县| 湖北省| 佛山市| 桂林市| 南宁市| 揭东县| 辰溪县| 南乐县| 图片| 潼关县| 普安县| 肇州县| 玉林市| 沛县| 威宁| 墨玉县| 菏泽市| 蒙阴县| 高阳县|