Tworzenie serwerów WWW w Golang - od prostych do złożonych

Tworzenie serwerów WWW w Golang - od prostych do złożonych

Zacząłem pięć lat temu rozwijaj Gophishadało to okazję do nauki Golangu. Zdałem sobie sprawę, że Go to potężny język, uzupełniony wieloma bibliotekami. Go jest wszechstronny: w szczególności można go bez problemu używać do tworzenia aplikacji serwerowych.

Ten artykuł dotyczy pisania serwera w Go. Zacznijmy od prostych rzeczy, takich jak „Witaj, świecie!”, a zakończmy aplikacją o następujących możliwościach:

- Korzystanie z Let's Encrypt dla HTTPS.
— Praca jako router API.
— Praca z oprogramowaniem pośredniczącym.
— Przetwarzanie plików statycznych.
— Prawidłowe wyłączenie.

Skillbox poleca: Kurs praktyczny „Programista Pythona od zera”.

Przypomnienie: dla wszystkich czytelników „Habr” - rabat w wysokości 10 000 rubli przy zapisywaniu się na dowolny kurs Skillbox przy użyciu kodu promocyjnego „Habr”.

Witaj świecie!

Możesz bardzo szybko utworzyć serwer WWW w Go. Oto przykład użycia procedury obsługi, która zwraca obiecane powyżej „Hello, world!”.

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

Następnie, jeśli uruchomisz aplikację i otworzysz stronę localhost, wtedy od razu zobaczysz tekst „Hello, world!” (oczywiście jeśli wszystko działa poprawnie).

Użyjemy procedury obsługi wiele razy później, ale najpierw zrozummy, jak wszystko działa.

sieć/http

W przykładzie użyto pakietu net/http, jest to podstawowe narzędzie w Go do tworzenia zarówno serwerów, jak i klientów HTTP. Aby zrozumieć kod, przyjrzyjmy się znaczeniu trzech ważnych elementów: http.Handler, http.ServeMux i http.Server.

Procedury obsługi HTTP

Kiedy otrzymamy żądanie, handler analizuje je i generuje odpowiedź. Procedury obsługi w Go są zaimplementowane w następujący sposób:

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

W pierwszym przykładzie użyto funkcji pomocniczej http.HandleFunc. Opakowuje inną funkcję, która z kolei przenosi http.ResponseWriter i http.Request do ServeHTTP.

Innymi słowy, procedury obsługi w Golangu są prezentowane w jednym interfejsie, który daje programiście wiele opcji. Na przykład oprogramowanie pośrednie jest implementowane przy użyciu procedury obsługi, w której ServeHTTP najpierw coś wykonuje, a następnie wywołuje metodę ServeHTTP innego modułu obsługi.

Jak wspomniano powyżej, programy obsługi po prostu generują odpowiedzi na żądania. Ale jakiego konkretnego modułu obsługi należy użyć w określonym momencie?

Poproś o routing

Aby dokonać właściwego wyboru, użyj multipleksera HTTP. W wielu bibliotekach nazywa się to muxerem lub routerem, ale wszystkie oznaczają to samo. Zadaniem multipleksera jest analiza ścieżki żądania i wybór odpowiedniej procedury obsługi.

Jeśli potrzebujesz wsparcia dla złożonego routingu, lepiej skorzystać z bibliotek innych firm. Niektóre z najbardziej zaawansowanych - goryl/mux и go-chi/chi, biblioteki te umożliwiają bezproblemową realizację przetwarzania pośredniego. Za ich pomocą możesz skonfigurować routing z symbolami wieloznacznymi i wykonać szereg innych zadań. Ich zaletą jest kompatybilność ze standardowymi procedurami obsługi HTTP. Dzięki temu można napisać prosty kod, który można w przyszłości modyfikować.

Praca ze złożonymi frameworkami w normalnej sytuacji będzie wymagała niestandardowych rozwiązań, a to znacznie komplikuje użycie domyślnych procedur obsługi. Do stworzenia zdecydowanej większości aplikacji wystarczy połączenie domyślnej biblioteki i prostego routera.

Przetwarzanie zapytań

Dodatkowo potrzebujemy komponentu, który będzie „nasłuchiwał” połączeń przychodzących i przekierowywał wszystkie żądania do odpowiedniego modułu obsługi. http.Server bez problemu poradzi sobie z tym zadaniem.

