Gin源码阅读中Gin与Net/Http的关系转载本文请联系HHFCodeRv公众号。Gin是目前Go中使用最广泛的框架之一。了解gin框架的原理,有助于我们更好的使用gin。本系列的gin源码阅读会逐步讲解gin的原理。欢迎关注后续文章。gin概述想要了解gin,需要了解以下几个问题:请求数据是如何流动的?gin框架有什么作用?请求从gin流向net/http,最后在gin中如何返回到gin。为什么gin的上下文可以负责?gin的路由算法中间件是什么?什么是杜松子酒的引擎?net/httprequestet和response提供了哪些有用的东西?从gin官方的第一个demo开始.packagemainimport"github.com/gin-gonic/gin"funcmain(){r:=gin.Default()r.GET("/ping",func(c*gin.Context){c.JSON(200,gin.H{"message":"pong",})})r.Run()//listenandserveon0.0.0.0:8080}r.Run()源码:func(engine*Engine)Run(addr...string)(errerror){deferfunc(){debugPrintError(err)}()address:=resolveAddress(addr)debugPrint("ListeningandservingHTTPon%s\n",address)err=http.ListenAndServe(address,engine)return}看到那个http.ListenAndServe(address,engine),这个函数是net/http的一个函数,然后请求数据开始在net/http中流动。请求数据如何流动?先不要用gin,直接用net/http处理http请求"))})iferr:=http.ListenAndServe(":8000",nil);err!=nil{fmt.Println("starthttpserverfail:",err)}}在浏览器中输入localhost:8000,你会看到HelloWorld。让我们来看看这个简单的demo请求的流程。HTTP是如何建立的简单说一下http请求是如何建立的(需要有基本的网络基础,可以找相关书籍查,推荐阅读UNIX网络编程第一卷:SocketNetworkingAPI)TCP/IP五层modelsocket建立过程在TCP/IP五层模型下,HTTP位于应用层,需要一个传输层来承载HTTP协议。传输层比较常见的协议有TCP、UDP、SCTP等,由于UDP不可靠,SCTP有其特殊的应用场景,所以一般情况下,HTTP由TCP协议承载(可以使用wireshark抓包然后查看每一层的协议)。如果使用TCP协议,就会涉及到TCP是如何建立的。面试中三向握手、四向挥手经常能遇到的名词都在这里生成。具体的建立过程这里就不描述了,流程大概就是图中的左半部分。所以,如果想要能够响应客户端的http请求,首先需要建立TCP连接,也就是socket。接下来我们看看net/http是如何建立socket的?net/http是如何建立套接字的?从图中可以看出,服务器代码无论怎么封装,都离不开bind、listen、accept这些功能。从上面的简单demo开始查看源码funcmain(){http.HandleFunc("/",func(whttp.ResponseWriter,r*http.Request){w.Write([]byte("HelloWorld"))})iferr:=http.ListenAndServe(":8000",nil);err!=nil{fmt.Println("starthttpserverfail:",err)}}注册路由http.HandleFunc("/",func(whttp.ResponseWriter,r*http.Request){w.Write([]byte("HelloWorld"))})这段代码是向DefaultServeMux注册一个路由和这个路由的handler//server.go:L2366-2388func(mux*ServeMux)句柄(patternstring,handlerHandler){mux.mu.Lock()defermux.mu.Unlock()ifpattern==“”{panic(“http:invalidpattern”)}ifhandler==nil{panic(“http:nilhandler”)}if_,exist:=mux.m[pattern];exist{panic("http:multipleregistrationsfor"+pattern)}ifmux.m==nil{mux.m=make(map[string]muxEntry)}mux.m[模式]=muxEntry{h:handler,pattern:pattern}ifpattern[0]!='/'{mux.hosts=true}}可以看到这个路由注册太简单了,同样给了gin,iris,echo等框架留有扩展空间。这个东西的服务监听和响应后面会详细介绍。上面的路由已经注册到net/http.接下来,如何创建socket,最后如何获取注册的route,将对响应信息从handler中获取返回给client1.创建socketiferr:=http.ListenAndServe(":8000",nil);err!=nil{fmt.Println("starthttpserverfail:",err)}//net/http/server.go:L3002-3005funcListenAndServe(addrstring,handlerHandler)error{server:=&Server{Addr:addr,Handler:handler}returnserver.ListenAndServe()}//net/http/server.go:L2752-2765func(srv*Server)ListenAndServe()error{//...省略代码ln,err:=net.Listen("tcp",addr)//<-----看这里listeniferr!=nil{returnerr}returnsrv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})}2.Aaccept等待客户端连接//net/http/server.go:L2805-2853func(srv*Server)Serve(lnet.Listener)error{//...省略代码{rw,e:=l.Accept()//<-----看这里acceptife!=nil{select{case<-srv.getDoneChan():returnErrServerCloseddefault:}ifne,ok:=e.(net.Error);ok&&ne.Temporary(){iftempDelay==0{tempDelay=5*time.Millisecond}else{tempDelay*=2}ifmax:=1*time.Second;tempDelay>max{tempDelay=max}srv.logf("http:Accepterror:%v;retryingin%v",e,tempDelay)time.Sleep(tempDelay)continue}return}tempDelay=0c:=srv.newConn(rw)c.setState(c.rwc,StateNew)//beforeServecanreturngoc.serve(ctx)//<---看这里}}3.提供回调接口ServeHTTP//net/http/server.go:L1739-1878func(c*conn)serve(ctxcontext.Context){//...省略代码serverHandler{c.server}.ServeHTTP(w,w.req)w.c取消Ctx()ifc.hijacked(){return}w.finishRequest()//...省略代码}//net/http/server.go:L2733-2742func(shserverHandler)ServeHTTP(rwResponseWriter,req*Request){handler:=sh.srv.Handlerifhandler==nil{handler=DefaultServeMux}ifreq.RequestURI=="*"&&req.Method="OPTIONS"{handler=globalOptionsHandler{}}handler.ServeHTTP(rw,req)}//net/http/server.go:L2352-2362func(mux*ServeMux)ServeHTTP(wResponseWriter,r*Request){ifr.RequestURI="*"{ifr.ProtoAtLeast(1,1){w.Header().Set("连接","close")}w.WriteHeader(StatusBadRequest)return}h,_:=mux.Handler(r)//<---看这里h.ServeHTTP(w,r)}4.回调到实际执行ServeHTTP//net/http/server.go:L1963-1965func(fHandlerFunc)ServeHTTP(wResponseWriter,r*Request){f(w,r)}这基本上就是整个流程的代码。ln,错误:=净。Listen("tcp",addr)做了socket的初始操作,bind,listen。rw,e:=l.Accept()接受,等待客户端连接goc.serve(ctx)启动一个新的goroutine来处理这个请求。同时,主goroutine继续等待客户端连接并执行高并发操作h,_:=mux.Handler(r)获取注册的路由,然后获取这条路由的handler,然后将处理结果返回给客户端。从这里也可以看出,net/http基本上提供了全套的服务。为什么go框架很多//net/http/server.go:L2218-2238func(mux*ServeMux)match(pathstring)(hHandler,patternstring){//Checkforexactmatchfirst.v,ok:=mux.m[path]ifok{returnv.h,v.pattern}//Checkforlongestvalidmatch.varn=0fork,v:=rangemux.m{if!pathMatch(k,path){continue}ifh==nil||len(k)>n{n=len(k)h=v.hpattern=v.pattern}}return}从这个函数可以看出匹配规则太简单了。当可以匹配到路由时,会返回对应的handler。当路由匹配不到时,会返回/.net/http路由不符合RESTful规则。当遇到稍微复杂一点的需求时,这种简单的路由匹配规则简直就是噩梦。因此,基本上所有go框架所做的最重要的事情就是重写net/http路由。说gin只是一个httprouter一点也不为过。当然gin还提供了其他比较重要的功能,后面会一一介绍。综上所述,net/http基本上已经提供了http服务70%的功能。那些号称速度极快的go框架,基本上都提供了一些功能,让我们更好的处理客户端的请求。如果有兴趣的话,也可以自己做一个基于net/http的Go框架。
