Содержание статьи
- Многоуровневый роутинг веб-приложения
- Обработка несуществующих URL маршрутов — Ошибка 404
- DefaultServeMux
- Особенности использования servemux
- Добавление домена в роутинг HTTP запросов
- Что насчет REST маршрутизации?
Веб-приложение только с одним маршрутом не очень интересное… или не очень полезное! Давайте добавим несколько маршрутов, чтобы наше приложение начало принимать более динамичную форму.
URL-Шаблон | Обработчик | Действие |
---|---|---|
/ | home | Отображает домашнюю страницу |
/snippet | showSnippet | Отображает определенную заметку |
/snippet/create | createSnippet | Создает новую заметку |
Откроем файл main.go
и обновим его содержимое следующим кодом:
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package main import ( "log" "net/http" ) // Обработчик главной страницы. func home(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Привет из Snippetbox")) } // Обработчик для отображения содержимого заметки. func showSnippet(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Отображение заметки...")) } // Обработчик для создания новой заметки. func createSnippet(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Форма для создания новой заметки...")) } func main() { // Регистрируем два новых обработчика и соответствующие URL-шаблоны в // маршрутизаторе servemux mux := http.NewServeMux() mux.HandleFunc("/", home) mux.HandleFunc("/snippet", showSnippet) mux.HandleFunc("/snippet/create", createSnippet) log.Println("Запуск веб-сервера на http://127.0.0.1:4000") err := http.ListenAndServe(":4000", mux) log.Fatal(err) } |
Не забудьте сохранить данные изменения и затем перезапустите веб-приложение:
1 2 3 |
$ cd $HOME/code/snippetbox $ go run main.go 2020/12/12 13:55:40 Запуск веб-сервера на http://127.0.0.1:4000 |
При переходе по следующей ссылке в браузере вы должны получить подходящий ответ для каждого URL-маршрута:
http://127.0.0.1:4000/
http://127.0.0.1:4000/snippet
http://127.0.0.1:4000/snippet/create
Многоуровневый роутинг веб-приложения
Теперь, когда два новых маршрута созданы, давайте разберем теорию…
Servemux (маршрутизатор HTTP запросов) в Go поддерживает два разных типа URL-шаблонов: фиксированные пути и многоуровневые пути. Фиксированные пути не заканчиваются косой чертой, тогда как многоуровневые пути заканчиваются косой чертой.
Два наших новых шаблона — "/snippet"
и "/snippet/create"
— являются примерами фиксированных путей. В servemux такие шаблоны фиксированного пути сопоставляются (и вызывается соответствующий обработчик) только тогда, когда путь URL-запроса точно совпадает с фиксированным путем.
В то время как, роутинг "/"
является примером многоуровневого пути (потому что он заканчивается косой чертой). Другим примером является что-то вроде "/static/"
. Шаблоны путей сопоставляются (вызывается соответствующий обработчик) всякий раз, когда начало пути URL запроса совпадает с путем поддерева.
У многоуровневых путей в конце может быть какой-то вспомогательный символ. К примеру, "/**"
или "/static/**"
.
Теперь понятно, почему шаблон "/"
действует по сценарию «catch-all» (ловим все запросы). По сути, данный шаблон
означает совпадение с одним слэшем, за которым следует что-нибудь (или вообще ничего).
Обработка несуществующих URL маршрутов — Ошибка 404
Что, если нам не нужно, чтобы шаблон "/"
улавливал абсолютно все запросы?
К примеру, в приложении, которое мы создаем, требуется, чтобы домашняя страница отображалась, если — и только если — URL-путь запроса точно совпадает с маршрутом "/"
и за ней ничего больше нет. В противном случае пользователь должен получить ошибку 404 страница не найдена
.
Для этой цели, поведение маршрутизатора HTTP запросов servemux в Go изменить невозможно, но вы можете добавить простую if-проверку в функцию обработчик home
. Это в конечном итоге даст тот же эффект:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package main ... // Обработчик главной странице. func home(w http.ResponseWriter, r *http.Request) { // Проверяется, если текущий путь URL запроса точно совпадает с шаблоном "/". Если нет, вызывается // функция http.NotFound() для возвращения клиенту ошибки 404. // Важно, чтобы мы завершили работу обработчика через return. Если мы забудем про "return", то обработчик // продолжит работу и выведет сообщение "Привет из SnippetBox" как ни в чем не бывало. if r.URL.Path != "/" { http.NotFound(w, r) return } w.Write([]byte("Привет из Snippetbox")) } ... |
Обновите функцию home
из файла main.go
, затем перезапустите веб-сервер и откройте несуществующую страницу вроде http://127.0.0.1:4000/menya-net
. Вы должны получить следующий ответ:
DefaultServeMux
Возможно, при работе с Go вы уже встречали функции http.Handle()
и http.HandleFunc()
. Они позволяют регистрацию маршрутов без объявления самого servemux, например:
1 2 3 4 5 6 7 8 9 |
func main() { http.HandleFunc("/", home) http.HandleFunc("/snippet", showSnippet) http.HandleFunc("/snippet/create", createSnippet) log.Println("Запуск веб-сервера на http://127.0.0.1:4000") err := http.ListenAndServe(":4000", nil) log.Fatal(err) } |
Где-то за кулисами, эти функции регистрируют свои маршруты с помощью так называемого DefaultServeMux. В этом нет ничего особенного — это обычный servemux, который мы уже использовали, но он инициализируется сам по умолчанию и сохраняется в глобальной переменной которая в свою очередь хранится в модуле net/http
. Вот как это выглядит в исходном коде пакета net/http
:
1 |
var DefaultServeMux = NewServeMux() |
Хотя такой подход может значительно уменьшить код (на целую одну строку!!!), мы не рекомендуем использовать DefaultServeMux для ваших приложений.
Поскольку DefaultServeMux
является глобальной переменной, любой пакет может получить к ней доступ и зарегистрировать маршрут — включая любые сторонние пакеты, которые использует ваше приложение. Если один из сторонних пакетов скомпрометирован, они могут манипулировать DefaultServeMux
, и ваше веб-приложение будет обрабатывать вредоносные HTTP запросы и гипотетически может быть создан backdoor для доступа к файлам на сервере.
В целях безопасности — рекомендуется избегать использование DefaultServeMux
и соответствующих вспомогательных функций. Вместо этого лучше используйте свой собственный servemux с локальной областью видимости, как мы делали до сих пор.
Особенности использования servemux
- В servemux, более длинные проверочные URL-шаблоны всегда обладают приоритетом перед более короткими. Таким образом, если servemux содержит несколько проверочных шаблонов, соответствующих HTTP запросу, он всегда отправит запрос обработчику с более детальным шаблоном проверки URL совпадения. Приятный побочный эффект заключается в том, что вы можете регистрировать шаблоны в любом порядке, и это не меняет поведения servemux;
- URL-пути запросов автоматически очищаются. Если путь запроса содержит такие символы как
.
или..
или повторяющиеся слэши, пользователь будет автоматически перенаправлен на эквивалентный чистый URL. Например, если пользователь делает запрос к/foo/bar/..//baz
, он автоматически получит ответ301 Permanent Redirect
и будет перенаправлен на/foo/baz
; - Если многоуровневый путь был зарегистрирован и HTTP запрос получен для этого пути без слэша в конце, то пользователю автоматически будет перенаправлен через
301 Permanent Redirect
на путь с добавленной косой чертой. Например, если вы зарегистрировали многоуровневый путь/foo/
, то любой запрос к/foo
будет перенаправлен на/foo/
.
Добавление домена в роутинг HTTP запросов
Можно включить названия домена в проверочный URL-шаблон. Это может быть полезно, когда требуется напрямую перенаправить все HTTP запросы каноничному URL, или если приложение действует как бэкенд для нескольких сайтов или сервисов на разных языках. В зависимости от суб-домена, меняется и язык сайта. К примеру:
1 2 3 4 |
mux := http.NewServeMux() mux.HandleFunc("ru.example.org/", fooHandler) mux.HandleFunc("en.example.org/", barHandler) mux.HandleFunc("/baz", bazHandler) |
Когда дело доходит до сопоставления с URL-шаблоном, сначала проверяются любые шаблоны где используется имя домена. В случае совпадения, запрос будет отправлен соответствующему обработчику. Только в том случае, если не найдено совпадений для конкретного домена, будут проверены шаблоны, которые не связаны с именем домена.
Что насчет REST маршрутизации?
Важно понимать, что функциональность маршрутизации запросов, предоставляемая от servemux в Go, довольно легкая. Она не поддерживает маршрутизацию на основе метода запроса (вроде разных обработчиков для POST и GET методов на один и тот же URL), динамические URL с переменными в них, а также не поддерживает шаблоны на основе регулярных выражений. Если у вас есть опыт использования таких фреймворков, как Rails, Django или Laravel, это может показаться немного ограничивающим… и неожиданным!
Пусть это вас не смущает. Реальность такова, что servemux в Go все еще может многое реализовать, и для большинства веб-приложений этого вполне достаточно. Если вам нужно больше возможностей, то существует огромный выбор маршрутизаторов на github вроде mux, httprouter или chi, которые можно использовать вместо встроенного servemux от Go. Мы рассмотрим некоторые популярные варианты в будущих уроках.
Финальный код на текущий момент
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
package main import ( "log" "net/http" ) // Обработчик главной странице. func home(w http.ResponseWriter, r *http.Request) { // Проверяется, если текущий путь URL запроса точно совпадает с шаблоном "/". Если нет, вызывается // функция http.NotFound() для возвращения клиенту ошибки 404. // Важно, чтобы мы завершили работу обработчика через return. Если мы забудем про "return", то обработчик // продолжит работу и выведет сообщение "Привет из SnippetBox" как ни в чем не бывало. if r.URL.Path != "/" { http.NotFound(w, r) return } w.Write([]byte("Привет из Snippetbox")) } // Обработчик для отображения содержимого заметки. func showSnippet(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Отображение заметки...")) } // Обработчик для создания новой заметки. func createSnippet(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Форма для создания новой заметки...")) } func main() { // Регистрируем два новых обработчика и соответствующие URL-шаблоны в // маршрутизаторе servemux mux := http.NewServeMux() mux.HandleFunc("/", home) mux.HandleFunc("/snippet", showSnippet) mux.HandleFunc("/snippet/create", createSnippet) log.Println("Запуск веб-сервера на http://127.0.0.1:4000") err := http.ListenAndServe(":4000", mux) log.Fatal(err) } |
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»