Показаны сообщения с ярлыком Gorilla mux. Показать все сообщения
Показаны сообщения с ярлыком Gorilla mux. Показать все сообщения

четверг, 25 июля 2019 г.

Пакет Gorilla mux: добавление промежуточного программного обеспечения (middleware)

Mux поддерживает добавление промежуточного программного обеспечения (middleware) к маршрутизатору (Router), который выполняется в порядке их добавления, если найдено совпадение, включая его подчиненные маршрутизаторы. Промежуточное программное обеспечение - это (как правило) небольшие куски кода, которые принимают один запрос, что-то с ним делают и передают другому промежуточному программному обеспечению или конечному обработчику. Некоторыми распространенными вариантами использования промежуточного программного обеспечения являются ведение журнала запросов, манипулирование заголовками или перехват ResponseWriter.

type MiddlewareFunc func(http.Handler) http.Handler

Как правило, возвращаемый обработчик является замыканием, которое делает что-то с переданными ему http.ResponseWriter и http.Request, а затем вызывает обработчик, переданный в качестве параметра в MiddlewareFunc (замыкания могут обращаться к переменным из контекста, в котором они созданы).

Простое базовое промежуточное программное обеспечение, которое регистрирует URI обрабатываемого запроса, может быть записано как:

func simpleMw(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Делаем работу здесь
        log.Println(r.RequestURI)
        // Вызов следующего обработчика, 
        // который может быть другим промежуточным программным 
        // обеспечением в цепочке или конечным обработчиком.
        next.ServeHTTP(w, r)
    })
}

Промежуточное программное обеспечение (middleware) может быть добавлено к маршрутизатору с помощью Router.Use():

r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(simpleMw)

Более сложное промежуточное программное обеспечение для аутентификации, которое сопоставляет токен сессии с пользователями, может быть записано как:

// Определим нашу структуру
type authenticationMiddleware struct {
    tokenUsers map[string]string
}

// Инициализируем где-нибудь
func (amw *authenticationMiddleware) Populate() {
    amw.tokenUsers["00000000"] = "user0"
    amw.tokenUsers["aaaaaaaa"] = "userA"
    amw.tokenUsers["05f717e5"] = "randomUser"
    amw.tokenUsers["deadbeef"] = "user0"
}

// Функция промежуточного программного обеспечения, 
// которая будет вызываться для каждого запроса
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("X-Session-Token")

        if user, found := amw.tokenUsers[token]; found {
            // Мы нашли токен в нашей карте
            log.Printf("Authenticated user %s\n", user)
            next.ServeHTTP(w, r)
        } else {
            http.Error(w, "Forbidden", http.StatusForbidden)
        }
    })
}

r := mux.NewRouter()
r.HandleFunc("/", handler)

amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
amw.Populate()

r.Use(amw.Middleware)

Примечание. Цепочка обработчиков будет остановлена, если ваше промежуточное программное обеспечение не вызовет next.ServeHTTP() с соответствующими параметрами. Это можно использовать для отмены запроса, если этого хочет автор промежуточного программного обеспечения.

Пример использования middleware с Gorilla mux:

package mux_test

import (
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

// Определим нашу структуру
type authenticationMiddleware struct {
    tokenUsers map[string]string
}
// Инициализируем где-нибудь
func (amw *authenticationMiddleware) Populate() {
    amw.tokenUsers["00000000"] = "user0"
    amw.tokenUsers["aaaaaaaa"] = "userA"
    amw.tokenUsers["05f717e5"] = "randomUser"
    amw.tokenUsers["deadbeef"] = "user0"
}

// Функция промежуточного программного обеспечения, которая будет вызываться для каждого запроса
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("X-Session-Token")

        if user, found := amw.tokenUsers[token]; found {
            // Мы нашли токен в нашей карте
            log.Printf("Authenticated user %s\n", user)
            next.ServeHTTP(w, r)
        } else {
            http.Error(w, "Forbidden", http.StatusForbidden)
        }
    })
}

func Example_authenticationMiddleware() {
    r := mux.NewRouter()
    r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // Делаем что-то здесь
    })
    amw := authenticationMiddleware{make(map[string]string)}
    amw.Populate()
    r.Use(amw.Middleware)
}


Читайте также:


Пакет Gorilla mux: построение зарегистрированных URL

В этом посте рассмотрим, как построить зарегистрированные URL с пакетом Gorilla mux.

Маршруты могут быть названы. Все маршруты, которые определяют имя, могут иметь свои построенные URL-адреса. Мы определяем имя, вызывая Name() на маршруте. Например:

r := mux.NewRouter()
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
  Name("article")

Чтобы построить URL, получите маршрут и вызовите метод URL(), передав последовательность пар ключ/значение для переменных маршрута. Для предыдущего маршрута мы бы сделали:

url, err := r.Get("article").URL("category", "technology", "id", "42")

... и результатом будет url.URL со следующим путем:

"/articles/technology/42"

Это также работает для переменных значения хоста и запроса:

r := mux.NewRouter()
r.Host("{subdomain}.domain.com").
  Path("/articles/{category}/{id:[0-9]+}").
  Queries("filter", "{filter}").
  HandlerFunc(ArticleHandler).
  Name("article")

// url.String() будет "http://news.domain.com/articles/technology/42?filter=gorilla"
url, err := r.Get("article").URL("subdomain", "news",
                                 "category", "technology",
                                 "id", "42",
                                 "filter", "gorilla")

Все переменные, определенные в маршруте, являются обязательными, и их значения должны соответствовать соответствующим шаблонам. Эти требования гарантируют, что сгенерированный URL всегда будет соответствовать зарегистрированному маршруту - единственное исключение - явно определенные маршруты "только для сборки" ("build-only"), которые никогда не совпадают.

Также существует поддержка регулярных выражений для сопоставления заголовков в маршруте. Например, мы могли бы сделать:

r.HeadersRegexp("Content-Type", "application/(text|json)")

... и маршрут будет соответствовать обоим запросам с Content-Type `application/json`, а также `application/text`

Существует также способ построения только хоста или пути URL для маршрута: для этого используйте методы URLHost() или URLPath(). Для предыдущего маршрута мы бы сделали:

// "http://news.domain.com/"
host, err := r.Get("article").URLHost("subdomain", "news")

// "/articles/technology/42"
path, err := r.Get("article").URLPath("category", "technology", "id", "42")

И если вы используете подчиненные маршрутизаторы (subrouters), хост и путь, определенные отдельно, также могут быть построены:

r := mux.NewRouter()
s := r.Host("{subdomain}.domain.com").Subrouter()
s.Path("/articles/{category}/{id:[0-9]+}").
  HandlerFunc(ArticleHandler).
  Name("article")

// "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
                                 "category", "technology",
                                 "id", "42")


Читайте также:


вторник, 23 июля 2019 г.

Пакет Gorilla mux: маршруты, субмаршруты

Маршруты также могут быть ограничены доменом или поддоменом. Просто определите шаблон хоста для сопоставления. Они также могут иметь переменные:

r := mux.NewRouter()
// Соответствует только если домен "www.example.com".
r.Host("www.example.com")
// Соответствует динамическому поддомену.
r.Host("{subdomain:[a-z]+}.domain.com")

Есть несколько других совпадений, которые можно добавить. Чтобы соответствовать префиксам пути:

r.PathPrefix("/products/")

... или методам HTTP:

r.Methods("GET", "POST")

... или схеме URL:

r.Schemes("https")

... или значению заголовка:

r.Headers("X-Requested-With", "XMLHttpRequest")

... или значению запроса:

r.Queries("key", "value")

... или использовать пользовательскую функцию соответствия:

r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
    return r.ProtoMajor == 0
})

... и, наконец, можно объединить несколько сопоставителей в одном маршруте:

r.HandleFunc("/products", ProductsHandler).
  Host("www.example.com").
  Methods("GET").
  Schemes("http")

Задавать одни и те же условия сопоставления снова и снова может быть скучно, поэтому есть способ сгруппировать несколько маршрутов с одинаковыми требованиями. Он называется "субмаршрутизацией" (subrouting).

Например, допустим, есть несколько URL-адресов, которые должны совпадать только в том случае, если хостом является "www.example.com". Создайте маршрут для этого хоста и получите от него "субмаршрутизатор" ("subrouter"):

r := mux.NewRouter()
s := r.Host("www.example.com").Subrouter()

Затем зарегистрируйте маршруты в субмаршрутизаторе(subrouter):

s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)

Три URL-пути, которые мы зарегистрировали выше, будут проверяться только в том случае, если домен является "www.example.com", поскольку сначала проверяется подчиненный маршрутизатор (subrouter). Это не только удобно, но и оптимизирует сопоставление запросов. Вы можете создавать субмаршрутизаторы (subrouters), комбинирующие любые сопоставления атрибутов, принятые маршрутом (route).

