Gin is one of the most widely used frameworks in Go at present. To understand the principle of GIN framework will help us better use GIN. This series of gin source readings will walk you through the principles of GIN.

An overview of the gin

To understand GIN, you need to understand the following questions:

  • How does request data flow
  • What role does the GIN framework play
  • How do requests flow from GIN to NET/HTTP and back to GIN
  • How can GIN’s context handle complex requirements
  • Gin routing algorithm
  • What is gin’s middleware
  • What exactly is gin’s Engine
  • Net/HTTP requeset, Response provides useful things

Start with gin’s first official demo.

package main

import "github.com/gin-gonic/gin"

func main(a) {
    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
}
Copy the code

R.run () ¶

func (engine *Engine) Run(addr ...string) (err error) {
    defer func(a) { debugPrintError(err) }()

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine)
    return
}
Copy the code

ListenAndServe(Address, engine), this function is the NET/HTTP function, and then the request data starts flowing at NET/HTTP.

How does Request data flow

Instead of using GIN, use NET/HTTP directly to handle HTTP requests

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) } }Copy the code

Type localhost:8000 in your browser and you’ll see Hello World. Let’s take a look at the request flow using this simple demo.

How was HTTP set up

A brief description of how HTTP requests are set up (basic networking basics are required, and books on this subject are recommended. UNIX Network Programming Volume 1: Socket Networking apis are recommended)

In the TCP/IP five-tier model, HTTP is located at the application layer, and a transport layer is required to carry HTTP. Common protocols at the transport layer are TCP,UDP, and SCTP. Because UDP is unreliable and SCTP has its own special application scenarios, HTTP is generally carried by TCP (you can use Wireshark to capture packets and view each layer protocol).

When you use TCP, you’re talking about how TCP is set up. Three handshakes and four waves of the hand. The specific establishment process is not stated, the process is probably the left half of the figure

Therefore, in order to be able to respond to the client HTTP request, the first need to establish a TCP connection, also known as socket. How does net/ HTTP set up sockets?

How does NET/HTTP set up sockets

As you can see from the diagram, no matter how server code is packaged, bind,listen, and accept functions are required. From the above simple demo to view the source code.

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) } }Copy the code

Registered routing

http.HandleFunc("/".func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))})Copy the code

This code registers a route and its handler into DefaultServeMux

// server.go:L2366-2388 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) } mux.m[pattern] = muxEntry{h: handler, pattern: pattern} if pattern[0] ! = '/' { mux.hosts = true } }Copy the code

As you can see, this route registration is too simple, leaving room for extensions to gin, Iris, Echo, etc., more on this later

The service listens and responds

Net/http: / / net/http: / / net/http: / / net/http: / / net/http: / / net/http: / / net/http: / / net/http: / / net/http: / / net/http: / / net/http: / / net/http: / / net/http: / / net/http: / / net/http: / / net/http

1. Create a socket

if err := http.ListenAndServe(":8000", nil); err ! = nil { fmt.Println("start http server fail:", err) }Copy the code
// net/http/server.go:L3002-3005
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}
Copy the code
// net/http/server.go:L2752-2765 func (srv *Server) ListenAndServe() error { // ... Log, err := net.Listen(" TCP ", addr) // <----- = nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) }Copy the code

2.Accept Waits for the client link

// net/http/server.go:L2805-2853 func (srv *Server) Serve(l net.Listener) error { // ... For {rw, e := l.acept () // <----- = 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) // <-- look here}}Copy the code

3. Provide a callback interface ServeHTTP

// net/http/server.go:L1739-1878 func (c *conn) serve(ctx context.Context) { // ... ServerHandler {c.server}.servehttp (w, w.eq) w.ancelctx () if c.jacked () {return} w.finishRequest() //... Omit code}Copy the code
// 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)
}
Copy the code
// 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.riteHeader (StatusBadRequest) return} h, _ := mux.handler (r)}Copy the code

4. Callback to the actual ServeHTTP to execute

// net/http/server.go:L1963-1965
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	  f(w, r)
}
Copy the code

This is basically the code for the whole process.

  1. ln, err := net.Listen("tcp", addr)Do theThe socket was first tested.bind.listenIn the operation.
  2. rw, e := l.Accept()Accept and wait for the client to connect
  3. go c.serve(ctx)Start a new Goroutine to handle the request. Meanwhile, the main Goroutine continues to wait for the client to connect, performing high-concurrency operations
  4. h, _ := mux.Handler(r)Get the registered route, get the handler for that route, and return the result to the client

As you can see from this, NET/HTTP basically offers a full set of services.

Why are there so many GO frameworks

// net/http/server.go:L2218-2238 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. var n = 0 for k, v := range mux.m { if ! pathMatch(k, path) { continue } if h == nil || len(k) > n { n = len(k) h = v.h pattern = v.pattern } } return }Copy the code

It can be seen from this function that the matching rule is too simple. When a route can be matched, it returns the corresponding handler, and when it cannot be matched, it returns /.net/HTTP. Route matching cannot meet the complex requirements of development.

So basically one of the main things that all go frameworks do is rewrite the NET/HTTP route. It’s not too much of an overstatement to say gin is an Httprouter, but gin also provides other major functions that will be covered later.

Summary: NET/HTTP has provided 70% of the functions of HTTP services. Those so-called fast GO frameworks basically provide some functions to enable us to better handle requests from clients. If you are interested, you can also build a Go framework based on NET/HTTP.