В предыдущем разделе мы научились использовать пакет 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