Shanraq.org Shanraq.org
Первая программа на Go: веб-сервер на net/http за 20 минут
IT

Go: с нуля до своего блога Урок 3 из 50

Первая программа на Go: веб-сервер на net/http за 20 минут

Третий урок курса по Go. Первая программа на Go и работающий веб-сервер: package main, func main, http.HandleFunc, http.ListenAndServe и порт 8080. Откройте localhost:8080 в браузере и увидите ответ своей программы раньше, чем выучите хоть одно правило языка. И что делать, если порт занят.

Зачем это нужно

В прошлом уроке вы смотрели на чужой сервер со стороны клиента. Сегодня встанете на другую сторону: напишете программу, которая отвечает браузеру.

На это уйдёт двадцать минут, и ни одного правила языка учить не придётся. Всё остальное в курсе — подробности к тому, что вы сделаете сейчас.

Сразу целиком

Рабочее место у вас уже собрано в прошлом уроке. Создайте рядом с ним новую папку sabaq-02, откройте её в редакторе (File → Open Folder) и выполните в терминале:

go mod init sabaq02

Теперь создайте в ней файл main.go и положите в него эту программу:

package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Привет!"))
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Перейдите в терминале в эту папку и запустите:

go run .

Курсор замрёт, новая строка не появится. Так и должно быть: программа работает и ждёт. Откройте браузер и наберите localhost:8080.

На экране «Привет!». Это ответила ваша программа.

Остановить её — Ctrl+C в терминале.

Если не получилось. Строка ввода вернулась сразу, а в терминале написано address already in use — порт 8080 занят другой программой. Замените в коде 8080 на 8081 и откройте localhost:8081. Написано command not found: go — терминал не видит Go: закройте и откройте его заново, а если не помогло, вернитесь к прошлому уроку, к проверке go version. Windows спросил про доступ в сеть — разрешите для частных сетей; порт :8080 без адреса слева означает «все сетевые интерфейсы», то есть в домашней сети сервер увидят и с телефона. Пока вы учитесь, это удобно; чтобы закрыться только своей машиной, пишут 127.0.0.1:8080.

Разбор

package main и func main

package main говорит: это не библиотека, а программа, которую можно запустить. func main — то место, откуда запуск начинается. Ровно одна такая функция на программу, и Go ищет её по имени.

Пока примите это как обязательную рамку. К пакетам вернёмся в уроке про пакеты и модули, когда их станет несколько.

import “net/http”

Всё, что нужно для веба, уже есть в самом Go — отдельно ставить нечего. net/http умеет и спрашивать как клиент, и отвечать как сервер. Мы взяли вторую половину.

http.HandleFunc: что отвечать

Первый аргумент — путь, второй — что на нём делать. Мы сказали: на пути "/" выполняй вот эту функцию.

Обратите внимание: функция написана прямо внутри вызова, без имени. В Go так можно, и для коротких обработчиков это обычное дело.

Образ. Список поручений, вывешенный на двери: «придут за справкой — выдайте вот эту». Пока никто не пришёл, список просто висит и ничего не делает.

w и r: ответ и запрос

Два аргумента обработчика — это те самые две стороны из прошлого урока.

r — запрос: что читатель спросил, каким методом, по какому пути, с какими заголовками. Сейчас мы его не трогаем.

w — то, куда пишется ответ. w.Write отправляет тело. Код состояния мы не указывали, и Go подставил 200 сам — потому что это самый частый случай.

[]byte("Привет!") пока читайте как «эта строка в виде набора байтов». Почему так, станет ясно в уроке про строки и руны, где мы разберём, из чего они сделаны.

Образ. Два лотка на столе. В левый почтальон кладёт пришедшее письмо — это r, его вы читаете. Правый пустой, и в него вы кладёте ответ — это w. Перепутать лотки нельзя: из левого не отправляют.

http.ListenAndServe: где слушать

