Когда вы начинаете разрабатывать веб-приложение с применением Go, один из первых вопросов, которым вы, вероятно, зададитесь, — “Какой роутер мне следует использовать?”.
И это не такой простой вопрос, как может показаться сначала. Доступно более 100 различных роутеров с разными API, функциями и поведением. Для этой статьи я оценил 30 самых популярных из них и создал краткий список лучших вариантов вместе с блок-схемой, которую вы можете использовать, чтобы сделать свой выбор.
Примечание: Если вам не интересно читать все это, то вы смело можете переходить к блок-схеме.
Прежде чем мы начнем, давайте обговорим некоторые моменты касательно терминологии:
Под поддержкой маршрутизации на основе методов (method-based routing) я имею в виду, что роутер упрощает отправку HTTP-запроса различным обработчикам на основе метода запроса ("GET", "POST" и т. д.).
Под поддержкой переменных в URL-путях (variables in URL paths) я подразумеваю, что роутер упрощает объявление маршрутов наподобие
/movies/{id}
, где{id}
— динамическое значение в URL-пути.
Под поддержкой шаблонов маршрутов на основе регулярных выражений (regexp route patterns) я подразумеваю, что роутер упрощает объявление маршрутов наподобие
/movies/{[az-]+}
где[az-]+
— это необходимое совпадение с шаблоном регулярного выражения в URL-пути.
Под поддержкой маршрутов на основе хоста (host-based routes) я подразумеваю, что роутер позволяет отправку HTTP-запросов различным обработчикам на основе хоста URL (вроде www.example.com), а не только по URL-пути.
Под поддержкой пользовательских правил маршрутизации (custom routing rules) я подразумеваю, что роутер упрощает добавление настраиваемых правил для запросов маршрутизации (таких как маршрутизация к различным обработчикам на основе IP-адреса или значения в заголовке Authorization).
Под конфликтующими маршрутами (conflicting routes) я подразумеваю ситуацию, когда вы регистрируете два (или более) маршрута, которые потенциально соответствуют одному и тому же URL-пути запроса. Например, если вы зарегистрируете маршруты
/blog/{slug}
и/blog/new
, то HTTP-запрос с путем/blog/new
будет соответствовать обоим этим маршрутам.
Примечание: С точки зрения разработки программного обеспечения конфликтующие маршруты — это плохо. Они могут быть источником ошибок и путаницы, и вам обычно следует стараться избегать их в своих приложениях.
Роутеры, попавшие в окончательный список
По итогам моего отбора в окончательный список вошли четыре роутера. Это http.ServeMux, julienschmidt/httprouter, go-chi/chi и gorilla/mux. Все четыре хорошо протестированы, задокументированы и активно поддерживаются. Они могут похвастаться стабильными (в основном) API и совместимы с http.Handler, http.HandlerFunc и стандартным шаблоном middleware.
Что касается скорости, то все четыре роутера достаточно быстры (почти) в каждом варианте их использования, и я рекомендую делать выбор между ними, основываясь на конкретных функциях, которые вам нужны, а не на производительности. Лично я использовал все четыре в приложениях, над которыми работал в разное время, и был ими всеми очень доволен.
http.ServeMux
Начну я с того, что если у вас есть возможность использовать http.ServeMux, то, вероятнее всего, именно его вам и следует выбрать.
Как часть стандартной библиотеки Go, он очень хорошо задокументирован и протестирован в боевых условиях. Его использование означает, что вам не нужно импортировать какие-либо сторонние зависимости, и большинство других разработчиков Go также будут знакомы с тем, как он работает. Гарантия совместимости с Go 1 также означает, что вы можете рассчитывать на то, что в долгосрочной перспективе http.ServeMux будет работать точно так же, как и сейчас. Все эти моменты являются очень большими плюсами с точки зрения обслуживаемости приложений.
В отличие от большинства других роутеров, он также поддерживает маршруты на основе хоста, URL входящих запросов автоматически санируются, а способ сопоставления маршрутов интеллектуален: более длинные шаблоны маршрутов всегда имеют приоритет над более короткими. У этого есть приятный побочный эффект: вы можете регистрировать шаблоны в любом порядке, и это не изменит поведение вашего приложения.
Два основных ограничения http.ServeMux заключаются в том, что он не поддерживает маршрутизацию на основе методов или переменные в URL-путях. Но отсутствие поддержки маршрутизации на основе методов не всегда является такой уж веской причиной, чтобы отказываться от этого роутера — ее довольно легко обойти с помощью такого кода:
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", index)
err := http.ListenAndServe(":3000", mux)
log.Fatal(err)
}
func index(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
// Здесь может быть общий код для всех запросов...
switch r.Method {
case http.MethodGet:
// Обработка GET запроса...
case http.MethodPost:
// Обработка POST запроса...
case http.MethodOptions:
w.Header().Set("Allow", "GET, POST, OPTIONS")
w.WriteHeader(http.StatusNoContent)
default:
w.Header().Set("Allow", "GET, POST, OPTIONS")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
В этих нескольких строках вы фактически получили маршрутизацию на основе методов, а также собственные ответы 404 и 405 и поддержку OPTIONS запросов. И это намного больше, чем могут предложить большинство сторонних роутеров.
Со временем я понял, что http.ServeMux имеет много положительных сторон и во многих случаях его вполне достаточно. Фактически, единственный раз, когда я бы рекомендовал не использовать его, — это когда вам нужна поддержка переменных в путях URL или пользовательских правилах маршрутизации. В этих случаях попытка работать с http.ServeMux может обернуться для вас некоторыми сложностями, и я думаю, что в целом лучше выбрать какой-нибудь сторонний роутер.
julienschmidt/httprouter
Я думаю, что julienschmidt/httprouter настолько близок к “идеальному выбору”, насколько любой сторонний роутер может быть с точки зрения его поведения и соответствия спецификациям HTTP. Он поддерживает маршрутизацию на основе методов и динамические URL-адреса. Он также автоматически обрабатывает OPTIONS запросы и корректно отправляет 405, а также позволяет устанавливать собственные обработчики для ответов 404 и 405.
Он не поддерживает маршруты на основе хоста, пользовательские правила маршрутизации или шаблоны маршрутов на основе регулярных выражений. Также важно отметить, что httprouter не допускает конфликтующих маршрутов, таких как /post/create и /post/:id. Объективно это хорошо, потому что помогает избежать ошибок, но может быть проблемой, если вам нужно использовать конфликтующие маршруты (например, для единообразия с маршрутами, используемыми существующей системой).
Одним из недостатков httprouter является то, что его API и документация далеки от идеала. Пакет был впервые опубликован до введения контекста запроса в Go 1.7, и многие актуальные API все еще живут в реалиях старых версий Go. В наши дни вы можете писать свои обработчики, используя обычные сигнатуры http.Handler и http.HandlerFunc, и все, что вам нужно, это методы router.Handler()
и router.HandlerFunc()
для их регистрации. Например:
func main() {
router := httprouter.New()
router.HandlerFunc("GET", "/", indexGet)
router.HandlerFunc("POST", "/", indexPost)
err := http.ListenAndServe(":3000", mux)
log.Fatal(err)
}
func indexGet(w http.ResponseWriter, r *http.Request) {
// Обработка GET запроса...
}
func indexPost(w http.ResponseWriter, r *http.Request) {
// Обработка POST запроса...
}
go-chi/chi
Пакет go-chi/chi поддерживает маршрутизацию на основе методов, переменные в URL-путях и шаблоны маршрутов на основе регулярных выражений. Как и httprouter, он также позволяет вам устанавливать собственные обработчики ответов 404 и 405.
Но что мне больше всего нравится в chi
, так это то, что вы можете создавать “группы” маршрутов, которые используют определенное middleware, как показано в фрагменте кода ниже. Это очень полезно в больших приложениях, где у вас есть много middleware и маршрутов, которыми вам нужно оперировать.
r := chi.NewRouter()
// Middleware используемое на всех маршрутах
r.Use(exampleMiddlewareOne)
r.Use(exampleMiddlewareTwo)
r.Get("/one", exampleHandlerOne)
r.Group(func(r chi.Router) {
// Middleware используемое только в этой группе маршрутов
r.Use(exampleMiddlewareThree)
r.Get("/two", exampleHandlerTwo)
})
chi
разрешает конфликтующие маршруты, и при этом маршруты сопоставляются в том порядке, в котором они объявлены.
Два недостатка chi заключаются в том, что он не обрабатывает OPTIONS запросы и не устанавливает заголовок Allow в ответах 405. Если вы создаете веб-приложение или приватный API, то эти вещи, вероятно, не представляют большой проблемы, но если вы работаете над публичным API, то это нужно иметь ввиду. Как и httprouter, он также не поддерживает маршруты на основе хоста.
В качестве предостережения: за последние 6 лет было 5 крупных обновлений chi
, большинство из которых содержали критические изменения. История не обязательно предсказывает будущее, но намекает, что обратная совместимость и отказ от внесения критических изменений являются менее приоритетными для chi
, чем для некоторых других роутеров в этом списке. В отличие от них, httprouter и gorilla/mux за это время не вносили критических изменений.
gorilla/mux
Пакет gorilla/mux, пожалуй, самый известный Go роутер, и на то есть веские причины. Он содержит множество функций, включая поддержку маршрутизации на основе методов, динамических URL-адресов, шаблонов маршрутов на основе регулярных выражений и маршрутизации на основе хоста. Важно отметить, что это единственный роутер в этом списке, который поддерживает пользовательские правила маршрутизации и “реверсирование” маршрута (route reversing - как в Django, Rails или Laravel). Он также позволяет вам устанавливать собственные обработчики для ответов 404 и 405.
Его недостатки в основном такие же, как у chi
— он не обрабатывает OPTIONS запросы и не включает заголовок Allow в 405 ответы. Опять же, как и chi
, он разрешает конфликтующие маршруты, при этом маршруты сопоставляются в том порядке, в котором они объявлены.
Учитывая, что недостатки chi
и gorilla/mux схожи, выбор между ними довольно прост: используйте gorilla/mux , если вам нужна поддержка настраиваемых правил маршрутизации, маршрутизация на основе хоста или “реверсирование” маршрута. Если вам не нужны эти “расширенные” функции, то, вероятно, вам лучше выбрать chi
из-за приятных функций, которые вы получаете для управления middleware, особенно если вы создаете большое приложение.
Мой “приз зрительских симпатий”
Два других роутера, которые, как я думаю, заслуживают упоминания, это bmizerany/pat и matryer/way. У меня есть некоторая симпатия к обоим из них, потому что они намеренно простые. У них небольшие API и очень четкий и понятный код, что позволяет легко понять, как именно роутер работает под капотом. Очень рекомендую ознакомиться с кодом, лежащим в основе matryer/way.
Хотя они менее функциональны, чем другие роутеры в моем списке, я думаю, что их простота делает их подходящими вариантами для использования в туториалах (особенно туториалах, предназначенных для новоиспеченных гоферов) или в качестве отправной точки/вдохновения, если вы хотите написать свой собственный роутер.
Блок-схема
Учитывая различные плюсы и минусы, а также поддерживаемые функции, эта блок-схема должна помочь вам выбрать один из четырех роутеров, включенных в окончательный список.
Остальные роутеры
Остальные роутеры, которые я оценивал, перечислены ниже вместе с кратким примечанием, объясняющим, почему они не попали в окончательный список.
Примечание: я использовал вопрос “содержит ли репозиторий файл go.mod ?” в качестве репрезентативного показателя того, поддерживается ли кодовая база в настоящее время или нет. Мне это кажется вполне разумным — если мейнтейнер все еще вовлечен в мир Go и заботится о коде, я предполагаю, что в какой-то момент за последние пару лет он обновил бы репозиторий для использования модулей.
Репозиторий |
Причина |
В настоящее время не поддерживается. |
|
В настоящее время не поддерживается. |
|
Использует пользовательскую сигнатуру обработчика (не http.Handler или http.HandlerFunc). |
|
Не полностью поддерживает http.Handler. Требуется middleware для настройки пользовательских обработчиков 404/405. |
|
В настоящее время не поддерживается. |
|
В настоящее время не поддерживается. |
|
Использует пользовательскую сигнатуру обработчика (не http.Handler или http.HandlerFunc). |
|
В настоящее время не поддерживается. |
|
Хорош, но по сути представляет собой урезанную версию chi. Неполные тесты. |
|
Избыточное объявление маршрутов. Не отправляет автоматически ответы 405. |
|
В настоящее время не поддерживается. |
|
Немного необычный, но гибкий API, поддерживающий пользовательские сопоставители. Требуется middleware для пользовательских обработчиков 404/405. Хорош, но я думаю, что gorilla/mux предлагает аналогичные функции и проще в использовании. |
|
Использует собственную сигнатуру обработчика (не http.Handler или http.HandlerFunc). |
|
Хорош, но по сути представляет собой урезанную версию chi. Невозможно реализовать собственный обработчик 405. |
|
Невозможно реализовать собственные обработчики 404 или 405. |
|
В настоящее время не поддерживается. Поддерживает только http.HandlerFunc. |
|
В настоящее время не поддерживается. |
|
Хорош, но по сути представляет собой урезанную версию chi. Оборачивает http.ResponseWriter собственным пользовательским типом, что в некоторых случаях может вызывать проблемы. |
|
Отсутствует документация. |
|
В настоящее время не поддерживается. |
|
В настоящее время не поддерживается. |
|
В настоящее время не поддерживается. Использует собственную сигнатуру обработчика (не http.Handler или http.HandlerFunc). |
|
В настоящее время не поддерживается. |
|
Хорош, но по сути представляет собой урезанную версию chi. Четыре крупных обновления за 5 лет намекают, что API может быть ненадежным. |
|
Хорош, но по сути представляет собой урезанную версию chi. Невозможно реализовать собственный обработчик 405. |
|
В настоящее время не поддерживается. |
Перевод материала подготовлен в преддверии старта курса "Golang Developer. Professional".
Комментарии (2)
GoodGod
23.02.2022 14:42+1Статья устарела. gorilla/mux уже год нет релизов, и пакет ищет мейнтейнера.
go-chi/chi наоборот сильно вырос, и
Как и httprouter, он также не поддерживает маршруты на основе хоста.
уже поддерживает маршруты на основе хоста.
Пакет gorilla/mux, пожалуй, самый известный Go роутер, и на то есть веские причины.
Может и самый известный, но chi его уже сильно обогнал по возможностям. Если посмотреть структуру папок и полазить в коде, то увидим, что chi имеет гораздо больше возможностей чем mux.
Я не претендую, что chi - это то что нужно выбрать, т.к. есть и навороченнее типа gin есть и быстрее и легче.
gohrytt
Фастхттп и производные как всегда игнорим, ну да, ну да