Files
build-web-application-with-…/ru/03.4.md
2021-06-21 22:01:42 +07:00

138 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 3.4 Внутренний мир пакета http
В предыдущих разделах мы узнали о том, как работает Веб и немного затронули работу с пакетом `http`. В данном разделе мы изучим две основные функции этого пакета: Conn и ServeMux.
## Использование горутин в функции Conn
В отличии от обычных HTTP серверов, Go использует горутины при каждом обращении к функции Conn. За счет этого обеспечивается высокая производительность и параллельная обработка.
Go использует следующий код для ожидания новых подключений от клиента:
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
Как вы видите, горутины создаются для каждого подключения. При этом в горутину передается обработчик, способный читать данные из запросов.
## Настраиваемые ServeMux
В предыдущем разделе, при рассмотрении метода 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`.
1. Вызывается `HandleFunc` из `DefaultServeMux`.
2. Вызывается `Handle` из `DefaultServeMux`.
3. Добавляются правила маршрутизации в карту `map[string]muxEntry` из `DefaultServeMux`.
- Вызывается `http.ListenAndServe(":9090", nil)`.
1. Создается экземпляр `Server`.
2. Вызывается `ListenAndServe` для `Server`.
3. Вызывается `net.Listen("tcp", addr)` для прослушки порта.
4. Запускается бесконечный цикл, в теле которого происходит прием запросов.
5. Создается экземпляр Conn и запускаются горутины для каждого запроса: `go c.serve()`.
6. Читаются данные запроса: `w, err := c.readRequest()`.
7. Проверяется существует ли обработчик и если обработчика нет используется `DefaultServeMux`.
8. Вызывается `ServeHTTP` для обработчика.
9. Исполняется код в `DefaultServeMux` в нашем случае.
10. Выбирается обработчик, соответсвующий URL, и исполняется код обработчика: `mux.handler.ServeHTTP(w, r)`
11. Как выбирается обработчик:
A. Проверяются правила маршрутизации по данному URL.
B. Вызывается `ServeHTTP` в данном обработчике, если он есть.
C. В противном случае вызывается `ServeHTTP` для `NotFoundHandler`.
## Ссылки
- [Содержание](preface.md)
- Предыдущий раздел: [Как Go работает с веб](03.3.md)
- Следующий раздел: [Итоги раздела](03.5.md)