
Pięć lat temu zacząłem , co dało mi możliwość nauczenia się Golanga. Zrozumiałem, że Go to potężny język, którego możliwości są wzbogacane przez liczne biblioteki. Go jest wszechstronny: w szczególności umożliwia bezproblemowe opracowywanie aplikacji serwerowych.
Ten artykuł poświęcony jest tworzeniu serwera w Go. Zacznijmy od prostych rzeczy, jak „Hello world!”, a skończymy na aplikacji z następującymi funkcjami:
— Użycie Let’s Encrypt do HTTPS.
— Działa jako router API.
— Praca z middleware.
— Obsługa plików statycznych.
— Poprawne zakończenie pracy.
Skillbox poleca: Praktyczny kurs .
Przypominamy: dla wszystkich czytelników „Habra” — zniżka 10 000 rubli przy zapisie na dowolny kurs Skillbox z kodem promocyjnym „Habra”.
Witaj, świecie!
Stworzenie serwera WWW w Go jest bardzo szybkie. Oto przykład użycia handlera, który zwraca obiecane wyżej „Witaj, świecie!”.
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)
}Po tym, jeśli uruchomisz aplikację i otworzysz stronę , od razu zobaczysz tekst „Witaj, świecie!” (oczywiście, jeśli wszystko działa poprawnie).
Następnie wielokrotnie będziemy korzystać z handlera, ale najpierw zrozummy, jak to wszystko działa.
net/http
W przykładzie użyto pakietu , co jest podstawowym narzędziem w Go do tworzenia zarówno serwerów, jak i klientów HTTP. Aby zrozumieć kod, rozważmy znaczenie trzech ważnych elementów: http.Handler, http.ServeMux i http.Server.
HTTP-handlery
Gdy otrzymujemy zapytanie, handler je analizuje i formułuje odpowiedź. Handlery 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. Owrapowuje ona inną funkcję, która z kolei przyjmuje http.ResponseWriter i http.Request w ServeHTTP.
Innymi słowy, handlery w Golangu są reprezentowane przez jeden interfejs, co daje programiście liczne możliwości. Na przykład middleware jest wdrażane za pomocą handlera, gdzie ServeHTTP najpierw coś robi, a potem wywołuje metodę ServeHTTP innego handlera.
Jak wspomniano powyżej, handlery po prostu formułują odpowiedzi na zapytania. Ale który konkretnie handler należy użyć w danym momencie?
Trasowanie zapytań
Aby dokonać właściwego wyboru, skorzystaj z HTTP-multiplexer. W wielu bibliotekach nazywa się go muxer lub router, ale wszystko to jest to samo. Funkcja multiplexer polega na analizie ścieżki zapytania i wyborze odpowiedniego obsługującego.
Jeśli jednak potrzebujesz wsparcia dla zaawansowanego routingu, lepiej skorzystać z zewnętrznych bibliotek. Niektóre z najbardziej zaawansowanych to i , te biblioteki umożliwiają realizację przetwarzania pośredniego bez większych problemów. Dzięki nim można skonfigurować routing z wildcardami i wykonać szereg innych zadań. Ich zaletą jest kompatybilność ze standardowymi HTTP-handlerami. W rezultacie możesz napisać prosty kod z możliwością jego modyfikacji w przyszłości.
Praca z bardziej złożonymi frameworkami w zwykłej sytuacji wymagać będzie niezbyt standardowych rozwiązań, co znacznie komplikuje korzystanie z domyślnych obsługujących. Do stworzenia zdecydowanej większości aplikacji wystarczy kombinacja domyślnej biblioteki oraz prostego routera.
Obsługa zapytań
Ponadto potrzebujemy komponentu, który będzie 'wsłuchiwał się' w przychodzące połączenia i przekierowywał wszystkie zapytania do odpowiedniego obsługującego. Z tym zadaniem bez trudu poradzi sobie http.Server.
Poniżej widać, że serwer odpowiada za wszystkie zadania związane z obsługą połączeń. Należy do nich na przykład praca z protokołem TLS. Standardowy serwer HTTP jest wykorzystywany do realizacji wywołania http.ListenAndServer.
Teraz przyjrzyjmy się bardziej złożonym przykładom.
Dodawanie Let’s Encrypt
Domyślnie nasze aplikacje działają w protokole HTTP, jednak zaleca się korzystanie z protokołu HTTPS. W Go można to zrobić bez problemów. Jeśli uzyskałeś certyfikat i klucz prywatny, wystarczy wskazać ListenAndServeTLS z odpowiednimi plikami certyfikatu i klucza.
http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)Zawsze można zrobić lepiej.
oferuje bezpłatne certyfikaty z możliwością automatycznego odnawiania. Aby skorzystać z usługi, potrzebny jest pakiet autocert.
Najłatwiejszym sposobem jego skonfigurowania jest skorzystanie z metody autocert.NewListener w kombinacji z http.Serve. Metoda ta pozwala uzyskiwać i odnawiać certyfikaty TLS, podczas gdy serwer HTTP obsługuje zapytania:
http.Serve(autocert.NewListener("example.com"), nil)Jeśli otworzymy w przeglądarce , otrzymamy odpowiedź HTTPS „Hello, world!”.
Jeśli potrzebna jest dokładniejsza konfiguracja, warto skorzystać z menedżera autocert.Manager. Następnie tworzymy własną instancję http.Server (dotychczas używaliśmy domyślnej) i dodajemy menedżera do TLSConfig serwera:
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("", "")To prosty sposób na wdrożenie pełnej obsługi HTTPS z automatycznym odnawianiem certyfikatu.
Dodawanie niestandardowych tras
Domyślny router zawarty w standardowej bibliotece jest dobry, ale bardzo prosty. W większości aplikacji potrzebne są bardziej skomplikowane trasy, w tym zagnieżdżone i wildcard, oraz procedura ustawiania szablonów i parametrów ścieżek.
W takim przypadku warto użyć pakietów i . Z ostatnim nauczymy się pracować — poniżej znajduje się przykład.
Dane — 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 dla tras prefiks api/v1 w głównym pliku.
Możemy następnie zamontować go do naszego głównego routera pod prefiksem api/v1 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 ze złożonymi trasami w Go umożliwia uproszczenie struktury w obsłudze dużych, złożonych aplikacji.
Praca z middleware
W przypadku przetwarzania pośredniego polega to na owijaniu jednego HTTP-handlera innym, co pozwala szybko przeprowadzić autoryzację, kompresję, logowanie oraz inne funkcje.
Jako przykład rozważmy interfejs http.Handler, którego użyjemy do napisania handlera z autoryzacją użytkowników serwisu.
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
}
\/\/ Zakładając, że autoryzacja przeszła, uruchom oryginalny handler
next.ServeHTTP(w, r)
})
}Są też zewnętrzne routery, na przykład chi, które pozwalają rozszerzyć funkcjonalność przetwarzania pośredniego.
Praca ze statycznymi plikami
Standardowa biblioteka Go zawiera możliwości pracy ze statycznymi treściami, w tym obrazami oraz plikami JavaScript i CSS. Można uzyskać do nich dostęp za pomocą funkcji http.FileServer. Zwraca ona handler, który udostępnia pliki z określonego katalogu.
func NewRouter() http.Handler {
router := chi.NewRouter()
r.Get("/{name}", HelloName)
// Konfiguracja serwera plików statycznych
staticPath, _ := filepath.Abs("../../../static/")
fs := http.FileServer(http.Dir(staticPath))
router.Handle("/*", fs)
return rNależy pamiętać, że http.Dir wyświetla zawartość katalogu tylko wtedy, gdy nie ma w nim głównego pliku index.html. W takim przypadku, aby uniknąć kompromitacji katalogu, warto użyć pakietu .
Poprawne zakończenie pracy
W Go istnieje również funkcja, która pozwala na poprawne zakończenie pracy serwera HTTP. Można to zrobić za pomocą metody Shutdown(). Serwer uruchamia się w gorutinie, po czym kanał nasłuchuje sygnału przerwania. Gdy tylko sygnał zostanie odebrany, serwer się wyłącza, jednak nie od razu, a po kilku sekundach.
handler := server.NewRouter()
srv := &http.Server{
Handler: handler,
}
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
// Czekaj na przerwanie
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
// Próba poprawnego zamknięcia
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ą standardową biblioteką. Jego domyślne możliwości są szerokie, a można je wzmocnić za pomocą interfejsów — co pozwala na tworzenie naprawdę niezawodnych serwerów HTTP.
Skillbox poleca:
- Dwuletni praktyczny kurs .
- Edukacyjny kurs online .
- Praktyczny roczny kurs .
Źródło: habr.com
