- PVSM.RU - https://www.pvsm.ru -

Создание веб-приложения на Go в 2017 году. Часть 4

Содержание

  1. Часть 1 [1]
  2. Часть 2 [2]
  3. Часть 3 [3]
  4. Часть 4

В этой части я попытаюсь кратко пройтись по пропущенным местам нашего очень упрощенного веб-приложения на Go.

Обертки обработчиков HTTP

Немного поворчу: мне не нравится слово “middleware”. Концепция обертки существует с начала вычислений, поэтому не вижу необходимости изобретать для нее новые слова.

Но отбросим это в сторону, допустим, нам потребовалась аутентификация для определенного URL. Сейчас наш обработчик главной страницы выглядит так:

func indexHandler(m *model.Model) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, indexHTML)
    })
}

Мы можем написать функцию, которая принимает http.Handler в качестве аргумента и возвращает (другой) http.Handler. Возвращенный обработчик проверяет, аутентифицирован ли пользователь, с помощью m.IsAuthenticated() (неважно, что конкретно там происходит) и перенаправляет пользователя на страницу входа или выполняет оригинальный обработчик, вызывая его метод ServeHTTP().

func requireLogin(h http.Handler, m *model.Model) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !m.IsAuthenticated(r) {
            http.Redirect(w, r, loginURL, http.StatusFound)
            return
        }
        h.ServeHTTP(w, r)
    })
}

С учетом этого, регистрация обработчика теперь будет выглядеть так:

   http.Handle("/", requireLogin(indexHandler(m), m))

Таким способом можно обернуть обработчики в любое необходимое количество слоев и этот очень гибкий подход. Все, начиная от установки заголовков до сжатия вывода, может быть выполнено с помощью обертки. Также отмечу, что мы можем передавать любые нужные нам аргументы, например, нашу *model.Model.

Параметры в URL

В какой-то момент нам могут понадобиться параметры в URL, например, /Person/3, где 3 — это идентификатор человека. Стандартная библиотека Go ничего для этого не предоставляет, оставляя эту задачу в качестве упражнения для разработчика. Программный компонент, ответственный за такую штуку, называется Mux [4], т.е. «мультиплексор», или «маршрутизатор», и его можно заменить нестандартной реализацией. Маршрутизатор также реализует метод ServeHTTP(), что означает, что он удовлетворяет интерфейсу http.Handler, то есть он является обработчиком.

Очень популярным вариантом маршрутизатора является Gorilla Mux [5]. Ему можно делегировать целые пути в тех местах, где требуется больше гибкости. Например, мы можем решить, что все, от /person и ниже, обрабатывается маршрутизатором Gorilla, и мы хотим, чтобы все это еще было аутентифицировано, тогда это может выглядеть так:

    // import "github.com/gorilla/mux"
    pr := mux.NewRouter().PathPrefix("/person").Subrouter()
    pr.Handle("/{id}", personGetHandler(m)).Methods("GET")
    pr.Handle("/", personPostHandler(m)).Methods("POST")
    pr.Handle("/{id}", personPutHandler(m)).Methods("PUT")
    http.Handle("/person/", requireLogin(pr))

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

Есть еще много других реализаций маршрутизатора/мультиплексора. Прелесть в том, что не привязываясь ни к какому фреймворку, мы можем выбрать тот маршрутизатор, который лучше всего нам подходит, или написать свой собственный (их нетрудно реализовать).

Обработка статики

Одна из самых изящных вещей в Go — это то, что скомпилированная программа представляет собой единственный двоичный файл, а не большую кучу файлов, как это часто бывает с большинством скриптовых языков и даже с некоторыми компилируемыми. Но если наша программа зависит от статических файлов (JS, CSS, изображений и других файлов), нам нужно будет скопировать и их на сервер во время развертывания.

Мы можем сохранить эту характерную особенность — «один бинарник» — нашей программы, включив статику как часть самого двоичного файла. Для этого существует проект go-bindata [6] и его племянник go-bindata-assetsfs [7].

Поскольку упаковка статики в двоичный файл несколько выходит за пределы того, что может сделать go build, нам понадобится какой-то скрипт, который об этом позаботится. Лично я предпочитаю использовать проверенный и "трушный" make, и не так уж редко можно встретиться с «Makefile» в проекте Go.

Вот пример подходящего правила Makefile:

