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
Якщо ви не вмієте говорити «ні», то ваше «так» нічого не значить
Якщо ви не вмієте говорити "ні", то ваше "так" нічого не значить