предисловие
Недавно я изучаю исходный код k8s, и модуль kube-apiserver использует структуру go-restful.Чтобы изучить go-restful, вам нужно сначала понять процесс официальных веб-запросов, поэтому я составил эту заметку об анализе исходного кода. .
Две другие статьи на go сопровождаются подробными легендами:
- Веб-фреймворк, используемый k8s: анализ исходного кода go-restful
- Анализ исходного кода k8s - Механизм информера
Стандартная библиотека net/http, предоставляемая go, упрощает реализацию простого http-сервера и требует всего несколько строк кода. В этой статье будет проведено более глубокое исследование принципа использования стандартной библиотеки go net/http для реализации http-сервисов.
Быстро создать службу http-сервера
Общие шаги для создания http-сервера включают в себя:
- Функция обработчика записи обработчика
- зарегистрировать маршрут
- Создайте сервис и начните слушать
package main
import (
"io"
"log"
"net/http"
)
// 请求处理函数
func indexHandler(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "hello, world!\n")
}
func main() {
// 注册路由
http.HandleFunc("/", indexHandler)
// 创建服务并开启监听
err := http.ListenAndServe(":8001", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
поток обработки http-сервиса
- Запрос войдет в маршрут первым
- Маршрут находит подходящий обработчик для запроса
- Обработчик обрабатывает запрос и формирует ответ
Поток обработки HTTP-пакетов Golang
- Основным объектом обработки маршрутизации является ServeMux.
- ServeMux поддерживает внутреннее свойство карты, которое сохраняет отношение сопоставления между путями маршрутизации и функциями обработки маршрутизации.
- При регистрации маршрута записывать данные на карту
- При сопоставлении маршрута найти соответствующий обработчик обработчика с карты
Ключевая исходная логика
На следующем рисунке показана логика ключей в исходном коде:
интерфейс регистрации маршрута
Есть две функции, которые можно использовать для регистрации маршрута, нижний уровень вызывает DefaultServeMux.
Расположение исходного кода: 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)
}
Реализация маршрутизации
Маршрутизация на ходу реализована на основе структуры ServeMux.
type ServeMux struct {
mu sync.RWMutex
// 存储路由和handler的对应关系
m map[string]muxEntry
// 将muxEntry排序存放,排序按照路由表达式由长到短排序
es []muxEntry
// 路由表达式是否包含主机名
hosts bool
}
type muxEntry struct {
// 路由处理函数
h Handler
// 路由表达式
pattern string
}
Логика регистрации маршрута
- go предоставляет экземпляр маршрутизации по умолчанию DefaultServeMux, если у пользователя нет собственного маршрута, используйте этот маршрут по умолчанию
- Добавьте основную логику функции маршрутизации: используйте выражение в качестве ключа и сохраните muxEntry, состоящую из функции обработки маршрутизации и выражения, в качестве значения на карте.
// 服务启动后的默认路由实例
var DefaultServeMux = &defaultServeMux
// 前面demo中调用handle的内部逻辑
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) {
...
// 创建ServeMux的m实例
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
// 根据路由表达式和路由处理函数,构造muxEntry对象
e := muxEntry{h: handler, pattern: pattern}
// muxEntry保存到map中
mux.m[pattern] = e
// 如果表达式以 '/' 结尾,加入到排序列表中
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
Запустить службу
Основная логика включает в себя: прослушивание портов, ожидание соединений, создание соединений и обработку запросов.
// 开启服务的入口
func ListenAndServe(addr string, handler Handler) error {
// 创建一个Server,传入handler
// 我们的例子中handler为空
server := &Server{Addr: addr, Handler: handler}
// 调用ListenAndServe真正监听
return server.ListenAndServe()
}
// ListenAndServe
func (srv *Server) ListenAndServe() error {
...
ln, err := net.Listen("tcp", addr)
return srv.Serve(ln)
}
func (srv *Server) Serve(l net.Listener) error {
...
// for循环
for {
// 创建上下文对象
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
// 等待新的连接建立
rw, err := l.Accept()
...
// 连接建立时,创建连接对象
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
// 创建协程处理请求
go c.serve(connCtx)
}
}
обработать запрос
Логика обработки запроса в основном такова: сопоставьте m ServeMux в соответствии с запросом маршрутизации и найдите соответствующий обработчик.
func (c *conn) serve(ctx context.Context) {
...
for {
// 读取下一个请求进行处理(所有的请求都在该协程中进行)
w, err := c.readRequest(ctx)
...
// 内部转调ServeHTTP函数
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
}
// ServeHTTP
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
// sh.srv.Handler是前面的http.ListenAndServe(":8001", nil)传入的handler
handler := sh.srv.Handler
// 如果handler为空,就用默认的DefaultServeMux
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
// 这里就是调用ServeMux的ServeHTTP
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
}
// 匹配路由函数
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// 先从前面介绍的ServeMux的m中精确查找路由表达式
v, ok := mux.m[path]
// 如果找到,直接返回handler
if ok {
return v.h, v.pattern
}
// 如果不能精确匹配,就去列表中找到最接近的路由
// mux.es中的路由是按照从长到短排序的
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}