Skip to content

Latest commit

 

History

History
137 lines (99 loc) · 7.4 KB

File metadata and controls

137 lines (99 loc) · 7.4 KB

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.

Ссылки