Стандартная библиотека Go позволяет запустить веб-сервер буквально в несколько строк. Это одна из главных причин, почему разработчики так любят Go. Но даже в таком простом коде есть нюансы, которые я хотел бы разобрать в этой статье.
Давайте разберем конкретный работающий пример веб-сервера и увидим, как он устроен.
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
n, err := fmt.Fprintf(w, "Hello, world!")
if err != nil {
fmt.Println(err)
}
fmt.Printf("Number of bytes written: %d\n", n))
})
if err := http.ListenAndServe(":8070", nil); err != nil {
fmt.Println("Server error:", err)
}
}
Что делает этот код?
При запуске этого кода программа делает две вещи:
- Регистрирует обработчик для корневого маршрута
/ - Запускает HTTP-сервер на порту
8070
Когда вы открываете в браузере http://localhost:8070, происходит следующее:
- В окне браузера появляется текст:
Hello, world! - В консоли, где запущена программа, появляется сообщение:
Number of bytes written: 13
Детальный разбор
Регистрация обработчика
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// тело функции
})
Что это значит:
http.HandleFunc — это команда для Go: «Когда кто-то зайдет на адрес /, выполни эту функцию».
Она принимает два параметра:
"/"— путь, на который реагирует обработчик (корень сайта)- функция — код, который будет выполнен при запросе
Почему / ловит все адреса?
В Go паттерн / соответствует корню и всем вложенным путям. То есть обработчик сработает и на http://localhost:8070/, и на http://localhost:8070/anything.
Что внутри обработчика?
Функция-обработчик получает два параметра:
w http.ResponseWriter— куда записывается ответ для пользователя. Все, что отправляется вw, улетает в браузер.r *http.Request— информация о запросе (метод, заголовки, параметры). В этом примере не используется.
n, err := fmt.Fprintf(w, "Hello, world!")
fmt.Fprintf — это функция форматированного вывода. Ее особенность: она пишет результат не в консоль, а туда, куда укажет первый аргумент. Передавая w, мы говорим: «Напиши Hello, world! прям в HTTP-ответ».
Функция возвращает 2 значения:
n— количество байт, которые были записаны. Строка"Hello, world!"занимает 13 байт (каждый символ — 1 байт).err— ошибка, если что-то пошло не так (например, пользователь закрыл браузер до получения ответа).
if err != nil {
fmt.Println(err)
}
Если при отправке ответа произошла ошибка — печатаем ее в консоль. В большинстве случаев ошибки не будет, но хороший код всегда должен проверять это.
fmt.Printf("Number of bytes written: %d\n", n)
Это строка выводит в консоль сервера сообщение о том, сколько байт было отправлено.
Важно понять: этот вывод виден только в консоли сервера. Пользователь в браузере его не увидит.
Запуск сервера
if err := http.ListenAndServe(":8070", nil); err != nil {
fmt.Println("Server error:", err)
}
http.ListenAndServe — главная команда, которая:
- Запускает HTTP-сервер
- Начинает слушать порт
8070 - Блокирует выполнение программы (то есть программа не завершится, пока сервер работает)
Второй параметр — nil — означает «используй стандартный механизм маршрутизации», который мы уже настроили через http.HandleFunc.
С помощью err мы обрабатываем случай, когда по какой-то причине сервер не смог запустится (например, порт 8070 уже занят другой программой). В таком случае мы увидим понятное сообщение об ошибке.
Почему проверка ошибки так важна?
Представьте ситуацию: вы запускаете сервер на рабочем сервере, но порт 8070 случайно занят другим процессом. Без проверки ошибки программа просто завершится без единого сообщения. Вы будете думать, что сервер работает, а на самом деле он даже не запустился.
С проверкой ошибки вы сразу увидите что-то вроде:
Server error: listen tcp :8070: bind: address already in use
И сразу поймете, в чем проблема.
Ключевые элементы кода
http.HandleFunc— регистрирует функцию, которая будет обрабатывать запросы к определенному пути.http.ResponseWriter— интерфейс для отправки ответа клиенту. Работает как приемник вfmt.Fprintf.http.Request— содержит данные о запросе (методы, заголовки, тело).fmt.Fprintf— записывает форматированную строку в любойio.Writer, включаяhttp.ResponseWriter.http.ListenAndServe— запускает сервер и блокирует выполнение программы.
Ключевой вывод
Всего несколько строк кода — и у вас есть полноценный веб-сервер. Go сделал создание HTTP-сервисов настолько простым, насколько это возможно. При этом важно помнить о деталях: проверять ошибки, понимать, куда пишет fmt.Fprintf, и знать разницу между выводом для пользователя и выводом в лог сервера.
Если вы запустите этот код, то сообщение
Number of bytes written: 13выведется 2 раза. Как вы думаете, почему? Напишите в комментариях ваши предположения.