Z poniższego wynika, że ​​serwer odpowiada za wszystkie zadania związane z przetwarzaniem połączenia. Działa to na przykład przy użyciu protokołu TLS. Do realizacji wywołania http.ListenAndServer wykorzystywany jest standardowy serwer HTTP.

Przyjrzyjmy się teraz bardziej złożonym przykładom.

Dodanie Let's Encrypt

Domyślnie nasza aplikacja działa w oparciu o protokół HTTP, jednak zaleca się korzystanie z protokołu HTTPS. Można to zrobić bez problemów w Go. Jeśli otrzymałeś certyfikat i klucz prywatny, wystarczy zarejestrować ListenAndServeTLS z odpowiednim certyfikatem i plikami kluczy.

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

Zawsze możesz zrobić lepiej.

Zakodujmy zapewnia bezpłatne certyfikaty z automatycznym odnawianiem. Aby skorzystać z usługi potrzebny jest pakiet autocert.

Najprostszym sposobem skonfigurowania jest użycie metody autocert.NewListener w połączeniu z http.Serve. Metoda pozwala na uzyskanie i aktualizację certyfikatów TLS podczas przetwarzania żądań przez serwer HTTP:

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

Jeśli otworzymy w przeglądarce example.com, otrzymamy odpowiedź HTTPS „Hello, world!”

Jeśli potrzebujesz bardziej szczegółowej konfiguracji, powinieneś skorzystać z menedżera autocert.Manager. Następnie tworzymy własną instancję http.Server (do tej pory korzystaliśmy z niej domyślnie) i dodajemy menadżera do serwera 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("", "")

Jest to łatwy sposób na wdrożenie pełnej obsługi HTTPS z automatycznym odnawianiem certyfikatów.

Dodawanie niestandardowych tras

Domyślny router zawarty w standardowej bibliotece jest dobry, ale jest bardzo prosty. Większość aplikacji wymaga bardziej złożonego routingu, w tym tras zagnieżdżonych i wieloznacznych lub procedury ustawiania wzorców i parametrów ścieżek.

W tym przypadku warto skorzystać z pakietów goryl/mux и go-chi/chi. Z tym ostatnim nauczymy się pracować – przykład poniżej.

Podano plik api/v1/api.go zawierający trasy dla naszego API:

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

Ustawiamy prefiks api/vq dla tras w pliku głównym.

Następnie możemy zamontować to na naszym głównym routerze pod prefiksem api/v1/ z powrotem w naszej głównej aplikacji:

// 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())

Łatwość pracy Go ze złożonymi trasami umożliwia uproszczenie strukturyzacji i konserwacji dużych, złożonych aplikacji.

Praca z oprogramowaniem pośrednim

Staging polega na opakowaniu jednego modułu obsługi HTTP innym, co umożliwia szybkie wykonanie uwierzytelniania, kompresji, rejestrowania i kilku innych funkcji.

Jako przykład przyjrzyjmy się interfejsowi http.Handler, użyjemy go do napisania procedury obsługi uwierzytelniającej użytkowników usługi.

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

Istnieją routery innych firm, takie jak chi, które umożliwiają rozszerzenie funkcjonalności oprogramowania pośredniego.

Praca z plikami statycznymi

Standardowa biblioteka Go zawiera możliwości pracy z zawartością statyczną, w tym obrazami, plikami JavaScript i CSS. Dostęp do nich można uzyskać poprzez funkcję http.FileServer. Zwraca moduł obsługi obsługujący pliki z określonego katalogu.

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

Na pewno warto pamiętać, że http.Dir wyświetla zawartość katalogu jeśli nie zawiera on głównego pliku Index.html. W takim przypadku, aby zapobiec włamaniu do katalogu, powinieneś użyć pakietu unindexed.

Prawidłowe wyłączenie

Go ma także funkcję zwaną łagodnym zamykaniem serwera HTTP. Można to zrobić za pomocą metody Shutdown(). Serwer jest uruchamiany w goroutine, a następnie kanał jest nasłuchiwany w celu otrzymania sygnału przerwania. Gdy tylko sygnał zostanie odebrany, serwer się wyłącza, ale nie od razu, ale po kilku sekundach.

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)

Na zakończenie

Go to potężny język z niemal uniwersalną biblioteką standardową. Jego domyślne możliwości są bardzo szerokie i można je rozszerzać za pomocą interfejsów - pozwala to na tworzenie naprawdę niezawodnych serwerów HTTP.

Skillbox poleca:

Źródło: www.habr.com

Dodaj komentarz