Pengembangan server web di Golang - dari yang sederhana hingga yang rumit

Pengembangan server web di Golang - dari yang sederhana hingga yang rumit

Lima tahun lalu saya memulainya mengembangkan Gophish, ini memberikan kesempatan untuk mempelajari Golang. Saya menyadari bahwa Go adalah bahasa yang kuat, dilengkapi dengan banyak perpustakaan. Go serbaguna: khususnya, dapat digunakan untuk mengembangkan aplikasi sisi server tanpa masalah.

Artikel ini tentang menulis server di Go. Mari kita mulai dengan hal sederhana seperti "Halo dunia!" dan diakhiri dengan aplikasi dengan kemampuan berikut:

- Menggunakan Let's Encrypt untuk HTTPS.
— Bekerja sebagai router API.
— Bekerja dengan middleware.
— Pemrosesan file statis.
— Shutdown yang benar.

Skillbox merekomendasikan: Tentu saja praktis "Pengembang Python dari awal".

Kami mengingatkan: untuk semua pembaca "Habr" - diskon 10 rubel saat mendaftar di kursus Skillbox apa pun menggunakan kode promosi "Habr".

Halo dunia!

Anda dapat membuat server web di Go dengan sangat cepat. Berikut adalah contoh penggunaan handler yang mengembalikan pesan “Halo, dunia!” yang dijanjikan di atas.

package main
 
import (
"fmt"
"net/http"
)
 
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
http.ListenAndServe(":80", nil)
}

Setelah ini, jika Anda menjalankan aplikasi dan membuka halaman localhost, maka Anda akan langsung melihat teks “Halo, dunia!” (jika semuanya berfungsi dengan benar, tentu saja).

Kita akan menggunakan handler beberapa kali nanti, tapi pertama-tama mari kita pahami cara kerja semuanya.

bersih/http

Contohnya menggunakan paket net/http, ini adalah alat utama di Go untuk mengembangkan server dan klien HTTP. Untuk memahami kodenya, mari kita pahami arti dari tiga elemen penting: http.Handler, http.ServeMux dan http.Server.

Penangan HTTP

Saat kami menerima permintaan, pengendali menganalisisnya dan menghasilkan respons. Penangan di Go diimplementasikan sebagai berikut:

type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

Contoh pertama menggunakan fungsi pembantu http.HandleFunc. Itu membungkus fungsi lain, yang pada gilirannya mengambil http.ResponseWriter dan http.Request ke dalam ServeHTTP.

Dengan kata lain, handler di Golang disajikan dalam satu antarmuka, yang memberikan banyak pilihan kepada programmer. Jadi, misalnya, middleware diimplementasikan menggunakan sebuah handler, di mana ServeHTTP terlebih dahulu melakukan sesuatu dan kemudian memanggil metode ServeHTTP dari handler lain.

Seperti disebutkan di atas, penangan hanya menghasilkan respons terhadap permintaan. Namun penangan mana yang sebaiknya digunakan pada waktu tertentu?

Permintaan Perutean

Untuk membuat pilihan yang tepat, gunakan multiplexer HTTP. Di beberapa perpustakaan disebut muxer atau router, tetapi semuanya sama. Fungsi multiplekser adalah menganalisis jalur permintaan dan memilih penangan yang sesuai.

Jika Anda memerlukan dukungan untuk perutean yang kompleks, lebih baik menggunakan perpustakaan pihak ketiga. Beberapa yang paling canggih - gorila/mux и pergi-chi/chi, perpustakaan ini memungkinkan penerapan pemrosesan perantara tanpa masalah apa pun. Dengan bantuan mereka, Anda dapat mengonfigurasi perutean wildcard dan melakukan sejumlah tugas lainnya. Keuntungannya adalah kompatibilitas dengan penangan HTTP standar. Hasilnya, Anda dapat menulis kode sederhana yang dapat dimodifikasi di masa mendatang.

Bekerja dengan kerangka kerja yang kompleks dalam situasi normal akan memerlukan solusi non-standar, dan ini secara signifikan mempersulit penggunaan penangan default. Untuk membuat sebagian besar aplikasi, kombinasi perpustakaan default dan router sederhana sudah cukup.

Pemrosesan Kueri

Selain itu, kita memerlukan komponen yang akan “mendengarkan” koneksi masuk dan mengarahkan semua permintaan ke pengendali yang benar. http.Server dapat dengan mudah menangani tugas ini.

Berikut ini menunjukkan bahwa server bertanggung jawab atas semua tugas yang terkait dengan pemrosesan koneksi. Misalnya, ini berfungsi menggunakan protokol TLS. Untuk mengimplementasikan panggilan http.ListenAndServer, server HTTP standar digunakan.

Sekarang mari kita lihat contoh yang lebih kompleks.

Menambahkan Mari Enkripsi

Secara default, aplikasi kita berjalan melalui protokol HTTP, namun disarankan untuk menggunakan protokol HTTPS. Ini dapat dilakukan tanpa masalah di Go. Jika Anda telah menerima sertifikat dan kunci pribadi, cukup mendaftarkan ListenAndServeTLS dengan sertifikat dan file kunci yang benar.

http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)

