preface

Recently in the study of K8S source code, kube-Apiserver module uses the Go restful framework, learning go restful need to first understand the official Web request process, so I sorted out this source analysis notes.

The other two articles on GO are accompanied by detailed illustrations:

  • Web framework used by K8S: Go -restful source analysis
  • K8s source code analysis – Informer mechanism

Go provides a standard library, NET/HTTP, that makes it very easy to implement a simple HTTP server in just a few lines of code. This article will take a deeper look at how the GO standard library net/ HTTP implements HTTP services

Quickly set up the HTTP server service

The procedure for setting up an HTTP server is as follows:

  • Write the handler function
  • Registered routing
  • Create the service and enable listening
package main

import (
  "io"
  "log"
  "net/http"
)

// Request handlers
func indexHandler(w http.ResponseWriter, r *http.Request) {
  _, _ = io.WriteString(w, "hello, world! \n")}func main(a) {
  // Register the route
  http.HandleFunc("/", indexHandler)
  // Create a service and enable listening
  err := http.ListenAndServe(": 8001".nil)
  iferr ! =nil {
    log.Fatal("ListenAndServe: ", err)
  }
}
Copy the code

HTTP service processing flow

  • The request goes to the route first
  • Routing finds the appropriate handler for the request
  • Handler processes the request and builds the response

Golang HTTP packet processing flow

  • The core object for routing processing is ServeMux
  • The ServeMux maintains a map attribute that stores the mapping between routing paths and routing processing functions
  • When registering a route, data is written to the map
  • When a route is matched, an appropriate handler is found from the map

Critical source logic

The key logic in the source code is shown below:

Hd address

Route Registration interface

There are two functions available for route registration, both of which call DefaultServeMux underneath

Source: SRC /net/ HTTP /server.go

type Handler interface {
  ServeHTTP(ResponseWriter, *Request)
}

func Handle(pattern string, handler Handler) {
  DefaultServeMux.Handle(pattern, handler)
}

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  DefaultServeMux.HandleFunc(pattern, handler)
}
Copy the code

Routing implementation

Routing in GO is implemented based on the ServeMux structure

type ServeMux struct {
  mu    sync.RWMutex
  // Mapping between storage routes and handlers
  m     map[string]muxEntry
  // muxEntry is sorted according to the route expression from long to short
  es    []muxEntry
  // Whether the routing expression contains a host name
  hosts bool
}

type muxEntry struct {
  // Route handler function
  h       Handler
  // Route expression
  pattern string
}

Copy the code

Route Registration logic

  • Go provides the default route instance DefaultServeMux, which is used if the user does not have a custom route
  • Add the core logic of the routing function: store the expression as the key and the muxEntry composed of the routing function and the expression as the value in the map
// The default route instance after the service is started
var DefaultServeMux = &defaultServeMux

// The internal logic of calling Handle in the previous demo
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  DefaultServeMux.HandleFunc(pattern, handler)
}

// HandleFunc
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)){... mux.Handle(pattern, HandlerFunc(handler)) }// Handle
func (mux *ServeMux) Handle(pattern string, handler Handler){...// Create an M instance of ServeMux
  if mux.m == nil {
    mux.m = make(map[string]muxEntry)
  }
  // Construct a muxEntry object based on route expressions and route handlers
  e := muxEntry{h: handler, pattern: pattern}
  // Save muxEntry to map
  mux.m[pattern] = e

  // If the expression ends in '/', it is added to the sorted list
  if pattern[len(pattern)- 1] = ='/' {
    mux.es = appendSorted(mux.es, e)
  }

  if pattern[0] != '/' {
    mux.hosts = true}}Copy the code

Open the service

The core logic includes: listening for ports, waiting for connections, creating connections, and processing requests

// Open the service entry
func ListenAndServe(addr string, handler Handler) error {
  // Create a Server and pass in handler
  // Handler is null in our example
  server := &Server{Addr: addr, Handler: handler}
  // Call ListenAndServe to really listen
  return server.ListenAndServe()
}

// ListenAndServe
func (srv *Server) ListenAndServe(a) error{... ln, err := net.Listen("tcp", addr)
  return srv.Serve(ln)
}

func (srv *Server) Serve(l net.Listener) error{.../ / a for loop
  for {
    // Create a context object
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    // Wait for a new connection to be established
    rw, err := l.Accept()
    ...
    // When a connection is established, create a connection object
    c := srv.newConn(rw)
    c.setState(c.rwc, StateNew) // before Serve can return
    // Create a coroutine to process the request
    go c.serve(connCtx)
  }
}
Copy the code

Handle the request

The logic for processing the request is to route the request to the ServeMux m and find the appropriate handler

func (c *conn) serve(ctx context.Context){...for {
    // Read the next request for processing (all requests are made in this coroutine)
    w, err := c.readRequest(ctx)
    ...

    // Internally call the ServeHTTP functionserverHandler{c.server}.ServeHTTP(w, w.req) ... }}// ServeHTTP
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
  // sh.srv.Handler is the Handler passed by the previous http.listenandServe (":8001", nil)
  handler := sh.srv.Handler
  // If handler is empty, DefaultServeMux is used
  if handler == nil {
    handler = DefaultServeMux
  }
  if req.RequestURI == "*" && req.Method == "OPTIONS" {
    handler = globalOptionsHandler{}
  }
  // Here is the ServeHTTP calling ServeMux
  handler.ServeHTTP(rw, req)
}

// ServeHTTP
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request){... h, _ := mux.Handler(r) h.ServeHTTP(w, r) }// Handler
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string){...return mux.handler(host, r.URL.Path)
}

// handler
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string){...if mux.hosts {
    h, pattern = mux.match(host + path)
  }
  if h == nil {
    h, pattern = mux.match(path)
  }
  if h == nil {
    h, pattern = NotFoundHandler(), ""
  }
  return
}

// Matches the routing function
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
  // Start by looking up the exact route expression from m in ServeMux
  v, ok := mux.m[path]
  // If found, return handler directly
  if ok {
    return v.h, v.pattern
  }

  // If there is no exact match, go to the list to find the closest route
  // Routes in mux.es are sorted from long to short
  for _, e := range mux.es {
    if strings.HasPrefix(path, e.pattern) {
      return e.h, e.pattern
    }
  }
  return nil.""
}
Copy the code