一文帶你分析Go語(yǔ)言中的http請(qǐng)求過(guò)程
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,自然便有Listen和Serve的部分,從源碼可以看出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è)置上下文,之后封裝TCPConn為http連接,并設(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.1的Keep-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ù)器的ConnContext在context基礎(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ì)代碼)
從前端開(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)境的操作步驟
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é)程和管道示例詳解
在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操作解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(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ì)則,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
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)命令行程序使用幫助的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
一文帶你學(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ù),boltdb是使用Go語(yǔ)言編寫(xiě)的開(kāi)源的鍵值對(duì)數(shù)據(jù)庫(kù),boltdb存儲(chǔ)數(shù)據(jù)時(shí) key和value都要求是字節(jié)數(shù)據(jù),此處需要使用到 序列化和反序列化2023-02-02