Anda selalu bisa melakukan yang lebih baik.

Mari Enkripsi memberikan sertifikat gratis dengan perpanjangan otomatis. Untuk menggunakan layanan ini, Anda memerlukan paket autocert.

Cara termudah untuk mengkonfigurasinya adalah dengan menggunakan metode autocert.NewListener yang dikombinasikan dengan http.Serve. Metode ini memungkinkan Anda memperoleh dan memperbarui sertifikat TLS saat server HTTP memproses permintaan:

http.Serve(autocert.NewListener("example.com"), nil)

Jika kita buka di browser example.com, kita akan menerima respons HTTPS “Halo, dunia!”

Jika Anda memerlukan konfigurasi yang lebih detail, maka Anda harus menggunakan manajer autocert.Manager. Kemudian kita membuat instance http.Server kita sendiri (sampai sekarang kita menggunakannya secara default) dan menambahkan manajer ke server TLSConfig:

m := &autocert.Manager{
Cache:      autocert.DirCache("golang-autocert"),
Prompt:     autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"),
}
server := &http.Server{
    Addr:      ":443",
    TLSConfig: m.TLSConfig(),
}
server.ListenAndServeTLS("", "")

Ini adalah cara mudah untuk menerapkan dukungan HTTPS penuh dengan perpanjangan sertifikat otomatis.

Menambahkan rute khusus

Router default yang disertakan dalam perpustakaan standar bagus, tetapi sangat mendasar. Sebagian besar aplikasi memerlukan perutean yang lebih kompleks, termasuk rute bersarang dan wildcard, atau prosedur untuk mengatur pola dan parameter jalur.

Dalam hal ini ada baiknya menggunakan paket gorila/mux и pergi-chi/chi. Kita akan belajar cara bekerja dengan yang terakhir - contohnya ditunjukkan di bawah.

Diberikan file api/v1/api.go yang berisi rute untuk API kita:

/ HelloResponse is the JSON representation for a customized message
type HelloResponse struct {
Message string `json:"message"`
}
 
// HelloName returns a personalized JSON message
func HelloName(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
response := HelloResponse{
Message: fmt.Sprintf("Hello %s!", name),
}
jsonResponse(w, response, http.StatusOK)
}
 
// NewRouter returns an HTTP handler that implements the routes for the API
func NewRouter() http.Handler {
r := chi.NewRouter()
r.Get("/{name}", HelloName)
return r
}

Kami menetapkan awalan api/vq untuk rute di file utama.

Kami kemudian dapat memasang ini ke router utama kami di bawah awalan api/v1/ kembali ke aplikasi utama kami:

// NewRouter returns a new HTTP handler that implements the main server routes
func NewRouter() http.Handler {
router := chi.NewRouter()
    router.Mount("/api/v1/", v1.NewRouter())
    return router
}
http.Serve(autocert.NewListener("example.com"), NewRouter())

Kemudahan Go dalam bekerja dengan rute yang kompleks memungkinkan untuk menyederhanakan penataan dan pemeliharaan aplikasi yang besar dan kompleks.

Bekerja dengan middleware

Pementasan melibatkan penggabungan satu penangan HTTP dengan penangan lainnya, sehingga memungkinkan untuk melakukan otentikasi, kompresi, pencatatan log, dan beberapa fungsi lainnya dengan cepat.

Sebagai contoh, mari kita lihat antarmuka http.Handler; kita akan menggunakannya untuk menulis sebuah handler yang mengautentikasi pengguna layanan.

func RequireAuthentication(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !isAuthenticated(r) {
            http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
            return
        }
        // Assuming authentication passed, run the original handler
        next.ServeHTTP(w, r)
    })
}

Ada router pihak ketiga, seperti chi, yang memungkinkan Anda memperluas fungsionalitas middleware.

Bekerja dengan file statis

Pustaka standar Go mencakup kemampuan untuk bekerja dengan konten statis, termasuk gambar, file JavaScript, dan CSS. Mereka dapat diakses melalui fungsi http.FileServer. Ia mengembalikan handler yang menyajikan file dari direktori tertentu.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Настройка раздачи статических файлов
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

Perlu diingat bahwa http.Dir menampilkan isi direktori jika tidak berisi file index.html utama. Dalam hal ini, untuk mencegah direktori disusupi, Anda harus menggunakan paket tersebut unindexed.

Shutdown yang benar

Go juga memiliki fitur yang disebut mematikan server HTTP dengan anggun. Hal ini dapat dilakukan dengan menggunakan metode Shutdown(). Server dimulai di goroutine, dan kemudian saluran didengarkan untuk menerima sinyal interupsi. Segera setelah sinyal diterima, server mati, tetapi tidak segera, tetapi setelah beberapa detik.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Wait for an interrupt
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Attempt a graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)

Sebagai kesimpulan

Go adalah bahasa yang kuat dengan perpustakaan standar yang hampir universal. Kemampuan defaultnya sangat luas, dan dapat ditingkatkan menggunakan antarmuka - ini memungkinkan Anda mengembangkan server HTTP yang benar-benar andal.

Skillbox merekomendasikan:

Sumber: www.habr.com

Tambah komentar