предисловие
Для Golang реализация простогоhttp server
Очень просто, всего несколько строк кода. В то же время, с благословения сопрограмм, Go реализуетhttp server
Может добиться очень хорошей производительности. В этой статье мы рассмотрим стандартную библиотеку go.net/http
Углубленное изучение принципа реализации http-сервисов, чтобы научиться понимать общие парадигмы и идеи дизайна сетевого программирования.
HTTP-сервис
Сетевое приложение, построенное на основе HTTP, включает в себя два конца, а именно клиент (Client
) и сервер (Server
). Взаимодействие между двумя концами включает отправку от клиентаrequest
, сервер принимаетrequest
обработать и вернутьresponse
и обработка на стороне клиентаresponse
. Таким образом, работа http-сервера заключается в том, чтобы принятьrequest
, и вернуться к клиентуresponse
.
Поток обработки типичного http-сервера можно представить на следующем рисунке:
Когда сервер получает запрос, он сначала входит в маршрут (router
),ЭтоMultiplexer
, работа маршрутизации для этогоrequest
Найдите соответствующий процессор (handler
), пара процессоровrequest
обрабатывать и строитьresponse
. Реализовано Голангомhttp server
Следуйте тому же процессу обработки.
Давайте сначала посмотрим, как Golang реализует простойhttp server
:
package main
import (
"fmt"
"net/http"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello world")
}
func main() {
http.HandleFunc("/", indexHandler)
http.ListenAndServe(":8000", nil)
}
После запуска кода откройте его в браузереlocalhost:8000
ты можешь видетьhello world
. Сначала используйте этот кодhttp.HandleFunc
маршрут в корне/
зарегистрировалindexHandler
, а затем используйтеhttp.ListenAndServe
Включите мониторинг. Когда приходит запрос, по маршруту выполняется соответствующий маршрут.handler
функция.
Рассмотрим еще один распространенныйhttp server
Метод реализации:
package main
import (
"fmt"
"net/http"
)
type indexHandler struct {
content string
}
func (ih *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, ih.content)
}
func main() {
http.Handle("/", &indexHandler{content: "hello world!"})
http.ListenAndServe(":8001", nil)
}
Реализовано Gohttp
Шаги службы очень просты, сначала зарегистрируйте маршрут, затем создайте службу и начните слушать. Далее мы узнаем, как Golang реализует этапы регистрации маршрутов, запуска сервисов и обработки запросов.http
Служить.
зарегистрировать маршрут
http.HandleFunc
иhttp.Handle
Оба используются для регистрации маршрутов, можно обнаружить, что разница между ними заключается во втором параметре.func(w http.ResponseWriter, r *http.Requests)
подписанная функция, представляющая собой структуру, реализующуюfunc(w http.ResponseWriter, r *http.Requests)
Подписанный метод.http.HandleFunc
иhttp.Handle
Исходный код выглядит следующим образом:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}
Можно видеть, что эти две функции в конечном счете состоят изDefaultServeMux
перечислитьHandle
способ завершения регистрации маршрута.
Здесь мы сталкиваемся с двумя типами объектов:ServeMux
иHandler
, скажемHandler
.
Handler
Handler
это интерфейс:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Handler
Интерфейс объявляет имяServeHTTP
Сигнатура функции , то есть любой структуры, которая реализует этоServeHTTP
метод, то эта структура являетсяHandler
объект. На самом деле идиhttp
Услуги основаны наHandler
обрабатывается, покаHandler
объектServeHTTP
метод также используется для борьбы сrequest
и построитьresponse
где основная логика.
вернуться к вершинеHandleFunc
функции, обратите внимание на эту строку кода:
mux.Handle(pattern, HandlerFunc(handler))
некоторые могут подуматьHandlerFunc
Это функция, которая обернутся входящееhandler
функция, которая возвращаетHandler
объект. Однако здесьHandlerFunc
на самом деле будетhandler
функция делаетпреобразование типов,посмотриHandlerFunc
Определение:
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
HandlerFunc
это тип, который просто представляетfunc(ResponseWriter, *Request)
сигнатура типа функции, и этот тип реализуетServeHTTP
метод (вServeHTTP
метод вызывает сам себя), что означает, что этот тип функции на самом деле являетсяHandler
тип объекта. Используя это преобразование типов, мы можем преобразоватьhandler
функция преобразуется вHandler
объекта без необходимости определять структуру, а затем позволить этой структуре реализоватьServeHTTP
метод. Читатели могут испытать эту технику.
ServeMux
Маршрутизация в Golang (т.е.Multiplexer
) на основеServeMux
Структура, первый взгляд наServeMux
Определение:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
Сосредоточьтесь здесьServeMux
поля вm
,Этоmap
,key
это выражение маршрутизации,value
ЯвляетсяmuxEntry
структура,muxEntry
В структуре хранятся соответствующие выражения маршрутизации иhandler
.
В частности,ServeMux
также достигнутоServeHTTP
метод:
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)
h.ServeHTTP(w, r)
}
то естьServeMux
структура такжеHandler
объект, ноServeMux
изServeHTTP
методы не предназначены для работы с бетономrequest
и построитьresponse
, но используется для определения регистрации маршрутаhandler
.
зарегистрировать маршрут
пониматьHandler
иServeMux
После этого возвращаемся к предыдущему коду:
DefaultServeMux.Handle(pattern, handler)
здесьDefaultServeMux
означает значение по умолчаниюMultiplexer
, когда мы не создаем пользовательскиеMultiplexer
, он автоматически использует значение по умолчаниюMultiplexer
.
тогда посмотри еще разServeMux
изHandle
Что именно делает метод:
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)
}
// 利用当前的路由和handler创建muxEntry对象
e := muxEntry{h: handler, pattern: pattern}
// 向ServeMux的map[string]muxEntry增加新的路由匹配规则
mux.m[pattern] = e
// 如果路由表达式以'/'结尾,则将对应的muxEntry对象加入到[]muxEntry中,按照路由表达式长度排序
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
Handle
Этот метод в основном делает две вещи: первая заключается в том, чтобыServeMux
изmap[string]muxEntry
Добавляет заданное правило сопоставления маршрутов; затем, если выражение маршрута заканчивается на'/'
конец, соответствующийmuxEntry
объект добавлен в[]muxEntry
, отсортированные по длине выражения маршрутизации. Первое легко понять, но последнее может быть нелегко увидеть, какой эффект он оказывает, что будет проанализировано позже.
Пользовательский ServeMux
Мы также можем создать индивидуальныйServeMux
заменить значение по умолчаниюDefaultServeMux
:
package main
import (
"fmt"
"net/http"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello world")
}
func htmlHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
html := `<!doctype html>
<META http-equiv="Content-Type" content="text/html" charset="utf-8">
<html lang="zh-CN">
<head>
<title>Golang</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;" />
</head>
<body>
<div id="app">Welcome!</div>
</body>
</html>`
fmt.Fprintf(w, html)
}
func main() {
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(indexHandler))
mux.HandleFunc("/welcome", htmlHandler)
http.ListenAndServe(":8001", mux)
}
NewServeMux()
может создатьServeMux
пример, упомянутый ранееServeMux
также достигнутоServeHTTP
метод, поэтомуmux
такжеHandler
объект. заListenAndServe()
метод, если передан вhandler
параметр является пользовательскимServeMux
примерmux
,ТакServer
Объект маршрута, полученный экземпляром, больше не будетDefaultServeMux
ноmux
.
Запустить службу
Первый изhttp.ListenAndServe
Этот метод запускается:
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.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
Здесь впервые создалиServer
объекта, передавая адрес иhandler
параметры, затем вызовитеServer
объектListenAndServe()
метод.
посмотриServer
эта структура,Server
В структуре много полей, сначала можно получить общее представление:
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
TLSConfig *tls.Config
ReadTimeout time.Duration
ReadHeaderTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
disableKeepAlives int32 // accessed atomically.
inShutdown int32 // accessed atomically (non-zero means we're in Shutdown)
nextProtoOnce sync.Once // guards setupHTTP2_* init
nextProtoErr error // result of http2.ConfigureServer if used
mu sync.Mutex
listeners map[*net.Listener]struct{}
activeConn map[*conn]struct{}
doneChan chan struct{}
onShutdown []func()
}
существуетServer
изListenAndServe
метод, адрес прослушивания будет инициализированAddr
, во время вызоваListen
метод установки слушателя. Наконец, передайте прослушивающий объект TCPServe
метод:
func (srv *Server) Serve(l net.Listener) error {
...
baseCtx := context.Background() // base is always background, per Issue 16220
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, e := l.Accept() // 等待新的连接建立
...
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx) // 创建新的协程处理请求
}
}
Некоторые детали скрыты здесь для того, чтобы понятьServe
Основная логика метода. Сначала создайте объект контекста, затем вызовитеListener
изAccept()
Подождите, пока будет установлено новое соединение; как только новое соединение будет установлено, вызовитеServer
изnewConn()
Создайте новый объект соединения и отметьте состояние соединения какStateNew
, затем откройте новыйgoroutine
Обработка запросов на подключение.
обработать соединение
мы продолжаем исследоватьconn
изserve()
метод, этот метод тоже очень длинный, мы тоже смотрим только на ключевую логику. Держись, ты скоро увидишь море.
func (c *conn) serve(ctx context.Context) {
...
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)
}
...
// HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another,
// so we might as well run the handler in this goroutine.
// [*] Not strictly true: HTTP pipelining. We could let them all process
// in parallel even if their responses need to be serialized.
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
if !w.shouldReuseConnection() {
if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
c.closeWriteAndWait()
}
return
}
c.setState(c.rwc, StateIdle)
c.curReq.Store((*response)(nil))
...
}
}
Когда соединение установлено, все запросы в соединении будут обрабатываться в этой сопрограмме, пока соединение не будет закрыто. существуетserve()
метод будет вызываться циклическиreadRequest()
Метод считывает следующий запрос для обработки, и самая важная логика — это строка кода:
serverHandler{c.server}.ServeHTTP(w, w.req)
Дальнейшее объяснениеserverHandler
:
type serverHandler struct {
srv *Server
}
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)
}
существуетserverHandler
изServeHTTP()
в методеsh.srv.Handler
На самом деле мы изначальноhttp.ListenAndServe()
входящийHandler
Объект, который является нашим заказомServeMux
объект. еслиHandler
объектnil
, будет использоваться значение по умолчаниюDefaultServeMux
. последний звонокServeMux
изServeHTTP()
Метод соответствует текущему маршруту, соответствующемуhandler
метод.
Логика относительно проста и ясна, в основном в вызовеServeMux
изmatch
метод соответствует соответствующему зарегистрированному выражению маршрута иhandler
.
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
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)
h.ServeHTTP(w, r)
}
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
// 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, ""
}
существуетmatch
В методе мы видим упомянутый ранее мультиплексорm
поле (типmap[string]muxEntry
)иes
(тип[]muxEntry
). Этот метод сначала будет использовать точное совпадение вmap[string]muxEntry
Найдите, есть ли в запросе соответствующее правило маршрутизации; если нет соответствующего правила маршрутизации, он будет использоватьes
Сделайте примерное совпадение.
Как упоминалось ранее, при регистрации маршрута он будет начинаться с'/'
трейлинг-маршрут (который можно назватьМаршрутизация узла)принять участие вes
поле[]muxEntry
середина. для подобных/path1/path2/path3
Для такого маршрута, если не удается найти точное соответствие правил маршрутизации, оно будет соответствовать зарегистрированному маршруту родительского узла, ближайшему к текущему маршруту, поэтому, если маршрут/path1/path2/
Зарегистрирован, тогда маршрут будет сопоставлен, в противном случае продолжите сопоставление маршрута следующего родительского узла, пока не будет найден корневой маршрут./
.
так как[]muxEntry
серединаmuxEntry
Выражения маршрутизации сортируются от самого длинного к самому короткому, поэтому при выполнении приблизительного сопоставления маршрут сопоставленного узла должен быть ближайшим среди зарегистрированных маршрутов родительского узла.
До сих пор Go реализовывалhttp server
Общий принцип введения выполнен!
Суммировать
Голанг черезServeMux
Мультиплексор определен для управления маршрутизацией и пропускомHandler
Интерфейс определяет унифицированную спецификацию функции обработки маршрута, а именноHandler
должны быть реализованыServeHTTP
метод; в то же времяHandler
Интерфейс обеспечивает мощную расширяемость, что удобно для разработчиковHandler
В интерфейсе реализовано различное промежуточное ПО. Я верю, что каждый может почувствовать это после прочтенияHandler
объект вserver
Реализация услуг действительно везде. пониматьserver
Основной принцип реализации вы можете прочитать в некоторых стороннихhttp server
фреймворки и промежуточное ПО, которое пишет определенные функции.
выше.