ASSETS_DIR = "assets"
build:
    @export GOPATH=$${GOPATH-~/go} && 
    go get github.com/jteeuwen/go-bindata/... github.com/elazarl/go-bindata-assetfs/... && 
    $$GOPATH/bin/go-bindata -o bindata.go -tags builtinassets ${ASSETS_DIR}/... && 
    go build -tags builtinassets -ldflags "-X main.builtinAssets=${ASSETS_DIR}"

Это правило создает файл bindata.go, который помещается в тот же каталог, где находится main.go, соответственно, он становится частью пакета main. main.go как-то узнает, что статические файлы встроены, — это получается с помощью трюка -ldflags "-X main.builtinAssets=${ASSETS_DIR}", так мы можем присваивать значения переменным на этапе компиляции. Это значит, что теперь наш код может проверять значение builtinAssets, чтобы решить, что делать дальше, например:

    if builtinAssets != "" {
        log.Printf("Running with builtin assets.")
        cfg.UI.Assets = &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: builtinAssets}
    } else {
        log.Printf("Assets served from %q.", assetsPath)
        cfg.UI.Assets = http.Dir(assetsPath)
    }

Вторая важная вещь заключается в том, что мы определяем build tag [8] с именем builtinassets. Мы также сообщаем go-bindata о нем, что означает «скомпилируй меня, только когда установлен builtinassets», а это позволяет контролировать, при каких условиях необходимо компилировать bindata.go (который содержит нашу статику в виде кода Go).

Предварительная транспиляция JavaScript

Напоследок (по порядку, а не по важности), я хочу кратко упомянуть упаковку веб-статики. Для описания этого должным образом материала достаточно для целой новой серии статей, и это не имело бы ничего общего с Go. Но я могу хотя бы перечислить некоторые моменты.

  • В принципе, вы можете пойти на уступки — установить npm [9] и настроить файл package.json.

  • Как только npm установлен, банально устанавливается компилятор командной строки Babel — babel-cli, который является одним из способов транспиляции JavaScript.

  • Более сложный, в чем-то разочаровывающий, но в конечном счете более гибкий способ — использовать webpack [10]. Webpack будет не только претранспиливать JS-код, но и объединит все JS-файлы в один, а также минимизирует его.

  • Я был удивлен тому, насколько сложно было обеспечить функциональность импорта модулей в JavaScript. Проблема в том, что есть стандарт ES6 для ключевых слов import и export, но нет реализации, и даже Babel предполагает, что реализовывать их для вас будет кто-нибудь другой. В конце концов, я остановился на SystemJS [11]. Некоторое осложнение с SystemJS заключается в том, что внутрибраузерная транспиляция Babel должна быть чем-то, понятным для SystemJS, поэтому мне пришлось использовать его плагин для Babel. Webpack, в свою очередь (если я правильно понял), предоставляет свою собственную реализацию поддержки модулей, поэтому при упаковке SystemJS не нужен. В любом случае, это было довольно неприятно.

Заключение

Я бы сказал, что в примере, который я описываю в этой серии из четырех частей, Go безусловно блистает, а вот JavaScript — не особо. Но как только я преодолел начальные трудности с тем, чтобы заставить все это заработать, React/JSX оказался простым и, возможно, даже приятным для работы.

На этом я, пожалуй, закончу. Надеюсь, статьи оказались вам полезными.

Автор: Сергей Соломеин

Источник [12]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/golang/256366

Ссылки в тексте:

[1] Часть 1: https://habrahabr.ru/post/329582/

[2] Часть 2: https://habrahabr.ru/post/329584/

[3] Часть 3: https://habrahabr.ru/post/329612/

[4] Mux: https://golang.org/pkg/net/http/#ServeMux

[5] Gorilla Mux: https://github.com/gorilla/mux

[6] go-bindata: https://github.com/jteeuwen/go-bindata/

[7] go-bindata-assetsfs: https://github.com/elazarl/go-bindata-assetfs

[8] build tag: https://dave.cheney.net/2013/10/12/how-to-use-condition-compilation-with-the-go-build-tool

[9] npm: https://www.npmjs.com/

[10] webpack: https://webpack.github.io/

[11] SystemJS: https://github.com/systemjs/systemjs

[12] Источник: https://habrahabr.ru/post/329622/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best