На прошлой неделе я занимался разработкой небольшого веб-приложения на Go с фреймворком Gin и у меня заняло порядочно времени на то, чтобы разобраться с языком шаблонов, используемых в нём. В этой небольшой заметке я разберу несколько примеров работы с шаблонами в Go, которые должны помочь вам в их понимании.
Перед тем, как начать, приведу настройки моего GOPATH:
$ echo $GOPATH
/Users/markneedham/projects/gocode
Проект с этими примерами располагается в папке src:
$ pwd
/Users/markneedham/projects/gocode/src/github.com/mneedham/golang-gin-templating-demo
Вначале установим Gin:
$ go get gopkg.in/gin-gonic/gin.v1
Он установится сюда:
$ ls -lh $GOPATH/src/gopkg.in
total 0
drwxr-xr-x 3 markneedham staff 102B 23 Dec 10:55 gin-gonic
Теперь создадим главную функцию для запуска нашего веб-приложения.
Сегодня мы рассмотрим как обрабатывать HTTP-запросы в Go и предотвращать автоматические переходы на 301, 302 и подобные перенаправления. Это может понадобиться для раскрытия сокращённых ссылок из twitter, buffer, bit.ly или маркетинговых рассылок. Или для проверки ваших утилит для генерации таких ссылок 😉 Демо-версию можно посмотреть тут.
Внимание: для дальнейшей работы понадобится go не старше версии 1.7, иначе вам будут недоступны некоторые важные функции пакета net/http. Узнать версию Go можно командой go version в терминале. Ответ должен быть примерно таким: go version go1.7.3 linux/amd64.
Создание HTTP запросов в Go
Посмотрим как можно создать простой HTTP запрос в Go и прочитать его код состояния:
StatusCode: 200
https://jonathanmh.com/
Обратите внимание, что в resp.Request.URL мы получили совсем не тот адрес, что передали методу Get. Это произошло потому, что я перенаправляю пользователей с адреса www.jonathanmh.com на jonathanmh.com. Go делает всё как положено и просто следует правилам. В большинстве случаев такое поведение мы и ожидаем, но только не в том, случае, когда хотим сделать что-то вроде утилиты для проверки перенаправлений! В этом случае мы хотим знать каждый шаг перенаправления и код состояния каждого запроса.
Создание HTTP запросов в Go без следования редиректам
Для того, чтобы не следовать автоматическим перенаправлениям, создадим свой экземпляр http.Client с методом проверки CheckRedirect. Это поможет нам возвращать код состояния и адрес до перенаправления.
StatusCode: 301
http://www.jonathanmh.com
Супер! Мы пока не получили URL перенаправления, но у нас есть код состояния, что уже неплохо. Итак, продолжим.
Перебор HTTP-перенаправлений в Go
Теперь рассмотрим как можно обойти все перенаправления для получения конечного адреса. Сперва нам нужно задать лимит для запросов (иначе нас смогут отправить в бесконечную петлю перенаправлений) и добавить условие окончания перебора.
Будем считать, что получив код состояния 200, наш обход будет закончен и мы получим финальный адрес.
// file: http-redir-loop.go
package main
import(
"fmt"
"net/http"
)
func main(){
myURL := "http://www.jonathanmh.com"
nextURL := myURL
var i int
for i < 100 {
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
} }
resp, err := client.Get(nextURL)
if err != nil {
fmt.Println(err)
}
fmt.Println("StatusCode:", resp.StatusCode)
fmt.Println(resp.Request.URL)
if resp.StatusCode == 200 {
fmt.Println("Done!")
break
} else {
nextURL = resp.Header.Get("Location")
}
}
}
Ответ:
Done!
Этот код практически повторяет предыдущий пример, однако здесь мы добавили пару переменных, myURL и nextURL. И в конце цикла, если состояние не 200 - OK, мы присваиваем переменной nextURL значение resp.Header.Get("Location") - это HTTP заголовок цели перенаправления. Вы можете отслеживать эти заголовки на вкладке Сеть (Network) в Инструментах разработчика (Developer Tools) в Chrome или Firefox, поставив галочку Preserve Log.
Вот и всё! Теперь вы знаете, как делать несколько прикольных штук с net/http в Go. Теперь вы можете обернуть этот код в http API, запустить программу на сервере, ну и, конечно же, добавить ей простенький интерфейс, вроде того, как я сделал тут.
Для дальнейшего изучения
Одной статьёй не охватить всего, поэтому рекомендую почитать следующие материалы по теме:
У нас пока не работают ссылки на топики из общего списка. Сейчас мы добавим обработчики клика и шаблон для вывода топика.
Настройка роутов
Мы можем создать роут для обработки запросов для топика подобно роуту из предыдущей части. Однако, мы должны учитывать, что хотя обработчик для всех топиков будет один, URL каждого топика должен быть уникальным. Gin позволяет это сделать с помощью передачи параметров в роут:
Этот роут будет обрабатывать соответствующие указанному пути запросы, а также хранить значение параметра, переданного в роут — article_id, который мы обработаем в обработчике роута. Обработчиком нашего роута будет функция getArticle.
Изменённый файл routes.go:
// routes.go
package main
func initializeRoutes() {
// обработчик главного роута
router.GET("/", showIndexPage)
// Обработчик GET-запросов на /article/view/некоторый_article_id
router.GET("/article/view/:article_id", getArticle)
}
Шаблоны
Для вывода топика нам нужно создать новый шаблон templates/article.html. Он будет создан так же, как шаблон index.html, но с небольшим отличием: вместо передачи в него переменной со списком топиков, мы будем передавать в него только один топик.
Посмотреть код шаблона article.html можно на Github.
Определяем требования к обработчику роутов юнит-тестами
Тест обработчика будет проверять выполнение следующих условий:
Обработчик отвечает статус-кодом HTTP 200,
Возвращаемый HTML содержит тег title, содержащий название полученного топика.
Код теста будет в функции TestArticleUnauthenticated в файле handlers.article_test.go. Вспомогательные функции мы разместим в файле common_test.go.
Создаём обработчик роута
Итак, что должен делать обработчик роута для топика — getArticle:
1. Получить ID топика для вывода
Для вывода нужного топика, мы должны получить его ID из контекста. Примерно так:
c.Param("article_id")
где c — это Контекст Gin, который передаётся параметром в любой обработчик при разработке с Gin.
2. Получить сам топик
Это можно сделать с помощью функции getArticleByID() из файла models.article.go:
article, err := getArticleByID(articleID)
Функция getArticleByID (в models.article.go) выглядит так:
func getArticleByID(id int) (*article, error) {
for _, a := range articleList {
if a.ID == id {
return &a, nil
}
}
return nil, errors.New("Article not found")
}
Эта функция считывает список топиков в цикле и возвращает топик, ID которого соответствует переданному ID. Если такого топика нет, она возвращает ошибку.
3. Обработать шаблон article.html, передав в него топик
Код ниже как раз делает это:
c.HTML(
// Зададим HTTP статус 200 (OK)
http.StatusOK,
// Используем шаблон article.html
"article.html",
// Передадим данные в шаблон
gin.H{
"title": article.Title,
"payload": article,
},
)
Обновлённый файл handlers.article.go будет таким:
// handlers.article.go
package main
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
func showIndexPage(c *gin.Context) {
articles := getAllArticles()
// Вызовем метод HTML из Контекста Gin для обработки шаблона
c.HTML(
// Зададим HTTP статус 200 (OK)
http.StatusOK,
// Используем шаблон index.html
"index.html",
// Передадим данные в шаблон
gin.H{
"title": "Home Page",
"payload": articles,
},
)
}
func getArticle(c *gin.Context) {
// Проверим валидность ID
if articleID, err := strconv.Atoi(c.Param("article_id")); err == nil {
// Проверим существование топика
if article, err := getArticleByID(articleID); err == nil {
// Вызовем метод HTML из Контекста Gin для обработки шаблона
c.HTML(
// Зададим HTTP статус 200 (OK)
http.StatusOK,
// Используем шаблон index.html
"article.html",
// Передадим данные в шаблон
gin.H{
"title": article.Title,
"payload": article,
},
)
} else {
// Если топика нет, прервём с ошибкой
c.AbortWithError(http.StatusNotFound, err)
}
} else {
// При некорректном ID в URL, прервём с ошибкой
c.AbortWithStatus(http.StatusNotFound)
}
}
Если сейчас собрать и запустить наше приложение, при открытии localhost:8080/article/view/1 в браузере оно будет выглядеть так:
Новые файлы, добавленные в этом разделе:
└── templates
└── article.html
Ответ в JSON/XML
В этом разделе мы немного перепишем приложение так, что оно, в зависимости от заголовков запроса, будет отвечать в формате HTML, JSON или XML.
Повторно используемые функции
До сих пор мы использовали метод HTML Контекста Gin для обработки шаблонов прямо из обработчика. Этот способ хорошо если мы всегда будем выводить только в формате HTML. Однако, если мы хотим менять формат ответа, к примеру, на основе какого-то параметра, мы должны переписать эту часть функции, чтобы она делала только валидацию данных и их получение, а выводом в шаблон будет заниматься другая функция в зависимости от формата вывода на основе заголовка Accept. Мы создадим эту функция в файле main.go и она будет общая для всех обработчиков.
В Gin в Контексте, переданном обработчику роута, есть поле Request. В этом поле есть Header, в котором содержатся все заголовки запроса. Для получения заголовка Accept мы можем использовать метод Get в Header, вот так:
// c - это Gin Context
c.Request.Header.Get("Accept")
Если заголовок: application/json, функция выводит JSON,
Если заголовок: application/xml, функция выводит XML, и
Если заголовок любой другой или вообще пустой, функция выводит HTML.
Полный код функции:
// Render one of HTML, JSON or CSV based on the 'Accept' header of the request
// If the header doesn't specify this, HTML is rendered, provided that
// the template name is present
func render(c *gin.Context, data gin.H, templateName string) {
switch c.Request.Header.Get("Accept") {
case "application/json":
// Respond with JSON
c.JSON(http.StatusOK, data["payload"])
case "application/xml":
// Respond with XML
c.XML(http.StatusOK, data["payload"])
default:
// Respond with HTML
c.HTML(http.StatusOK, templateName, data)
}
}
Изменяем требования к обработчику роутов
Так как мы теперь должны проверить ответ в JSON и XML если задан специальный заголовок, нам нужно добавить тесты в файл handlers.article_test.go для проверки этих условий:
Проверить, что приложение вернёт список топиков в формате JSON если заголовок Accept равен application/json
Проверить, что приложение вернёт список топиков в формате XML если заголовок Accept равен application/xml
Мы добавим соответствующие функции TestArticleListJSON и TestArticleXML.
Обновляем обработчики
Обработчик у нас уже полностью готов, нам нужно только изменить метод обработки c.HTML на соответствующий требуемому формату метод вывода.
К примеру, обработчик роута showIndexPage будет изменён с такого:
func showIndexPage(c *gin.Context) {
articles := getAllArticles()
// Call the HTML method of the Context to render a template
c.HTML(
// Set the HTTP status to 200 (OK)
http.StatusOK,
// Use the index.html template
"index.html",
// Pass the data that the page uses
gin.H{
"title": "Home Page",
"payload": articles,
},
)
}
на такой:
func showIndexPage(c *gin.Context) {
articles := getAllArticles()
// Call the render function with the name of the template to render
render(c, gin.H{
"title": "Home Page",
"payload": articles}, "index.html")
}
Получаем список топиков в формате JSON
Чтобы увидеть приложение в работе, соберём его и запустим. Затем выполним следующую команду:
curl -X GET -H "Accept: application/json" http://localhost:8080/
Мы использовали тесты для определения требований к обработчикам роутов и моделям, поэтому можем теперь запустить их и проверить, что всё работает как предполагалось. В директории проекта запустите следующую команду:
go test -v Результат должен быть примерно таким:
=== RUN TestShowIndexPageUnauthenticated
[GIN] 2016/06/14 - 19:07:26 | 200 | 183.315µs | | GET /
--- PASS: TestShowIndexPageUnauthenticated (0.00s)
=== RUN TestArticleUnauthenticated
[GIN] 2016/06/14 - 19:07:26 | 200 | 143.789µs | | GET /article/view/1
--- PASS: TestArticleUnauthenticated (0.00s)
=== RUN TestArticleListJSON
[GIN] 2016/06/14 - 19:07:26 | 200 | 51.087µs | | GET /
--- PASS: TestArticleListJSON (0.00s)
=== RUN TestArticleXML
[GIN] 2016/06/14 - 19:07:26 | 200 | 38.656µs | | GET /article/view/1
--- PASS: TestArticleXML (0.00s)
=== RUN TestGetAllArticles
--- PASS: TestGetAllArticles (0.00s)
=== RUN TestGetArticleByID
--- PASS: TestGetArticleByID (0.00s)
PASS
ok github.com/demo-apps/go-gin-app 0.084s
Как мы видим, эта команда запускает все написанные нами тесты и, в нашем случае, сообщает, что всё работает как положено. Если вы присмотритесь к выводу, то увидите, что Go также сделал и HTTP запросы для нас, проверив обработчики роутов.
Заключение
В этом цикле статей мы сделали приложение с помощью фреймворка Gin и постепенно добавили в него функционал. Мы написали тесты, чтобы наше приложение было надёжным, а также использовали методологию повторно используемого кода, чтобы сделать вывод в различные форматы без больших затрат времени.
Код приложения можно скачать в этом Github репозитории.
Gin очень прост для того, чтобы начать писать веб-приложения — вкупе со встроенной функциональностью Go, он легко позволяет строить высококачественные, хорошо покрытые тестами веб-приложения и микросервисы.
По материалам Building Go Web Applications and Microservices Using Gin
Итак, мне нужно, чтобы API висело на том же домене, что и одностраничник на Vue.js, а это значит, что мне предстоит либо разместить API на другом сервере и добавить балансировщик, либо использовать другой порт и добавить правило для nginx.
Этот пост — продолжение серии о том, как мы делали photographerexcuses.com.
Я выбрал Nginx для кое-каких поделий на PHP, а также потому, что он лёгкий и имеет самые удобные конфиги, которые я видел.
Чтобы увидеть полный конфиг, пролистайте до конца страницы.
Вот самая важная часть конфига, отвечающая за перенаправление на Go API:
location /api/excuse {
rewrite ^/api/item(/.*)$ $1 break;
proxy_pass http://127.0.0.1:4000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
}
После этого nginx будет оставлять каждый запрос на http://127.0.0.1:4000 нетронутым. И к этому адресу можно обращаться как к http://photographerexcuses.com/api/item. Другие запросы будут перенаправляться на 404 страницу, потому что у одностраничного приложения обычно есть только файл index.html.
А $1 в той же строке означает, что все запросы, типа /api/item/141, будут переданы так: http://127.0.0.1:4000/141. Это нам нужно для передачи в запрос определённого ID.
server {
listen 80; ## listen for ipv4
listen :80[::]:80 default ipv6only=on; ## listen for ipv6
Как задеплоить приложение на Go? Это один из вопросов, возникших у меня после того, как вдоволь наигрался с Go.
Если вы просто хотите чтобы оно где-то работало, вы навряд ли захотите изучать контейнерные системы, вот об этом как раз и поговорим.
Внимание: мы не обсуждаем здесь лучшие способы деплоя, а просто описываем самый лёгкий способ показать вашим друзьям то, что вы сделали на Go, без изучения таких сервисов как Hyper, Heroku или систем типа Docker.
Этот пост — продолжение серии о том, как мы делали photographerexcuses.com.
Настройка Linux VPS
Для меня самый простой способ деплоя веб-приложения — настроить VPS. Он как мой компьютер, только всегда включен.
На сервере нам нужно создать директорию для проекта и создать пользователя для доступа в неё. В принципе, можно использовать домашнюю директорию.
mkdir project_name
Выкладываем бинарник
Если вы настроили ключи ssh, то можно воспользоваться Rsync для копирования бинарника с локальной машины на сервер:
#!/bin/bash
go build main.go
rsync -avz -e ssh main user@SERVER_IP:/home/jonathan/project_name
Затем соединимся с VPS, сменим директорию и запустим наше приложение (оно собрано для этой архитектуры):
ssh user@remote_ip
cd project_name
./main
Деплой с использованием screen
У нас пока есть проблема — как только закроется окно терминал, программа будет завершена. Чтобы предотвратить это, можно использовать screen. Установите её если необходимо (воспользуйтесь своим менеджером пакетов если apt недоступен):
sudo apt-get install screen
Теперь можно запустить новую сессию screen набрав команду screen.
Если мы повторно введём
cd project_name
./main
и нажмём комбинацию CTRL+A+D, сессия screen будет отсоединена. Набрав команду screen -ls, можно увидеть список отсоединённых сессий.
user@tempest:~# screen -ls
There are screens on:
2851.pts-0.tempest (04/01/2017 04:01:56 PM) (Detached)
2459.pts-0.tempest (04/01/2017 03:56:38 PM) (Detached)
Запустив pstree , можно наглядно увидеть концепцию screen:
user@tempest:~# pstree
systemd─┬─accounts-daemon─┬─{gdbus}
│ └─{gmain}
├─acpid
├─agetty
├─atd
├─cron
├─screen───bash
├─screen───bash───top
├─snapd───5*[{snapd}]
├─sshd───sshd───bash───pstree
Здесь одна из сессий screen не делает ничего, а в другой запущена top.
Можно создать любое количество сессий screen и заново подключаться к ним командой screen -r, например так:
screen -r 2851.pts-0.tempest.
Супер! Теперь мы знаем как сделать так, чтобы программа работала при закрытии сессии SSH, осталось сделать так, чтобы при перезапуске VPS наша программа автоматически запустилась.
Создание Init скрипта для нашего приложения
cd /etc/init.d/
touch my-service.sh
vim my-service.sh
chmod +x my-service.sh #!/bin/sh
### BEGIN INIT INFO
# Provides: main_go
# Required-Start: $network $syslog
# Required-Stop: $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start go server at boot time
# Description: your cool web app
### END INIT INFO
root@tempest:/etc/init.d# ./my-service.sh start
server running
Откроем другое ssh соединение к серверу и запустим:
cd /etc/init.d
./my-service.sh stop
В первой сессии будет выведено Terminated. Так мы проверили работу команд start и stop.
Теперь запустим эту службу без вывода в открытую консоль:
/etc/init.d/my-service.sh start
Чтобы он запускался при загрузке сервера выполните команду:
update-rc.d my-service.sh defaults
Проверить создание корректных симлинков можно командой
ls -r /etc/rc* | grep my-service
В моём случае вывод такой:
K01my-service.sh
S01my-service.sh
S01my-service.sh
S01my-service.sh
S01my-service.sh
K01my-service.sh
K01my-service.sh
Чтобы проверить, воспользуйтесь init 6 и дождитесь, чтобы сервер вернулся онлайн. Но не делайте этого, если на сервере запущены другие важные приложения.
При разработке приложений у вас, возможно, появлялось желание вынести некоторые изменяемые значения в отдельный файл настроек, а не задавать их в коде программы. Особенно часто при разработке веб-приложений конфигурационные файлы разделяют на те, что хранят настройки доступа к базе данных, имена серверов, пароли и т.п. — как раз то, что нельзя жёстко задавать в коде программы.
Мы рассмотрим пример открытия файла настроек в формате JSON из приложения на Go с последующей загрузкой этих данных в структуру данных, которую можно будет использовать в дальнейшем.
На самом деле Golang это всё уже умеет «из коробки». Рассмотрим пример структуры данных, которая представляет данные конфигурации:
type Config struct {
Database struct {
Host string `json:"host"`
Password string `json:"password"`
} `json:"database"`
Host string `json:"host"`
Port string `json:"port"`
}
Все поля это структуры, включая вложенные, имеют аннотации JSON. А значит, JSON-эквивалент этой структуры будет таким:
В этой статье мы рассмотрим как создать API на базе MySQL с Go и echo. Ей мы начинаем цикл статей по разработке сайта photographerexcuses.com (название сайта можно перевести как «оправдания фотографов»).
Сайт представляет собой одностраничное веб-приложение на базе Vue.js, получающее данные (оправдания) от API на Go. Мы храним эти данные в базе MySQL.
Выбор веб-фреймворка на Golang
Многие вам скажут, что фреймворк не нужен и будут абсолютно правы. Однако без фреймворка многие вещи делать сложнее. В общем, выбирайте что хотите, а я выбрал echo для этого проекта.
Начнём с создания файла main.go, настройки в нём echo и регистрации необходимых роутов. Обратите внимание, что мы импортируем database/sql с _ перед ним. Это сделано для того, чтобы компилятор пропустил этот неиспользуемый импорт сейчас.
e.Logger.Fatal(e.Start(":4000"))
}
Также, возможно, вам не понадобится middleware.CORSWithConfig, тут всё зависит от того, как вы настроите фронтенд. Я рекомендую оставить это хотя бы на время разработки.
Открыв http://localhost:4000 вы должны увидеть строку “Hi!”, а открыв http://localhost:4000/id/42 вы должны увидеть “42”. Всё работает как надо, ведь мы хотели как получать случайный результат, так и определённое оправдание по его ID.
Ответ в формате JSON
Для того, чтобы наш API всегда отвечал определённым образом, создадим специальный тип, которым и будем передавать ответ:
type (
Excuse struct {
Error string `json:"error"`
Id string `json:"id"`
Quote string `json:"quote"`
}
)
Обработка ошибок очень важна в любом проекте, поэтому мы задали специальное поле Error для этого. В коде фронтенда (это будет один из следующих постов) мы сделаем проверку этого поля, чтобы знать, что все SQL-запросы прошли успешно.
Выборка случайного поля в SQL на Go
var quote string;
var id string;
err = db.QueryRow("SELECT id, quote FROM excuses ORDER BY RAND() LIMIT 1").Scan(&id, "e)
В этом кусочке кода показана выборка из базы данных из таблицы excuses, а методом .Scan() мы записываем её в инициированные выше переменные.
Затем мы воспользуемся структурой Excuse с передачей в неё значений наших переменных, вернём c как контекст echo, и JSON() со статусом ответа 200 и готовым ответом:
response := Excuse{Id: id, Error: "false", Quote: quote}
return c.JSON(http.StatusOK, response)
В результате мы получим следующий ответ в httpie:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Length: 78
Content-Type: application/json; charset=utf-8
Date: Sat, 01 Apr 2017 12:51:01 GMT
Vary: Origin
X-Clacks-Overhead: GNU Terry Pratchett
{
"error": "false",
"id": "48",
"quote": "This is just how the industry works now."
}
А X-Clacks-Overhead: GNU Terry Pratchett приходит из middleware goClacks, которое я написал в честь известного фэнтезийного писателя.
Собираем воедино
Нам понадобится SQL только для создания таблицы в базе данных:
CREATE TABLE `excuses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`quote` text,
`author` varchar(191) DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
Немного доработанный, хоть и без обработки ошибок, наш код будет выглядеть так, как он описан в конце топика. И вы также можете повторно использовать MySQL-соединение вместо создания нового для каждого запроса.
Также можно будет сделать некоторые интересные вещи, вроде таких как
Кэширование последних запросов к базе данных и проверка ответа в памяти перед созданием очередного SQL запроса
Сегодня мы создадим несложное веб-приложение, в котором по списку авторов будет производится поиск их произведений с помощью Google Books API.
Подготовка
Для начала установите утилиту командной строки для Google Cloud SDK.
Создание проекта Google Cloud
С помощью команды gcloud alpha create создадим новый проект. Так как это пока альфа-релиз утилиты, её поведение может измениться в будущем. Другой способ — с помощью веб-сайта https://console.cloud.google.com.
$ gcloud alpha projects create go-serverless
Create in progress for [https://cloudresourcemanager.googleapis.com/v1/projects/go-serverless].
Waiting for [operations/pc.7509198304344852511] to finish…done.
Если это не первый ваш проект в Google Cloud, есть замечательная команда для просмотра всех ваших проектов:
$ gcloud projects list
PROJECT_ID NAME PROJECT_NUMBER
bespokemirrorapi bespokemirrorapi 811093430365
gdgnoco-fortune gdgnoco-fortune 861018601285
go-serverless go-serverless 49448245715
…
Выбор проекта по-умолчанию
Все последующие вызовы команды gcloud будут ассоциироваться с определённым проектом, поэтому будет лучше если мы сконфигурируем созданный проект как основной. При создании проекта он автоматически не становится основным.
Выведем все имеющиеся проекты командой gcloud config list и установим нужный командой gcloud set ….
Установим свойство core/project в go-serverless:
$ gcloud config set core/project go-serverless
Updated property [core/project].
$ gcloud config list
[compute]
zone = us-central1-c
[core]
account = ghchinoy@gmail.com
disable_usage_reporting = False
project = go-serverless
Your active configuration is: [default]
Создание локального приложения
Создадим папку проекта
Создадим файл для деплоя app.yaml
Обычно проекты Go создают в папке $GOPATH\src, в папке, связанной с репозиторием. Здесь я создаю проект, который связан с моим репозиторием github и перехожу в эту папку:
$ mkdir -p $GOPATH/src/github.com/ghchinoy/go-serverless
$ cd $GOPATH/src/github.com/ghchinoy/go-serverless
Добавим файл app.yaml:
runtime: go
env: flex
api_version: 1
skip_files:
- README.md
Добавим код в main.go (полный исходный код лежит здесь):
var (
// html templates
booklistTmpl = parseTemplate("list.html")
authordetailTmpl = parseTemplate("detail.html")
)
func main() {
log.Println("bookshelf")
r := mux.NewRouter()
r.Path("/").Methods("GET").Handler(apiHandler(listBooksHandler))
r.Path("/author/{author}").Methods("GET").Handler(apiHandler(showBooksByAuthorHandler))
appengine.Main()
}
func listBooksHandler(w http.ResponseWriter, r *http.Request) *apiError {
authors := struct {
Authors []string
}{
Authors: []string{
"Miguel de Cervantes",
"Charles Dickens",
"Antoine de Saint-Exupéry",
"J. K. Rowling",
},
}
return booklistTmpl.Execute(w, r, authors)
}
Запустим приложение и откроем браузер на http://localhost:8080
$ go run main.go template.go
2017/03/30 15:55:21 booklist
127.0.0.1 — — [30/Mar/2017:15:55:24 -0600] “GET / HTTP/1.1” 200 1584 “” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3056.0 Safari/537.36”
…
* оформление страницы опускаем здесь, его можно взять в репозитории.
Деплой
Перейдём в консоль Google Cloud и настроим параметры проекта, для этого откройте https://console.developers.google.com/project/go-serverless/settings, заменив go-serverless на название вашего проекта.
$ gcloud app deploy
You are creating an app for project [go-serverless].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed. More information about regions is at
https://cloud.google.com/appengine/docs/locations.
Please choose the region where you want your App Engine application
located:
[1] europe-west (supports standard and flexible)
[2] us-east1 (supports standard and flexible)
[3] us-central (supports standard and flexible)
[4] asia-northeast1 (supports standard and flexible)
[5] cancel
Please enter your numeric choice: 3
Creating App Engine application in project [go-serverless] and region [us-central]….done.
You are about to deploy the following services:
— go-serverless/default/20170330t205543 (from [/Users/ghc/dev/go/src/github.com/ghchinoy/go-serverless/app.yaml])
Deploying to URL: [https://go-serverless.appspot.com]
Do you want to continue (Y/n)? Y
If this is your first deployment, this may take a while…done.
Beginning deployment of service [default]…
Building and pushing image for service [default]
Some files were skipped. Pass ` — verbosity=info` to see which ones
…
Updating service [default]…done.
Deployed service [default] to [https://go-serverless.appspot.com]
You can stream logs from the command line by running:
$ gcloud app logs tail -s default
To view your application in the web browser run:
$ gcloud app browse
Готово! Мой проект лежит тут: https://go-serverless.appspot.com/
Подготовлено по материалам «Building a Serverless Go App on Google Cloud»
Сегодня наконец вышел предварительный релиз NativeScript 3.0! Как мы писали ранее, в нём было запланированы улучшения производительности, а также некоторые крупные изменения, ломающие обратную совместимость.
Новая имплементация модулей. Кросс-платформенные модули обновлены для повышения производительности, расширяемости, улучшения API.
Полностью обновлённая система измерений в макетах. До версии 3.0 система работала на основе DIP (независимых от устройства пикселей), а начиная с 3.0 всё пересчитывается в DP (пиксели устройства) и вы можете указать суффикс px для создания однопиксельного края элемента (1-px), к примеру.
Переход на TypeScript 2.2
NativeScript CLI
В поведение утилиты NativeScript CLI, кроме улучшений производительности и исправления ошибок, были внесены следующие изменения:
Удалена команда livesync, вместо неё команда tns run будет автоматически запускаться с ключом livesync —watch.
Удалена команда plugin find/search за неиспользованием.
Команда emulate запрещена, этот функционал выполняет команда run —emulate.
Команда run --device теперь сама запускает эмулятор, если он не был запущен ранее. До версии 3.0 ключ —device работал только с физическими устройствами.
Изменения в платформах iOS и Android
Теперь можно отслеживать сетевой трафик приложения прямо из Chrome DevTools. Для Android возвращена статическая генерация кода вместо создания файлов *.DEX. Обновлена утилита Gradle, что значительно ускорило сборку приложения для Android.
Переход на NativeScript 3.0 RC
Установить релиз-кандидат можно так:
npm install -g nativescript@rc
Создание проектов с NativeScript 3.0 RC
Создать проект на базе новых шаблонов можно так:
На чистом JavaScript: tns create MyApp --template tns-template-hello-world@rc
На TypeScript: tns create MyApp --template tns-template-hello-world-ts@rc
На Angular: tns create MyApp --template tns-template-hello-world-ng@rc
Добавить платформы можно так:
tns platform add android@rc
и
tns platform add ios@rc
И это всё! Запустить приложение можно будет так: tns run
Обновление существующего проекта
Для обновления проекта нужно обновить tns-core-modules и платформы:
tns plugin remove tns-core-modules
tns plugin add tns-core-modules@rc
tns platform remove
tns platform add @rc
Для приложений TypeScript и Angular нужно обновить плагин TypeScript:
npm uninstall nativescript-dev-typescript --save-dev
npm uninstall typescript --save-dev
npm install nativescript-dev-typescript@0.4 --save-dev
И, наконец, для проектов Angular выполните следующие команды:
1. Обновите плагин nativescript-angular:
tns plugin remove nativescript-angular
tns plugin add nativescript-angular@rc
2. Обновитесь до Angular 4, открыв файл package.json и прописав там версию 4.0.0. При этом зависимость zone.js должна быть прописана в зависимостях проекта (dependency), вместо зависимостей при разработке (devDependency), и обновлена до версии 0.8.2.
В результате файл package.json будет выглядеть примерно так:
На прошлой неделе команда Angular объявила о выпуске релиза Angular 4.0.0 и сегодня мы рады сообщить, что NativeScript отлично работает с этой версией Angular.
Из основных преимуществ Angular 4: меньшие размеры пакетов и увеличение скорости работы приложений. А теперь обновим наши приложения NativeScript, чтобы получить все эти улучшения.
Обновление
Для получения обновления необходимо вначале обновить зависимости проекта в файле package.json. Вот как выглядит типовое обновление:
Несколько замечаний по этим изменениям:
Главное здесь в том, чтобы обновить пакет nativescript-angular до версии 1.5.0 и пакеты Angular до версии 4.0.0.
В Angular 4 некоторые библиотеки анимации переехали в свой пакет @angular/animations. Этот пакет опционален и вы можете смело удалить его, если вам не нужны анимации в приложении.
Пакет zone.js теперь указан как dependency, а не devDependency как раньше.
Обратите особое внимание, что версия TypeScript указана как ~2.1.0. И это очень важно, так как NativeScript с Angular пока не поддерживает TypeScript 2.2.. Его поддержка запланирована на NativeScript 3.0, который выйдет очень скоро 🙂
Крупные изменения
При обновлении нужно учесть несколько моментов. Во-первых, изменён путь до класса NativeScriptModule. В большинстве приложений он указан только в файле app.module.ts, поэтому исправить недолго.
До исправления:
import { NativeScriptModule } from "nativescript-angular/platform";
После:
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
Во-вторых, для использования анимаций Angular нужно импортировать NativeScriptAnimationsModule из «nativescript-angular/animations» корневого NgModule. Оно будет работать также, как используемые вами классы NativeScriptFormsModule, NativeScriptHttpModule и NativeScriptRouterModule. Вот как примерно выглядит несложный файл app.module.ts, использующий NativeScriptAnimationsModule:
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { NativeScriptAnimationsModule } from "nativescript-angular/animations";
import { NgModule } from "@angular/core";
Внимание: при использовании анимаций Angular, обязательно включайте зависимость @angular/animations в файл package.json!
Заключение
И напоследок: команда Angular объявила устаревшим тег и вместо него нужно использовать , поэтому скорее всего вы увидите следующее сообщение при запуске приложений:
The element is deprecated. Use instead.
Исправляется это очень просто заменой тегов на .
Теперь точно всё 🙂 Для большинства обновление пройдёт очень легко. Если же у вас будут проблемы с обновлением, дайте нам знать об этом.