gin 概览
想弄清楚 gin, 需要弄明白以下几个问题:
- request数据是如何流转的
- gin框架到底扮演了什么角色
- 请求从gin流入net/http, 最后又是如何回到gin中
- gin的context为何能承担起来复杂的需求
- gin的路由算法
- gin的中间件是什么
- gin的Engine具体是个什么东西
- net/http的requeset, response都提供了哪些有用的东西
从gin的官方第一个demo入手.
package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})r.Run() // listen and serve on 0.0.0.0:8080}
r.Run() 的源码:
func (engine *Engine) Run(addr ...string) (err error) {defer func() { debugPrintError(err) }()trustedCIDRs, err := engine.prepareTrustedCIDRs()if err != nil {return err}engine.trustedCIDRs = trustedCIDRsaddress := 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开始流转.
net/http 是如何建立 socket 的
先不使用gin, 直接使用net/http来处理http请求
func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World"))})if err := http.ListenAndServe(":8000", nil); err != nil {fmt.Println("start http server fail:", err)}}
从图上可以看出, 不管server代码如何封装, 都离不开bind,listen,accept这些函数. 就从上面这个简单的demo入手查看源码.
注册路由
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World"))})
这段代码是在注册一个路由及这个路由的handler到DefaultServeMux中
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {DefaultServeMux.HandleFunc(pattern, handler)}
/usr/local/go/src/net/http/server.go:2453
// Handle registers the handler for the given pattern.// If a handler already exists for pattern, Handle panics.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] = eif pattern[len(pattern)-1] == '/' {mux.es = appendSorted(mux.es, e)}if pattern[0] != '/' {mux.hosts = true}}
可以看到这个路由注册太过简单了, 也就给gin, iris, echo等框架留下了扩展的空间, 后面详细说这个东西
服务监听及响应
上面路由已经注册到net/http了, 下面就该如何建立socket了, 以及最后又如何取到已经注册到的路由, 将正确的响应信息从handler中取出来返回给客户端
1.创建 socket
if err := http.ListenAndServe(":8000", nil); err != nil {fmt.Println("start http server fail:", err)}
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.Addrif addr == "" {addr = ":http"}// 这里创建了socket,处理连接ln, err := net.Listen("tcp", addr)if err != nil {return err}return srv.Serve(ln)}
2.Accept 等待客户端链接
func (srv *Server) Serve(l net.Listener) error {if fn := testHookServerServe; fn != nil {fn(srv, l) // call hook with unwrapped listener}origListener := ll = &onceCloseListener{Listener: l}defer l.Close()if err := srv.setupHTTP2_Serve(); err != nil {return err}if !srv.trackListener(&l, true) {return ErrServerClosed}defer srv.trackListener(&l, false)baseCtx := context.Background()if srv.BaseContext != nil {baseCtx = srv.BaseContext(origListener)if baseCtx == nil {panic("BaseContext returned a nil context")}}var tempDelay time.Duration // how long to sleep on accept failure// 这里的ctx比较关键,它把srv装进了上下文,供后面使用ctx := context.WithValue(baseCtx, ServerContextKey, srv)for {rw, err := l.Accept() // Accept接收连接if err != nil { // Accept异常处理select {case <-srv.getDoneChan():return ErrServerCloseddefault:}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 := ctxif cc := srv.ConnContext; cc != nil {connCtx = cc(connCtx, rw)if connCtx == nil {panic("ConnContext returned nil")}}tempDelay = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew, runHooks) // before Serve can returngo c.serve(connCtx) // 每个连接都会起一个go程去处理}}
可以看看srv.newConn干了啥
// Create new connection from rwc.func (srv *Server) newConn(rwc net.Conn) *conn {c := &conn{server: srv,rwc: rwc,}if debugServerConnections {c.rwc = newLoggingConn("server", c.rwc)}return c}
它比较简单,这里返回的conn 对net.Conn进行了一次包装, conn表示HTTP连接的服务器端
3. 提供回调接口 ServeHTTP
// Serve a new connection.func (c *conn) serve(ctx context.Context) {c.remoteAddr = c.rwc.RemoteAddr().String()ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())defer func() {if err := recover(); err != nil && err != ErrAbortHandler {const size = 64 << 10buf := make([]byte, size)buf = buf[:runtime.Stack(buf, false)]c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)}if !c.hijacked() {c.close()c.setState(c.rwc, StateClosed, runHooks)}}()// 如果是tls连接,要进行Handshakeif tlsConn, ok := c.rwc.(*tls.Conn); ok {....}// HTTP/1.x from here on.ctx, cancelCtx := context.WithCancel(ctx)c.cancelCtx = cancelCtxdefer cancelCtx()c.r = &connReader{conn: c}c.bufr = newBufioReader(c.r)c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)for {w, err := c.readRequest(ctx)if c.r.remain != c.server.initialReadLimitSize() {// If we read any bytes off the wire, we're active.c.setState(c.rwc, StateActive, runHooks)}// 读取数据失败的处理if err != nil {const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"switch {case err == errTooLarge:// Their HTTP client may or may not be// able to read this if we're// responding to them and hanging up// while they're still writing their// request. Undefined behavior.const publicErr = "431 Request Header Fields Too Large"fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)c.closeWriteAndWait()returncase isUnsupportedTEError(err):// Respond as per RFC 7230 Section 3.3.1 which says,// A server that receives a request message with a// transfer coding it does not understand SHOULD// respond with 501 (Unimplemented).code := StatusNotImplemented// We purposefully aren't echoing back the transfer-encoding's value,// so as to mitigate the risk of cross side scripting by an attacker.fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)returncase isCommonNetReadError(err):return // don't replydefault:if v, ok := err.(statusError); ok {fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s: %s%s%d %s: %s", v.code, StatusText(v.code), v.text, errorHeaders, v.code, StatusText(v.code), v.text)return}publicErr := "400 Bad Request"fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)return}}// Expect 100 Continue supportreq := w.reqif req.expectsContinue() {if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {// Wrap the Body reader with one that replies on the connectionreq.Body = &expectContinueReader{readCloser: req.Body, resp: w}w.canWriteContinue.setTrue()}} else if req.Header.get("Expect") != "" {w.sendExpectationFailed()return}c.curReq.Store(w)if requestBodyRemains(req.Body) {registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)} else {w.conn.r.startBackgroundRead()}serverHandler{c.server}.ServeHTTP(w, w.req) // 处理http请求w.cancelCtx()if c.hijacked() {return}w.finishRequest()if !w.shouldReuseConnection() {if w.requestBodyLimitHit || w.closedRequestBodyEarly() {c.closeWriteAndWait()}return}c.setState(c.rwc, StateIdle, runHooks)c.curReq.Store((*response)(nil))if !w.conn.server.doKeepAlives() {// We're in shutdown mode. We might've replied// to the user without "Connection: close" and// they might think they can send another// request, but such is life with HTTP/1.1.return}if d := c.server.idleTimeout(); d != 0 {c.rwc.SetReadDeadline(time.Now().Add(d))if _, err := c.bufr.Peek(4); err != nil {return}}c.rwc.SetReadDeadline(time.Time{})}}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {handler := sh.srv.Handlerif handler == nil {// http.ListenAndServe(":8000", nil),当handler为nil时,默认用的是DefaultServeMuxhandler = DefaultServeMux}if req.RequestURI == "*" && req.Method == "OPTIONS" {handler = globalOptionsHandler{}}handler.ServeHTTP(rw, req)}
4. 回调到实际要执行的 ServeHTTP
type Handler interface {ServeHTTP(ResponseWriter, *Request)}
我们已知handler其实就是DefaultServeMux,因此调用
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) // 这里在路由表中找到HandlerFunch.ServeHTTP(w, r)}
最后再调用ServeHTTP
type HandlerFunc func(ResponseWriter, *Request)// ServeHTTP calls f(w, r).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框架
// Find a handler on a handler map given a path string.// Most-specific (longest) pattern wins.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的路由匹配根本就不符合 RESTful 的规则,遇到稍微复杂一点的需求时,这个简单的路由匹配规则简直就是噩梦。
所以基本所有的go框架干的最主要的一件事情就是重写net/http的route。我们直接说 gin就是一个 httprouter 也不过分, 当然gin也提供了其他比较主要的功能, 后面会一一介绍。
综述, net/http基本已经提供http服务的70%的功能, 那些号称贼快的go框架, 基本上都是提供一些功能, 让我们能够更好的处理客户端发来的请求. 如果你有兴趣的话,也可以基于 net/http 做一个 Go 框架出来。