Субмаршрутизаторы (Subrouters) могут использоваться для создания "пространств имен" ("namespaces") домена или пути: вы определяете субмаршрутизаторы в одном месте, а затем части приложения могут регистрировать свои пути относительно данного субмаршрутизатора.

Есть еще одна вещь о субмаршрутах (subroutes). Когда у подчиненного маршрутизатора (subrouter) есть префикс пути, внутренние маршруты используют его в качестве основы для своих путей:

r := mux.NewRouter()
s := r.PathPrefix("/products").Subrouter()

// "/products/"
s.HandleFunc("/", ProductsHandler)

// "/products/{key}/"
s.HandleFunc("/{key}/", ProductHandler)

// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)

Обратите внимание, что путь к PathPrefix() представляет собой "подстановочный знак" ("wildcard"): вызов PathPrefix("/static/").Handler(...) означает, что обработчику будет передан любой запрос, соответствующий "/static/*". Это позволяет легко обслуживать статические файлы с помощью mux:

func main() {
    var dir string

    flag.StringVar(&dir, "dir", ".", "каталог для обслуживания файлов. По умолчанию текущий каталог")
    flag.Parse()
    r := mux.NewRouter()

    // Это будет обслуживать файлы по адресу 
    // http://localhost:8000/static/
    r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))

    srv := &http.Server{
        Handler:      r,
        Addr:         "127.0.0.1:8000",
        // Хорошая практика: 
        // применять таймауты для серверов, 
        // которые вы создаете!
        WriteTimeout: 15 * time.Second,
        ReadTimeout:  15 * time.Second,
    }

    log.Fatal(srv.ListenAndServe())
}


Читайте также:


понедельник, 22 июля 2019 г.

Пакет Gorilla mux для обработки веб-запросов

Gorilla это набор инструментов для веб-программирования (web toolkit).

Пакет Gorilla mux реализует маршрутизатор запросов (request router) и диспетчер.

Установка

$ go get github.com/gorilla/mux

Импорт

import "github.com/gorilla/mux"

Использование

Название mux означает "мультиплексор HTTP-запросов". Как и стандартный http.ServeMux, mux.Router сопоставляет входящие запросы со списком зарегистрированных маршрутов и вызывает обработчик для маршрута, который соответствует URL-адресу или другим условиям. Основными функциями являются:

1. Запросы могут быть сопоставлены на основе URL хоста, пути, префикса пути, схем, значения заголовка и запроса, метода HTTP или использование пользовательских сопоставлений.

2. У URL хостов, путей и значений запроса могут быть переменные с необязательным регулярным выражением.

3. Зарегистрированные URL-адреса могут быть построены или "перевернуты", что помогает поддерживать ссылки на ресурсы.

4. Маршруты можно использовать в качестве подчиненных маршрутов: вложенные маршруты проверяются только в том случае, если родительский маршрут совпадает. Это полезно для определения групп маршрутов, которые имеют общие условия, такие как хост, префикс пути или другие повторяющиеся атрибуты. В качестве бонуса это оптимизирует сопоставление запросов.

5. Реализует интерфейс http.Handler, поэтому совместим со стандартным http.ServeMux.

Зарегистрируем пару URL-путей и обработчиков:

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/", HomeHandler)
    r.HandleFunc("/products", ProductsHandler)
    r.HandleFunc("/articles", ArticlesHandler)
    http.Handle("/", r)
}

Здесь мы регистрируем три маршрута, отображающие URL-пути к обработчикам. Это эквивалентно тому, как работает http.HandleFunc(): если URL входящего запроса совпадает с одним из путей, соответствующий обработчик называется передачей (http.ResponseWriter, *http.Request) в качестве параметров.

Пути могут иметь переменные. Они определяются в формате {имя} или {имя:шаблон}. Если шаблон регулярного выражения не определен, соответствующая переменная будет иметь значение до следующей косой черты. Например:

r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)

Группы могут использоваться внутри шаблонов, если они не фиксируются (?:re). Например:

r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)

Имена используются для создания карты переменных маршрута, которые можно получить с помощью mux.Vars():

vars := mux.Vars(request)
category := vars["category"]

Обратите внимание, что если присутствуют какие-либо группы захвата, mux будет вызывать panic() во время анализа. Чтобы предотвратить это, преобразуйте любые группы захвата в не-захваты, например, замените "/{sort:(asc|desc)}" на "/{sort:(?:asc|desc)}". Это отличие от предыдущих версий, которые вели себя непредсказуемо, когда присутствовали группы захвата.


Читайте также: