Шаблоны в Gin Web Framework (Golang)

Шаблоны в Gin Web Framework (Golang)
На прошлой неделе я занимался разработкой небольшого веб-приложения на 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

Теперь создадим главную функцию для запуска нашего веб-приложения.

demo.go:
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*")

// our handlers will go here

router.Run("0.0.0.0:9090")
}

Мы запустили приложение на порту 9090, а шаблоны приложения располагаются в папке templates, которая находится там же:

$ ls -lh
total 8
-rw-r--r-- 1 markneedham staff 570B 23 Dec 13:34 demo.go
drwxr-xr-x 4 markneedham staff 136B 23 Dec 13:34 templates

Массивы

Создадим роут, выводящий значения неупорядоченного массива:

router.GET("/array", func(c *gin.Context) {
var values []int
for i := 0; i < 5; i++ { values = append(values, i) } c.HTML(http.StatusOK, "array.tmpl", gin.H{"values": values}) })

    {{ range .values }}

  • {{ . }}
  • {{ end }}


Запросим с cURL ответ от приложения и должны увидеть следующее:

$ curl http://localhost:9090/array

  • 0
  • 1
  • 2
  • 3
  • 4


А что если нам нужно вывести массив структур:

import "strconv"

type Foo struct {
value1 int
value2 string
}

router.GET("/arrayStruct", func(c *gin.Context) {
var values []Foo
for i := 0; i < 5; i++ { values = append(values, Foo{Value1: i, Value2: "value " + strconv.Itoa(i)}) } c.HTML(http.StatusOK, "arrayStruct.tmpl", gin.H{"values": values}) })

    {{ range .values }}

  • {{ .Value1 }} -> {{ .Value2 }}
  • {{ end }}


Запросим ответ с cURL:

$ curl http://localhost:9090/arrayStruct

  • 0 -> value 0
  • 1 -> value 1
  • 2 -> value 2
  • 3 -> value 3
  • 4 -> value 4

Карты

Сделаем то же самое с картами:

router.GET("/map", func(c *gin.Context) {
values := make(map[string]string)
values["language"] = "Go"
values["version"] = "1.7.4"

c.HTML(http.StatusOK, "map.tmpl", gin.H{"myMap": values})
})

    {{ range .myMap }}

  • {{ . }}
  • {{ end }}


заcURL-им:

$ curl http://localhost:9090/map

  • Go
  • 1.7.4


А что если нам нужно увидеть ключи?

router.GET("/mapKeys", func(c *gin.Context) {
values := make(map[string]string)
values["language"] = "Go"
values["version"] = "1.7.4"

c.HTML(http.StatusOK, "mapKeys.tmpl", gin.H{"myMap": values})
})

    {{ range $key, $value := .myMap }}

  • {{ $key }} -> {{ $value }}
  • {{ end }}


$ curl http://localhost:9090/mapKeys

  • language -> Go
  • version -> 1.7.4


И, наконец, что если нам нужно выбрать определённые значения из карты?

router.GET("/mapSelectKeys", func(c *gin.Context) {
values := make(map[string]string)
values["language"] = "Go"
values["version"] = "1.7.4"

c.HTML(http.StatusOK, "mapSelectKeys.tmpl", gin.H{"myMap": values})
})

  • Language: {{ .myMap.language }}
  • Version: {{ .myMap.version }}


$ curl http://localhost:9090/mapSelectKeys

  • Language: Go
  • Version: 1.7.4


Также мне понравился материал о шаблонах в Hugo, обязательно прочитайте его. Весь код с этими примерами можно взять здесь. Удачи!

По материалам: "Go: Templating with the Gin Web Framework"

Отслеживание HTTP-перенаправлений в Golang

Отслеживание HTTP-перенаправлений в Golang
Сегодня мы рассмотрим как обрабатывать 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 и прочитать его код состояния:

// file: http-request.go
package main

import(
"fmt"
"net/http"
)

func main(){
resp, err := http.Get("http://www.jonathanmh.com/")

if err != nil {
fmt.Println(err)
}

fmt.Println("StatusCode:", resp.StatusCode)
fmt.Println(resp.Request.URL)
}

Ответ:

StatusCode: 200
https://jonathanmh.com/

Обратите внимание, что в resp.Request.URL мы получили совсем не тот адрес, что передали методу Get. Это произошло потому, что я перенаправляю пользователей с адреса www.jonathanmh.com на jonathanmh.com. Go делает всё как положено и просто следует правилам. В большинстве случаев такое поведение мы и ожидаем, но только не в том, случае, когда хотим сделать что-то вроде утилиты для проверки перенаправлений! В этом случае мы хотим знать каждый шаг перенаправления и код состояния каждого запроса.

Создание HTTP запросов в Go без следования редиректам

Для того, чтобы не следовать автоматическим перенаправлениям, создадим свой экземпляр http.Client с методом проверки CheckRedirect. Это поможет нам возвращать код состояния и адрес до перенаправления.

// file: http-nofollow-request.go
package main

import(
"fmt"
"net/http"
)

func main(){
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
} }

resp, err := client.Get("http://www.jonathanmh.com")

if err != nil {
fmt.Println(err)
}

fmt.Println("StatusCode:", resp.StatusCode)
fmt.Println(resp.Request.URL)
}

Ответ:

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") } } }

Ответ:

StatusCode: 301

I’m JonathanMH


StatusCode: 301

I’m JonathanMH


StatusCode: 200

I’m JonathanMH


Done!

Этот код практически повторяет предыдущий пример, однако здесь мы добавили пару переменных, myURL и nextURL. И в конце цикла, если состояние не 200 - OK, мы присваиваем переменной nextURL значение resp.Header.Get("Location") - это HTTP заголовок цели перенаправления. Вы можете отслеживать эти заголовки на вкладке Сеть (Network) в Инструментах разработчика (Developer Tools) в Chrome или Firefox, поставив галочку Preserve Log.

Вот и всё! Теперь вы знаете, как делать несколько прикольных штук с net/http в Go. Теперь вы можете обернуть этот код в http API, запустить программу на сервере, ну и, конечно же, добавить ей простенький интерфейс, вроде того, как я сделал тут.

Для дальнейшего изучения

Одной статьёй не охватить всего, поэтому рекомендую почитать следующие материалы по теме:

Буду рад, если поделитесь мыслями по поводу материала и если он был полезен вам!

От переводчика: вы можете прокомментировать как оригинальный пост, так и перевод, но автор будет очень рад комментариям в своём блоге 🙂

Источник: "Tracing or Preventing HTTP Redirects in Golang"

Разработка Web-приложений и микросервисов на Go с Gin, часть 3

Создаём веб-приложение с Go, Gin и React

Это продолжение материала, часть 1, часть 2

Вывод топика

У нас пока не работают ссылки на топики из общего списка. Сейчас мы добавим обработчики клика и шаблон для вывода топика.

Настройка роутов

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

router.GET("/article/view/:article_id", getArticle)

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

Определяем требования к обработчику роутов юнит-тестами

Тест обработчика будет проверять выполнение следующих условий:

  1. Обработчик отвечает статус-кодом HTTP 200,
  2. Возвращаемый 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 в браузере оно будет выглядеть так:

Разработка Web-приложений и микросервисов на Go с Gin, часть 3

Новые файлы, добавленные в этом разделе:


└── 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 для проверки этих условий:

  1. Проверить, что приложение вернёт список топиков в формате JSON если заголовок Accept равен application/json
  2. Проверить, что приложение вернёт список топиков в формате 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/

Она должна вернуть следующее:

[{"id":1,"title":"Article 1","content":"Article 1 body"},{"id":2,"title":"Article 2","content":"Article 2 body"}]

Как вы видите, мы получили ответ в формате JSON, передав заголовок Accept как application/json.

Список топиков в формате XML

Теперь запросим детали конкретной статьи в формате XML. Для этого запустите приложение как написано выше и затем выполните команду:

curl -X GET -H "Accept: application/xml" http://localhost:8080/article/view/1

В ответ должно прийти следующее:


<article><ID>1</ID><Title>Article 1</Title><Content>Article 1 body</Content></article>
Тестирование приложения

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

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 с Nginx

Проксификация API с Nginx
Итак, мне нужно, чтобы 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

server_name photographerexcuses.com;

access_log /var/log/nginx/photographerexcuses.access.log;
error_log /var/log/nginx/photographerexcuses.error.log;

root /var/www/photographerexcuses.com/;

location / {
root /var/www/photographerexcuses.com;
try_files $uri $uri/ /index.html;
index index.php index.html;
}

# rewrite `http://www` to `http://`
if ($host ~* ^www\.(.*))
{
set $host_without_www $1;
rewrite ^/(.*)$ $scheme://$host_without_www/$1 permanent;
}

location /api/excuse {
rewrite ^/api/excuse(/.*)$ $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;
}

add_header X-Clacks-Overhead "GNU Terry Pratchett";
}

Источник: «Proxy / Rewrite your API Endpoint into Domain Segment with Nginx»

Продолжение: http://tehnojam.ru/category/development/sozdaem-odnostranichnik-s-vue_js.html

Деплой Golang приложения без использования Docker

Деплой Golang приложения без использования Docker
Как задеплоить приложение на 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

case "$1" in
start)
exec /var/www/project_name/main &
;;
stop)
kill $(lsof -t -i:4000)
;;
*)
echo $"Usage: $0 {start|stop}"
exit 1
esac
exit 0

Протестируем:

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

Источник: «Deploying your Golang App without Docker»

Продолжение: http://tehnojam.ru/category/development/proksifikacija-api-s-nginx.html

Загрузка настроек приложения из JSON в Golang

Загрузка настроек приложения из JSON в Golang
При разработке приложений у вас, возможно, появлялось желание вынести некоторые изменяемые значения в отдельный файл настроек, а не задавать их в коде программы. Особенно часто при разработке веб-приложений конфигурационные файлы разделяют на те, что хранят настройки доступа к базе данных, имена серверов, пароли и т.п. — как раз то, что нельзя жёстко задавать в коде программы.

Мы рассмотрим пример открытия файла настроек в формате 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-эквивалент этой структуры будет таким:

{
"database": {
"host": "localhost",
"password": "12345"
},
"host": "localhost",
"port": "8080"
}

Договоримся, что наши настройки хранятся в файле config.json и мы хотим загрузить их. Вот код для этого:

func LoadConfiguration(file string) Config {
var config Config
configFile, err := os.Open(file)
if err != nil {
fmt.Println(err.Error())
}
jsonParser := json.NewDecoder(configFile)
jsonParser.Decode(&config)
return config
}

Теперь эти настройки можно использовать в нашей программе.

Этот пример очень простой, однако очень полезный при разработке приложений на Go.

Источник: «Load A JSON Configuration From File In A Golang Application»

Разработка Go(lang) API с echo и MySQL

Разработка Go(lang) API с echo и MySQL
В этой статье мы рассмотрим как создать API на базе MySQL с Go и echo. Ей мы начинаем цикл статей по разработке сайта photographerexcuses.com (название сайта можно перевести как «оправдания фотографов»).

Сайт представляет собой одностраничное веб-приложение на базе Vue.js, получающее данные (оправдания) от API на Go. Мы храним эти данные в базе MySQL.

Выбор веб-фреймворка на Golang

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

Начнём с создания файла main.go, настройки в нём echo и регистрации необходимых роутов. Обратите внимание, что мы импортируем database/sql с _ перед ним. Это сделано для того, чтобы компилятор пропустил этот неиспользуемый импорт сейчас.

package main

import (
_ "database/sql"
"fmt"
"net/http"

_ "github.com/go-sql-driver/mysql"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
"github.com/JonathanMH/goClacks/echo"
)

func main() {
// Echo instance
e := echo.New()
e.Use(goClacks.Terrify) // optional ;)

// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())

e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.GET, echo.PUT, echo.POST, echo.DELETE},
}))

// Route => handler
e.GET("/", func(c echo.Context) error {

return c.JSON(http.StatusOK, "Hi!")
})

e.GET("/id/:id", func(c echo.Context) error {
requested_id := c.Param("id")
fmt.Println(requested_id);
return c.JSON(http.StatusOK, requested_id)
})

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

Разработка Go(lang) API с echo и MySQL

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:

(master)⚡ {33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a} http localhost:3131

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 запроса
  • Ограничения скорости по IP адресу

Ниже полная версия кода приложения:

package main

import (
"database/sql"
"fmt"
"net/http"

_ "github.com/go-sql-driver/mysql"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
"github.com/JonathanMH/goClacks/echo"
)

type (
Excuse struct {
Error string `json:"error"`
Id string `json:"id"`
Quote string `json:"quote"`
}
)

func main() {
// Echo instance
e := echo.New()
e.Use(goClacks.Terrify)

// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())

e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.GET, echo.PUT, echo.POST, echo.DELETE},
}))

// Route => handler
e.GET("/", func(c echo.Context) error {
db, err := sql.Open("mysql", "db_user:db_password@tcp(SERVER_IP:PORT)/database_name")

if err != nil {
fmt.Println(err.Error())
response := Excuse{Id: "", Error: "true", Quote: ""}
return c.JSON(http.StatusInternalServerError, response)
}
defer db.Close()

var quote string;
var id string;
err = db.QueryRow("SELECT id, quote FROM excuses ORDER BY RAND() LIMIT 1").Scan(&id, "e)

if err != nil {
fmt.Println(err)
}

fmt.Println(quote);
response := Excuse{Id: id, Error: "false", Quote: quote}
return c.JSON(http.StatusOK, response)
})

e.GET("/id/:id", func(c echo.Context) error {
requested_id := c.Param("id")
fmt.Println(requested_id);
db, err := sql.Open("mysql", "db_user:db_password@tcp(SERVER_IP:PORT)/database_name")

if err != nil {
fmt.Println(err.Error())
response := Excuse{Id: "", Error: "true", Quote: ""}
return c.JSON(http.StatusInternalServerError, response)
}
defer db.Close()

var quote string;
var id string;
err = db.QueryRow("SELECT id, quote FROM excuses WHERE id = ?", requested_id).Scan(&id, "e)

if err != nil {
fmt.Println(err)
}

response := Excuse{Id: id, Error: "false", Quote: quote}
return c.JSON(http.StatusOK, response)
})

e.Logger.Fatal(e.Start(":4000"))
}

Источник: «Building a Go(lang) API with echo and MySQL»

Продолжение: http://tehnojam.ru/category/development/deploj-golang-prilozhenija-bez-docker.html

Создание приложения Go в Google Cloud

Создание приложения Go в Google Cloud
Сегодня мы создадим несложное веб-приложение, в котором по списку авторов будет производится поиск их произведений с помощью 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 (полный исходный код лежит здесь):

package main

import (
"fmt"
"html/template"
"io/ioutil"
"log"
"path/filepath"

"net/http"

"github.com/gorilla/mux"
"google.golang.org/appengine"
)

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)
}

