В предыдущем разделе мы научились использовать пакет net/http
для создания простого веб-сервера. В этом разделе мы вернемся к принципам работы Веб, которые рассматривали ранее, но уже в контексте языка Go.
Request: запрос данных от пользователей, включая методы POST, GET, Cookie и URL
Response: данные ответа от сервера.
Conn: соединения между клиентами и серверами.
Handler: логика обработки запроса и генерация ответа.
На следующей картинке показано, как работает веб-сервер Go.
Рисунок 3.9 работа http сервера
- Сервер создает прослушивающий сокет на определенном порту и ожидает подключения клиентов.
- Сервер принимает запросы от клиентов.
- Обрабатывает запросы посредством чтения HTTP заголовков (если используется метод POST, читаются данные из тела запроса) и отправляет их обработчикам. Наконец, сокет возвращает данные клиентам.
Для того чтобы точно узнать, как Go работает с Веб - необходимо получить ответ на три вопроса:
- Как прослушивается порт?
- Как принимаются клиентские соединения?
- Как распределяются обработчики?
В предыдущем разделе мы видели, что Go использует функцию ListenAndServe
для инициализации объекта сервера и вызова метода net.Listen("tcp", addr)
, устанавливающего TCP прослушку на заданный адрес и порт.
Давайте посмотрим на исходный код пакета http
.
//Используется код Go версии 1.1.2
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
var tempDelay time.Duration // время сна в случае сбоя
for {
rw, e := l.Accept()
if e != nil {
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
}
}
Как происходит прием клиентских запросов? В исходном коде мы видим, что происходит вызов метода srv.Serve(net.Listener)
для управления клиентскими запросами. В теле функции бесконечный цикл for{}
принимает запросы, создает новое соединение, запускает новую горутину go c.serve()
и передает в нее данные запроса. Так Go поддерживает высокий параллелизм, за счет того, что все горутины являются независимыми.
Теперь ответим на вопрос: как используются конкретные функции для управления запросами? Сначала метод conn
парсит запрос, возвращаемый c.ReadRequest()
, а затем получает соответствующий обработчик: handler := c.server.Handler
, который, в свою очередь, передается в качестве второго аргумента при вызове метода ListenAndServe
. В нашем сервере мы использовали nil
, поэтому Go использует обработчик по умолчанию: handler = DefaultServeMux
. Возникает вопрос - что здесь делает DefaultServeMux
? DefaultServeMux - это переменная, содержащая указатель на текущий маршрутизатор, который вызывает обработчики для заданных URL-адресов. Разве мы его устанавливали? Ответ - да. Помните в первой строке нашего веб-сервера мы использовали http.HandleFunc("/", sayhelloName)
. Эта функция регистрирует правила маршрутизации для пути "/". Когда URL-адрес запроса соответствует «/», маршрутизатор вызывает функцию «sayhelloName». DefaultServeMux вызывает ServerHTTP для получения функции обработчика соответствующего заданному пути. В нашем случае он вызывает «sayhelloName». Наконец, сервер отвечает клиенту.
Подробное описание процесса:
Рисунок 3.10 воркфлоу обработки HTTP-запроса
Теперь, я думаю, вы разобрались с тем, как работают веб-сервера Go.
- Содержание
- Предыдущий раздел: Создание простого веб-сервера
- Следующий раздел: Внутренний мир пакета http