":8080" — порт, тот самый номер квартиры из прошлого урока. Второй аргумент nil означает «бери обработчики, которые я зарегистрировал через HandleFunc». В уроке про маршруты мы поставим туда своё и увидим разницу.

Эта строка и есть причина, по которой терминал замер: программа не закончилась, она слушает.

log.Fatal вокруг неё — на случай, когда слушать не удалось: порт занят, прав не хватило. Тогда программа не умрёт молча, а напишет причину и выйдет. Без этой обёртки самый частый сбой новичка выглядит как «просто ничего не произошло».

Образ. Сесть за окно приёма и открыть его. Пока сидите — принимаете посетителей. Встали и ушли — окно закрыто, и стучать бесполезно.

Почему 8080, а не 80

Порт 80 — стандартный для HTTP, но занимать его на своей машине обычно нельзя без прав администратора. Если IP — это дом, а порт — квартира, то 80 и 443 в этом доме служебные: туда без ключа от управляющей компании не заселишься. 8080 свободен и ничем не хуже для разработки. На настоящем сервере, в уроке про домен и HTTPS, 80 и 443 займёт не ваша программа, а Caddy перед ней.

Что произошло, когда вы открыли страницу

Браузер отправил GET / на localhost порт 8080. Ваша программа приняла запрос, нашла обработчик для /, выполнила его и вернула 200 и тело. Ровно тот обмен, который вы разбирали в уроке о том, как работает интернет — только сервером на этот раз были вы.

Карта урока

Карта урока: что отвечать, где слушать, браузер

Скажите своими словами

Не подглядывая, ответьте вслух или на бумаге. Ответы — в конце урока.

  1. Почему после go run терминал «завис», и почему это правильно?
  2. Что делает HandleFunc, а что ListenAndServe? Почему нужны обе?
  3. В коде зарегистрирован только путь "/". Что программа ответит на localhost:8080/about?

Задание

Обязательное. Поменяйте текст ответа на своё имя. Остановите программу, запустите заново, обновите страницу. Всё.

По желанию.

  • Добавьте второй адрес: ещё один http.HandleFunc с путём "/about" и другим текстом.
  • Посмотрите на свой ответ глазами прошлого урока: curl -i localhost:8080. Найдите код состояния и content-type. Откуда взялся content-type, если вы его не писали?
  • Поменяйте порт на 3000 и убедитесь, что на 8080 больше никто не отвечает.

Куда это встанет в блоге

Эти строки — скелет вашего блога. Он ещё изменится: у сервера появится свой маршрутизатор, таймауты и корректное завершение. Но форма останется той же, и всё остальное вешается на неё: в уроке про маршруты один путь "/" превратится в десяток адресов, в уроке про шаблоны ответ станет настоящей HTML-страницей, а в уроке про CRUD текст придёт из базы, а не из кода.

Соберите свой блог. Отдельные программы учат языку, а блог растёт из них. Состояние блога после этого урока лежит в репозитории курса — step-1. Сначала напишите сами, потом сверьтесь: сверка после работы учит, сверка вместо работы — нет.

Ответы

Показать ответы
  1. Потому что ListenAndServe не возвращает управление: программа слушает порт и ждёт запросов. Завершись она сразу — отвечать было бы некому. Это не зависание, а работа.
  2. HandleFunc только запоминает, что отвечать на таком-то пути; сама по себе она ничего не слушает. ListenAndServe открывает порт и начинает принимать запросы, но без зарегистрированных обработчиков отвечать ей было бы нечем. Одна без другой бесполезна.
  3. Тоже «Привет!». Путь "/" в Go — не «только главная страница», а «всё, для чего не нашлось более точного правила». Отсюда и ответ на /about, и, кстати, отсутствие 404 там, где вы его ждали.

Источники

Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом

Проверить задание

Сначала решите и запустите в VS Code — редактор покажет ошибку на месте. Готовое решение вставьте сюда. Проверяет модель: она укажет на ошибку, но не даст готовый ответ.

Чтобы проверить, нужно войти. Войти

Комментарии (0)

Пока нет комментариев. Будьте первым.