В предыдущих разделах мы узнали о том, как работает Веб и немного затронули работу с пакетом http
. В данном разделе мы изучим две основные функции этого пакета: Conn и ServeMux.
В отличии от обычных HTTP серверов, Go использует горутины при каждом обращении к функции Conn. За счет этого обеспечивается высокая производительность и параллельная обработка.
Go использует следующий код для ожидания новых подключений от клиента:
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
Как вы видите, горутины создаются для каждого подключения. При этом в горутину передается обработчик, способный читать данные из запросов.
В предыдущем разделе, при рассмотрении метода conn.server, мы использовали роутер по умолчанию. Основная задача роутера - передать данные запроса конкретному обработчику.
Структура роутера по умолчанию:
type ServeMux struct {
mu sync.RWMutex // здесь используются мьютексы для синхронизации параллельных потоков
m map[string]muxEntry // правила маршрутизации, каждая строка ссылается на обработчик
}
Структура muxEntry:
type muxEntry struct {
explicit bool // точное совпадение или нет
h Handler
}
Интерфейс Handler:
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // реализация маршрутизации
}
Handler
- это интерфейс, однако, функция sayhelloName
не реализует этот интерфейс. Почему, в таком случае, мы смогли использовать ее в качестве обработчика? Потому, что в пакете http
существует другой тип HandlerFunc
. В нашем сервере из раздела 3.2 при вызове HandlerFunc
происходит автоматическое приведение нашей функции sayhelloName
к интерфейсу Handler
. Это равносильно вызову HandlerFunc(f)
, при этом f
будет принудительно приведена к типу HandlerFunc
.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP вызывает f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
Как маршрутизатор вызывает обработчики после установки правил?
Маршрутизатор вызывает mux.handler.ServeHTTP(w, r)
при получении запросов. Другими словами, он вызывает ServeHTTP
интерфейсы обработчиков.
Давайте посмотрим, как работает mux.handler
.
func (mux *ServeMux) handler(r *Request) Handler {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-зависимый шаблон, имеет приоритет над универсальным
h := mux.match(r.Host + r.URL.Path)
if h == nil {
h = mux.match(r.URL.Path)
}
if h == nil {
h = NotFoundHandler()
}
return h
}
Маршрутизатор использует URL-адрес в качестве ключа для поиска соответствующего обработчика, который сохранен в карте, и вызывает handler.ServeHTTP для обработки данных.
Теперь вы должны понимать принципы работы роутера. Фактически, Go поддерживает настраиваемые роутеры. Второй аргумент функции ListenAndServe
необходим для конфигурации настраиваемого роутера с типом Handler
. Таким образом, любой роутер реализует интерфейс Handler
.
Следующий пример покажет, как реализовать простой роутер.
package main
import (
"fmt"
"net/http"
)
type MyMux struct {
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
sayhelloName(w, r)
return
}
http.NotFound(w, r)
return
}
func sayhelloName(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello myroute!")
}
func main() {
mux := &MyMux{}
http.ListenAndServe(":9090", mux)
}
Давайте посмотрим на поток выполнения.
- Вызывается
http.HandleFunc
.- Вызывается
HandleFunc
изDefaultServeMux
. - Вызывается
Handle
изDefaultServeMux
. - Добавляются правила маршрутизации в карту
map[string]muxEntry
изDefaultServeMux
.
- Вызывается
- Вызывается
http.ListenAndServe(":9090", nil)
.- Создается экземпляр
Server
. - Вызывается
ListenAndServe
дляServer
. - Вызывается
net.Listen("tcp", addr)
для прослушки порта. - Запускается бесконечный цикл, в теле которого происходит прием запросов.
- Создается экземпляр Conn и запускаются горутины для каждого запроса:
go c.serve()
. - Читаются данные запроса:
w, err := c.readRequest()
. - Проверяется существует ли обработчик и если обработчика нет используется
DefaultServeMux
. - Вызывается
ServeHTTP
для обработчика. - Исполняется код в
DefaultServeMux
в нашем случае. - Выбирается обработчик, соответсвующий URL, и исполняется код обработчика:
mux.handler.ServeHTTP(w, r)
- Как выбирается обработчик:
A. Проверяются правила маршрутизации по данному URL.
B. Вызывается
ServeHTTP
в данном обработчике, если он есть. C. В противном случае вызываетсяServeHTTP
дляNotFoundHandler
.
- Создается экземпляр
- Содержание
- Предыдущий раздел: Как Go работает с веб
- Следующий раздел: Итоги раздела