func showBooksByAuthorHandler(w http.ResponseWriter, r *http.Request) *apiError {
vars := mux.Vars(r)
authorInfo := struct {
Author string
}{
Author: vars["author"],
}
return authordetailTmpl.Execute(w, r, authorInfo)
}

Тестируем приложение

Запустим приложение и откроем браузер на 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”

Создание приложения Go в Google Cloud

Создание приложения Go в Google Cloud
* оформление страницы опускаем здесь, его можно взять в репозитории.

Деплой

Перейдём в консоль 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/

Создание приложения Go в Google Cloud

Подготовлено по материалам «Building a Serverless Go App on Google Cloud»

Предварительный релиз NativeScript 3.0

Предварительный релиз NativeScript 3.0
Сегодня наконец вышел предварительный релиз NativeScript 3.0! Как мы писали ранее, в нём было запланированы улучшения производительности, а также некоторые крупные изменения, ломающие обратную совместимость.

Разработчики подготовили документ, описывающий каждое изменение в этом релизе, его цель и важность для проекта (англ.). Также доступен гайд по миграции на NS 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 будет выглядеть примерно так:

"dependencies": {
"nativescript-theme-core": "~1.0.2",
"nativescript-angular": "rc",
"tns-core-modules": "rc",
"@angular/animations": "4.0.0",
"@angular/core": "4.0.0",
"@angular/common": "4.0.0",
"@angular/compiler": "4.0.0",
"@angular/http": "4.0.0",
"@angular/platform-browser": "4.0.0",
"@angular/platform-browser-dynamic": "4.0.0",
"@angular/forms": "4.0.0",
"@angular/router": "4.0.0",
"rxjs": "~5.2.0",
"reflect-metadata": "~0.1.8",
"zone.js": "~0.8.2"
},
"devDependencies": {
"typescript": "~2.2.1",
"nativescript-dev-typescript": "~0.4.0",
"nativescript-dev-android-snapshot": "^0.*.*"
}

Известные проблемы

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

  • Нет поддержки Xcode 8.3
  • Невозможно установить приложение на физическое устройство iOS без ключа —syncAllFiles
  • Для Android пока нет снапшотов (snapshot packages)

Новый релиз-кандидат будет выпущен как только исправят эти недостатки.

Если у вас будут проблемы с этим релиз-кандидатом, сообщите, пожалуйста, об этом разработчикам, чтобы они могли их починить к официальному релизу.

Также возможны проблемы с некоторыми плагинами (которые работают непосредственно с интерфейсом приложения), но с ними уже идёт работа.

Когда будет официальный релиз?

Официальный релиз 3.0 запланирован на 26 апреля 2017 года. А до этого времени будут выпускаться релиз-кандидаты и исправляться возможные ошибки.

Подготовлено по материалам официального блога NativeScript.

Поддержка Angular 4 в NativeScript

Поддержка Angular 4 в NativeScript

На прошлой неделе команда Angular объявила о выпуске релиза Angular 4.0.0 и сегодня мы рады сообщить, что NativeScript отлично работает с этой версией Angular.

Из основных преимуществ Angular 4: меньшие размеры пакетов и увеличение скорости работы приложений. А теперь обновим наши приложения NativeScript, чтобы получить все эти улучшения.

Обновление

Для получения обновления необходимо вначале обновить зависимости проекта в файле package.json. Вот как выглядит типовое обновление:

Поддержка Angular 4 в NativeScript
Несколько замечаний по этим изменениям:

  • Главное здесь в том, чтобы обновить пакет 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";

import { AppComponent } from "./app.component";

@NgModule({
imports: [
NativeScriptModule,
NativeScriptAnimationsModule
],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule {}

Внимание: при использовании анимаций Angular, обязательно включайте зависимость @angular/animations в файл package.json!

Заключение

И напоследок: команда Angular объявила устаревшим тег