Когда вы начинаете разрабатывать веб-приложение с применением 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 и заботится о коде, я предполагаю, что в какой-то момент за последние пару лет он обновил бы репозиторий для использования модулей.

Репозиторий

Причина

celrenheit/lion

В настоящее время не поддерживается.

claygod/Bxog

В настоящее время не поддерживается.

clevergo/clevergo

Использует пользовательскую сигнатуру обработчика (не http.Handler или http.HandlerFunc).

dimfeld/httptreemux

Не полностью поддерживает http.Handler. Требуется middleware для настройки пользовательских обработчиков 404/405.

donutloop/mux

В настоящее время не поддерживается.

gernest/alien

В настоящее время не поддерживается.

go-ozzo/ozzo-routing

Использует пользовательскую сигнатуру обработчика (не http.Handler или http.HandlerFunc).

go-playground/lars

В настоящее время не поддерживается.

go-zoo/bone

Хорош, но по сути представляет собой урезанную версию chi. Неполные тесты.

go101/tinyrouter

Избыточное объявление маршрутов. Не отправляет автоматически ответы 405.

gocraft/web

В настоящее время не поддерживается.

goji/goji

Немного необычный, но гибкий API, поддерживающий пользовательские сопоставители. Требуется middleware для пользовательских обработчиков 404/405. Хорош, но я думаю, что gorilla/mux предлагает аналогичные функции и проще в использовании.

goroute/route

Использует собственную сигнатуру обработчика (не http.Handler или http.HandlerFunc).

gowww/router

Хорош, но по сути представляет собой урезанную версию chi. Невозможно реализовать собственный обработчик 405.

GuilhermeCaruso/bellt

Невозможно реализовать собственные обработчики 404 или 405.

husobee/vestigo

В настоящее время не поддерживается. Поддерживает только http.HandlerFunc.

naoina/denco

В настоящее время не поддерживается.

nbari/violetear

Хорош, но по сути представляет собой урезанную версию chi. Оборачивает http.ResponseWriter собственным пользовательским типом, что в некоторых случаях может вызывать проблемы.

nbio/hitch

Отсутствует документация.

nissy/bon

В настоящее время не поддерживается.

razonyang/fastrouter

В настоящее время не поддерживается.

rs/xmux

В настоящее время не поддерживается. Использует собственную сигнатуру обработчика (не http.Handler или http.HandlerFunc).

takama/router

В настоящее время не поддерживается.

vardius/gorouter

Хорош, но по сути представляет собой урезанную версию chi. Четыре крупных обновления за 5 лет намекают, что API может быть ненадежным.

VividCortex/siesta

Хорош, но по сути представляет собой урезанную версию chi. Невозможно реализовать собственный обработчик 405.

xujiajun/gorouter

В настоящее время не поддерживается.

Перевод материала подготовлен в преддверии старта курса "Golang Developer. Professional".

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


  1. gohrytt
    23.02.2022 02:39
    +2

    Фастхттп и производные как всегда игнорим, ну да, ну да


  1. GoodGod
    23.02.2022 14:42
    +1

    Статья устарела. gorilla/mux уже год нет релизов, и пакет ищет мейнтейнера.

    go-chi/chi наоборот сильно вырос, и

    Как и httprouter, он также не поддерживает маршруты на основе хоста.

    уже поддерживает маршруты на основе хоста.

    Пакет gorilla/mux, пожалуй, самый известный Go роутер, и на то есть веские причины.

    Может и самый известный, но chi его уже сильно обогнал по возможностям. Если посмотреть структуру папок и полазить в коде, то увидим, что chi имеет гораздо больше возможностей чем mux.

    Я не претендую, что chi - это то что нужно выбрать, т.к. есть и навороченнее типа gin есть и быстрее и легче.