gin框架預覽

- router.Run()的源碼:
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)
return
}
然后看到開始調用的是http.ListenAndServe(address, engine), 這個函數是net/http的函數. 然后請求數據就在net/http開始流轉.
所以, gin源碼閱讀系列就是要弄明白以下幾個問題:
- request數據是如何流轉的
- gin框架到底扮演了什么角色
- 請求從gin流入net/http, 最后又是如何回到gin中
- gin的context為何能承擔起來復雜的需求
- gin的路由算法
- gin的中間件是什么
- gin的Engine具體是個什么東西
- net/http的requeset, response都提供了哪些有用的東西
request數據是如何流轉的
先不使用gin, 直接使用net/http來處理http請求
func main() {
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Println(request.URL.Path)
_, _ = writer.Write([]byte(request.URL.Path + "/哈哈哈"))
})
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalln("start server failed", err)
}
}
HTTP是如何建立起來的
簡單的說一下http請求是如何建立起來的(需要有基本的網絡基礎,socket編程)

在TCP/IP五層模型下, HTTP位於應用層, 需要有傳輸層來承載HTTP協議. 傳輸層比較常見的協議是TCP,UDP, SCTP等. 由於UDP不可靠, SCTP有自己特殊的運用場景, 所以一般情況下HTTP是由TCP協議承載的(可以使用wireshark抓包然后查看各層協議)
使用TCP協議的話, 就會涉及到TCP是如何建立起來的. 面試中能夠常遇到的名詞三次握手, 四次揮手就是在這里產生的. 具體的建立流程就不在陳述了, 大概流程就是圖中左半邊
所以說, 要想能夠對客戶端http請求進行回應的話, 就首先需要建立起來TCP連接, 也就是socket. 下面要看下net/http是如何建立起來socket?
net/http是如何建立socket的
從圖上可以看出, 不管server代碼如何封裝, 都離不開bind,listen,accept這些函數. 就從上面這個簡單的demo入手查看源碼.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return 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)
if err != nil {
return err
}
return srv.Serve(ln)
for {
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
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
}
srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
注冊路由
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Println(request.URL.Path)
_, _ = writer.Write([]byte(request.URL.Path + "/哈哈哈"))
})
這段代碼是在注冊一個路由及這個路由的handler到DefaultServeMux中
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
- 定義一個函數類型,對一個函數進行轉型成一個類型,然后該類型實現一些方法,從而實現某些接口
func main() {
_ = http.ListenAndServe(":8080", IndexHandler(Index))
}
type IndexHandler func(w http.ResponseWriter, r *http.Request)
func (i IndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
i(w, r)
}
func Index(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL.Path)
_, _ = w.Write([]byte("哈哈哈123456"))
}
可以看到這個路由注冊太過簡單了, 也就給gin, iris, echo等框架留下了擴展的空間, 后面詳細說這個東西
服務監聽及響應
上面路由已經注冊到net/http了, 下面就該如何建立socket了, 以及最后又如何取到已經注冊到的路由, 將正確的響應信息從handler中取出來返回給客戶端
1.
_ = http.ListenAndServe(":8080", nil)
2.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
// net/http/server.go:L2752-2765
func (srv *Server) ListenAndServe() error {
// ... 省略代碼
ln, err := net.Listen("tcp", addr) // <-----看這里listen
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
// net/http/server.go:L2805-2853
func (srv *Server) Serve(l net.Listener) error {
// ... 省略代碼
for {
rw, e := l.Accept() // <----- 看這里accept
if e != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx) // <--- 看這里
}
}
// net/http/server.go:L1739-1878
func (c *conn) serve(ctx context.Context) {
// ... 省略代碼
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
// ... 省略代碼
}
// net/http/server.go:L2733-2742
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)
}
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)
}
// net/http/server.go:L2352-2362
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)
}
// net/http/server.go:L1963-1965
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
這基本是整個過程的代碼了. 基本上是:
- ln, err := net.Listen("tcp", addr)做了初試化了socket, bind, listen的操作.
- rw, e := l.Accept()進行accept, 等待客戶端進行連接
- go c.serve(ctx) 啟動新的goroutine來處理本次請求. 同時主goroutine繼續等待客戶端連接, 進行高並發操作
- h, _ := mux.Handler(r) 獲取注冊的路由, 然后拿到這個路由的handler, 然后將處理結果返回給客戶端
從這里也能夠看出來, net/http基本上提供了全套的服務.
為什么會出現很多go框架
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// Check for longest valid match. mux.es contains all patterns
// that end in / sorted from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
從這段函數可以看出來, 匹配規則過於簡單, 當能匹配到路由的時候就返回其對應的handler, 當不能匹配到時就返回/. 所以net/http的路由匹配無法滿足復雜的需求開發. 所以基本所有的go框架干的最主要的一件事情就是重寫net/http的route
所以我們直接說gin就是一個httprouter也不過分, 當然gin也提供了其他比較主要的功能, 后面會一一介紹
還有一個go框架要實現的東西是http.ResponseWriter
綜述, net/http基本已經提供http服務的70%的功能, 那些號稱賊快的go框架, 基本上都是提供一些功能, 讓我們能夠更好的處理客戶端發來的請求.
