Go - це просто. Створюємо HelloWorld веб-сервер
На численні прохання читачів DOU публікую першу статтю про Go.
Нижче будуть розкриті наступні теми:
- як створити на Go простий веб-сервер, якому не потрібні Apache з nginx'ом;
- як додати підтримку https без використання OpenSSL;
- як перестати платити за TLS-сертифікати і турбуватися про їх своєчасному оновленні.
Створюємо HelloWorld веб-сервер на Go
Опустимо розділи про установку і настройку Go. При бажанні можете почитати самі . Перейдемо відразу до справи :)
Створюємо файл server.go і зберігаємо в нього наступний код:
// Оголошуємо назва пакету.
// Всі *.go - файли повинні починатися з назви пакета.
// Пакет з назвою "main" має спеціальне призначення - він вказує
// компілятору go, що з цього пакету потрібно зібрати самодостатній
// виконуваний файл. Це бінарники, якому для запуску не потрібні додаткові
// залежності - його достатньо скопіювати на комп'ютер і запустити.
package main
// Імпортуємо пакети, використовувані в даному файлі.
//
// Документацію по стандартним і стороннім пакетів легко знайти за адресою
// https://godoc.org/<package_path>.
// Наприклад,
//
// * https://godoc.org/flag
// * https://godoc.org/github.com/valyala/fasthttp
import (
"flag"
"github.com/valyala/fasthttp"
"log"
)
// Оголошуємо глобальну змінну addr, куди буде записано значення параметра
// -addr при запуску програми.
//
// Наприклад, параметр addr стане рівним ":80" для наступної рядка
// запуску:
// ./server -addr=:80
//
// Пропущений IP TCP адресу говорить про те, щоб сервер "слухав"
// на всіх доступних IP-адреси.
//
// flag.String вказує на те, що значення -addr - рядок.
// flag.String приймає три аргументи:
//
// * Назва аргументу, який потрібно розпарсити. "addr" в даному випадку.
// * Значення за замовчуванням. "127.0.0.1:8080" в даному випадку.
// * Опис аргументу, яке виводиться при виклику програми
// з параметром -help.
//
// flag.String повертає покажчик на рядок, де зберігається значення -addr.
var addr = flag.String("addr", "127.0.0.1:8080",
"TCP address to listen to for incoming connections")
// main - функція, з якої починається виконання програми.
// Ця функцію повинна знаходитися в package main.
func main() {
// Парсим параметри, вказані в рядку запуску програми.
flag.Parse()
// Конфігуруємо http сервер.
//
// См. можливі параметри конфігурації
// https://godoc.org/github.com/valyala/fasthttp#Server
s := fasthttp.Server{
// Hanlder - функція-обробник входять http запитів.
// См. код функції handler нижче.
Handler: handler,
}
// Запускаємо сервер.
//
// ListenAndServe приймає TCP адресу, де буде запущений сервер.
// ListenAndServe повертає результат тільки в двох випадках:
//
// * Якщо у під час запуску сервера сталася помилка.
// Наприклад, вказану адресу уже зайнятий іншим сервером.
// Тоді відповідна помилка потрапить в err.
// * Якщо сервер був зупинений. Тоді err буде дорівнює nil.
err := s.ListenAndServe(*addr)
if err != nil {
log.Fatalf("error in ListenAndServe: %s", err)
}
}
// handler обробляє вхідні запити.
func handler(ctx *fasthttp.RequestCtx) {
ctx.WriteString("Hello, world!
")
}
Цей файл використовують сторонній пакет — github.com/valyala/fasthttp , який потрібно встановити перед компіляцією. Зробимо це:
$ go get -u github.com/valyala/fasthttp
Исходники всіх сторонніх пакетів, отримані з допомогою go get , зберігаються в папку $GOPATH/src/ . Про $GOPATH можна почитати в офіційній документації .
Тепер скомпилируем наш веб-сервер:
$ go build ./server.go
У поточному каталозі повинен з'явитися виконуваний файл з ім'ям server. Переконаємося в цьому:
$ ls -l | grep server -rwxrwxr-x 1 aliaksandr aliaksandr 6140200 May 7 19:39 server -rw-rw-r-- 1 aliaksandr aliaksandr 4020 May 7 19:31 server.go
Перевіримо параметри, які він приймає:
$ ./server -help Usage of ./server: -addr string TCP address to listen to for incoming connections (default "127.0.0.1:8080")
Запустимо його:
$ ./server
В окремому вікні переконаємося з допомогою nc , що сервер працює:
$ nc 127.0.0.1 8080 GET/HTTP/1.0 HTTP/1.1 200 OK Server: fasthttp Date: Sun, 07 May 2017 17:43:40 GMT Content-Type: text/plain; charset=utf-8 Content-Length: 14 Connection: close Hello, world!
Також можна відкрити 127.0.0.1:8080 в браузері і переконатися, що сервер працює.
Перевіримо швидкість його роботи за допомогою wrk .
Через одне підключення:
$ wrk -t 1 -c 1 http://127.0.0.1:8080/ Running 10s test @ http://127.0.0.1:8080/ 1 threads and connections 1 Thread Stats Avg Stdev Max +/- Stdev Latency 13.25 us 24.26 us 1.70 ms 99.74% Req/Sec 76.71 k 4.43 k 81.43 k 91.09% 771609 requests in 10.10 s, 109.64 read MB Requests/sec: 76399.78 Transfer/sec: 10.86 MB
Через 1000 одночасних підключень:
$ wrk -t 2 -c 1000 http://127.0.0.1:8080/ Running 10s test @ http://127.0.0.1:8080/ 2 threads and connections 1000 Thread Stats Avg Stdev Max +/- Stdev Latency 5.24 ms 3.44 ms 150.36 ms 89.22% Req/Sec 82.85 k 11.32 k 110.93 k 68.18% 1643377 requests in 10.01 s, 233.52 read MB Requests/sec: 164183.83 Transfer/sec: 23.33 MB
Через 100 одночасних підключень, в кожному з 32 pipelined запиту:
$ wrk -t 2 -c 100 -s pipeline.lua http://127.0.0.1:8080 --/32 Running 10s test @ http://127.0.0.1:8080 2 threads and connections 100 Thread Stats Avg Stdev Max +/- Stdev Latency 1.40 ms 1.46 ms 39.22 ms 90.68% Req/Sec 824.20 k 75.28 k 0.99 M 78.50% 16469856 requests in 10.05 s, 2.29 GB read Requests/sec: 1638496.88 Transfer/sec: 232.83 MB
Як бачите, простий веб-сервер з пари десятків рядків на Go не вимагає ні nginx, ні Apache, і може обробляти більше 1,6 млн запитів в секунду на звичайному ноут трирічної давності.
Додаємо підтримку https
У стандартну поставку Go входить пакет crypto/tls , з допомогою якого можна настроювати https на будь-який смак і колір. За розробку даного пакету відповідає Adam Langley , автор BoringSSL . Кілька фактів про crypto/tls :
- він написаний на Go з чистого аркуша, без використання OpenSSL або інших сторонніх TLS-бібліотек;
- у crypto/tls знаходиться на порядок менше дірок у порівнянні з кількістю вразливостей в OpenSSL ;
- цей пакет активно використовується в CloudFlare , яка регулярно вносить до нього різні оптимізації та поліпшення .
Якщо у вас вже є TLS-сертифікат і ви хочете швидше включити підтримку https, то просто замініть наступний рядок в server.go :
err := s.ListenAndServe(*addr)
На
err := s.ListenAndServeTLS(*addr, certFile, keyFile)
Де certFile і keyFile — шляхи до файлів сертифіката відповідного ключа.
Якщо потрібна додаткова настройка https, наприклад, як описано в статті Exposing Go on the Internet , то потрібно трохи повозитися:
package main
import (
"crypto/tls"
"flag"
"github.com/valyala/fasthttp"
"log"
"net"
)
var (
addr = flag.String("addr", "127.0.0.1:8080", "TCP address to listen to for http")
tlsAddr = flag.String("tlsAddr", "", "TCP address to listen to for https")
tlsCertFile = flag.String("tlsCertFile", "", "Path to TLS certificate file")
tlsKeyFile = flag.String("tlsKeyFile", "", "Path to TLS key file")
)
func main() {
flag.Parse()
// Намагаємося запустити сервер https
startTLS()
// Запускаємо http сервер
log.Printf("Serving http on -addr=%q", *addr)
err := fasthttp.ListenAndServe(*addr, handler)
if err != nil {
log.Fatalf("error in ListenAndServe: %s", err)
}
}
func startTLS() {
if len(*tlsAddr) == 0 {
log.Printf("-tlsAddr is empty, so skip serving https")
return
}
// Читаємо TLS сертифікат з файлу
cert, err := tls.LoadX509KeyPair(*tlsCertFile, *tlsKeyFile)
if err != nil {
log.Fatalf("cannot load cert for -tlsCertFile=%q, -tlsKeyFile=%q: %s"
*tlsCertFile, *tlsKeyFile, err)
}
// Створюємо net.Listener'а, який приймає підключення по -tlsAddr.
ln, err := net.Listen("tcp4", *tlsAddr)
if err != nil {
log.Fatalf("cannot listen for -tlsAddr=%q: %s", *tlsAddr, err)
}
// Створюємо потрібну конфігурацію tls.
// См. https://blog.gopheracademy.com/advent-2016/exposing-go-on-the-internet/ .
tlsConfig := tls.Config{
PreferServerCipherSuites: true,
CurvePreferences: []tls.CurveID{
tls.CurveP256,
tls.X25519,
},
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
Certificates: []tls.Certificate{cert},
}
// Створюємо net.Listener'а для підключень tls поверх створеного
// вище net.Listener'а
tlsLn := tls.NewListener(ln, &tlsConfig)
// запускаємо сервер https в окремому потоці
log.Printf("Serving https on -tlsAddr=%q", *tlsAddr)
go fasthttp.Serve(tlsLn, handler)
}
func handler(ctx *fasthttp.RequestCtx) {
ctx.WriteString("Hello, world!
")
}
Тепер при вказуванні параметрів -tlsAddr , -tlsCertFile і -tlsKeyFile сервер буде приймати https-запитів -tlsAddr додатково до http-запитами на -addr . І знову ніяких nginx'ов c apache'ами і openssl'ами не потрібно. Швидкість обробки https-трафіку сервером на Go порівнянна зі швидкістю nginx , тому перед ним не потрібно ставити TLS termination proxy .
Автоматизуємо безкоштовне отримання і оновлення TLS-сертифікатів
Багато вже чули про чудовий сервіс letsencrypt.org , який видає всім бажаючим безкоштовні TLS-сертифікати. І ці сертифікати визнаються всіма сучасними браузерами. Нижче показано, наскільки просто додати підтримку автоматичного отримання і оновлення TLS-сертифікатів letsencrypt.org в наш сервер на Go:
package main
import (
"crypto/tls"
"flag"
"github.com/valyala/fasthttp"
"golang.org/x/crypto/acme/autocert"
"log"
"net"
)
var (
addr = flag.String("addr", "127.0.0.1:8080", "TCP address to listen to for http")
tlsAddr = flag.String("tlsAddr", "", "TCP address to listen to for https")
tlsCertFile = flag.String("tlsCertFile", "", "Path to TLS certificate file. "+
"The certificate is automatically generated and put "+
"to -autocertCacheDir if empty")
tlsKeyFile = flag.String("tlsKeyFile", "", "Path to TLS key file. "+
"The key is automatically generated and put "+
"to -autocertCacheDir if empty")
autocertCacheDir = flag.String("autocertCacheDir", "autocert-cache",
"Path to the directory where letsencrypt certs are cached")
)
func main() {
flag.Parse()
// Намагаємося запустити сервер https
startTLS()
// Запускаємо http сервер
log.Printf("Serving http on -addr=%q", *addr)
err := fasthttp.ListenAndServe(*addr, handler)
if err != nil {
log.Fatalf("error in ListenAndServe: %s", err)
}
}
func startTLS() {
if len(*tlsAddr) == 0 {
log.Printf("-tlsAddr is empty, so skip serving https")
return
}
// Створюємо net.Listener'а, який приймає підключення по -tlsAddr.
ln, err := net.Listen("tcp4", *tlsAddr)
if err != nil {
log.Fatalf("cannot listen for -tlsAddr=%q: %s", *tlsAddr, err)
}
// Створюємо потрібну конфігурацію tls.
// См. https://blog.gopheracademy.com/advent-2016/exposing-go-on-the-internet/ .
tlsConfig := tls.Config{
PreferServerCipherSuites: true,
CurvePreferences: []tls.CurveID{
tls.CurveP256,
tls.X25519,
},
}
if len(*tlsCertFile) > 0 {
// Читаємо TLS сертифікат з файлу
cert, err := tls.LoadX509KeyPair(*tlsCertFile, *tlsKeyFile)
if err != nil {
log.Fatalf("cannot load cert for -tlsCertFile=%q, -tlsKeyFile=%q: %s", *tlsCertFile, *tlsKeyFile, err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
} else {
// Налаштовуємо автоматичне створення і оновлення сертифікатів.
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
// Сертифікати будуть кешуватися в -autocertCacheDir,
// щоб при рестарт сервера не доводилося
// перестворювати їх знову.
Cache: autocert.DirCache(*autocertCacheDir),
}
tlsConfig.GetCertificate = m.GetCertificate
}
// Створюємо net.Listener'а для підключень tls поверх створеного
// вище net.Listener'а
tlsLn := tls.NewListener(ln, &tlsConfig)
// запускаємо сервер https в окремому потоці
log.Printf("Serving https on -tlsAddr=%q", *tlsAddr)
go fasthttp.Serve(tlsLn, handler)
}
func handler(ctx *fasthttp.RequestCtx) {
ctx.WriteString("Hello, world!
")
}
Перед компіляцією даного коду знадобиться завантажити ще один сторонній пакет — golang.org/x/crypto/acme/autocert , який відповідає за автоматичне створення і оновлення TLS-сертифікатів:
$ go get -u golang.org/x/crypto/acme/autocert
Тепер сервер буде автоматично створювати і оновлювати TLS-сертифікати для всіх hostname'ів, запитаних за адресою https -tlsAddr , якщо не зазначено -tlsCertFile . Виписані сертифікати будуть кешуватися в каталозі -autocertCacheDir .
Висновок
Як ви могли переконатися, на Go можна легко і невимушено створювати самодостатні високопродуктивні http і https-сервери, яким не потрібні ніякі залежності, включаючи Apache, nginx і OpenSSL. Код виходить лаконічним і простим, без зайвих абстракцій і xml-конфіги.
У статті розглянуто найпростіший http-сервер, який видає «Hello, world!». На Go можна створювати сервера проксі з набагато більш складною логікою. В якості прикладу рекомендую оцінити простий у використанні http-проксі, балансувальник навантаження і TLS termination проксі, який також вміє економити трафік — httptp .
Пропонуйте в коментарях теми Go для наступних статей.
Опубліковано: 17/05/17 @ 10:00
Розділ Хостинг
Рекомендуємо:
DOU Проектор: Raccoon.world - гаджети для взаємодії з цифровою реальністю
DevOps дайджест #13: Docker поплив, біля Go водяться дракони
DOU Books: 5 книг по програмуванню, які радить Денис Шевченко, Director of Technology в Plarium
Якщо ви не вмієте говорити «ні», то ваше «так» нічого не значить
Якщо ви не вмієте говорити "ні", то ваше "так" нічого не значить