Pull to refresh

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

Reading time 5 min
Views 31K
Original author: Grisha Trubetskoy
Содержание

В этой части я попытаюсь кратко пройтись по пропущенным местам нашего очень упрощенного веб-приложения на 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, т.е. «мультиплексор», или «маршрутизатор», и его можно заменить нестандартной реализацией. Маршрутизатор также реализует метод ServeHTTP(), что означает, что он удовлетворяет интерфейсу http.Handler, то есть он является обработчиком.


Очень популярным вариантом маршрутизатора является Gorilla Mux. Ему можно делегировать целые пути в тех местах, где требуется больше гибкости. Например, мы можем решить, что все, от /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 и его племянник go-bindata-assetsfs.


Поскольку упаковка статики в двоичный файл несколько выходит за пределы того, что может сделать 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 с именем builtinassets. Мы также сообщаем go-bindata о нем, что означает «скомпилируй меня, только когда установлен builtinassets», а это позволяет контролировать, при каких условиях необходимо компилировать bindata.go (который содержит нашу статику в виде кода Go).


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


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


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


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


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


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

Заключение


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


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

Tags:
Hubs:
+12
Comments 77
Comments Comments 77

Articles