предисловие
Для 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фреймворки и промежуточное ПО, которое пишет определенные функции.
выше.