Développer des serveurs Web dans Golang - du plus simple au plus complexe

Développer des serveurs Web dans Golang - du plus simple au plus complexe

Il y a cinq ans, j'ai commencé développer Gophish, il a permis d'apprendre le Golang. J'ai réalisé que Go est un langage puissant, qui est complété par de nombreuses bibliothèques. Go est polyvalent : en particulier, vous pouvez facilement développer des applications côté serveur avec lui.

Cet article concerne l'écriture d'un serveur en Go. Commençons par des choses simples comme "Hello world !" et terminons avec une application avec ces fonctionnalités :

- Utilisation de Let's Encrypt pour HTTPS.
- Travailler en tant que routeur API.
- Travailler avec des intergiciels.
- Gestion des fichiers statiques.
- Arrêt correct.

Skillbox vous recommande : Cours pratique "Développeur Python à partir de zéro".

Nous rappelons: pour tous les lecteurs de "Habr" - une remise de 10 000 roubles lors de l'inscription à n'importe quel cours Skillbox en utilisant le code promotionnel "Habr".

Bonjour le monde!

La création d'un serveur Web dans Go peut être très rapide. Voici un exemple d'utilisation d'un gestionnaire qui renvoie le "Hello, world!" promis ci-dessus.

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

Après cela, si vous lancez l'application et ouvrez la page localhost, alors vous verrez immédiatement le texte "Hello, world!" (bien sûr, si tout fonctionne correctement).

Nous utiliserons le gestionnaire à plusieurs reprises dans ce qui suit, mais commençons par comprendre comment tout cela fonctionne.

réseau/http

L'exemple utilise le package net/http, est l'outil principal de Go pour développer à la fois des serveurs et des clients HTTP. Afin de comprendre le code, comprenons la signification de trois éléments importants : http.Handler, http.ServeMux et http.Server.

Gestionnaires HTTP

Lorsque nous recevons une requête, le gestionnaire l'analyse et génère une réponse. Les gestionnaires dans Go sont implémentés comme suit :

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

Le premier exemple utilise la fonction d'assistance http.HandleFunc. Il encapsule une autre fonction qui accepte à son tour http.ResponseWriter et http.Request dans ServeHTTP.

En d'autres termes, les gestionnaires de Golang sont représentés par une seule interface, ce qui offre de nombreuses possibilités au programmeur. Ainsi, par exemple, le middleware est implémenté à l'aide d'un gestionnaire, où ServeHTTP fait d'abord quelque chose, puis appelle la méthode ServeHTTP d'un autre gestionnaire.

Comme mentionné ci-dessus, les gestionnaires forment simplement des réponses aux demandes. Mais quel gestionnaire doit être utilisé à un moment donné ?

Routage des demandes

Pour faire le bon choix, utilisez le multiplexeur HTTP. Dans un certain nombre de bibliothèques, il est appelé multiplexeur ou routeur, mais ils sont tous identiques. La fonction du multiplexeur est d'analyser le chemin de la demande et de sélectionner le gestionnaire approprié.

Si vous avez besoin de support pour un routage complexe, il est préférable d'utiliser des bibliothèques tierces. L'un des plus avancés gorille/mux и go-chi/chi, ces bibliothèques permettent de mettre en œuvre sans problème des traitements intermédiaires. Avec leur aide, vous pouvez configurer un routage générique et effectuer un certain nombre d'autres tâches. Leur avantage est la compatibilité avec les gestionnaires HTTP standard. En conséquence, vous pouvez écrire un code simple qui peut être modifié à l'avenir.

Travailler avec des frameworks complexes dans une situation normale nécessitera des solutions pas tout à fait standard, ce qui complique grandement l'utilisation des gestionnaires par défaut. La combinaison de la bibliothèque par défaut et d'un simple routeur suffira à créer la grande majorité des applications.

Traitement des requêtes

De plus, nous avons besoin d'un composant qui "écoutera" les connexions entrantes et redirigera toutes les requêtes vers le bon gestionnaire. http.Server peut facilement faire face à cette tâche.

Ce qui suit montre que le serveur est responsable de toutes les tâches liées à la gestion des connexions. Il s'agit, par exemple, de travailler sur le protocole TLS. Un serveur HTTP standard est utilisé pour implémenter l'appel http.ListenAndServer.

Voyons maintenant des exemples plus complexes.

Ajout de Let's Encrypt

Par défaut, notre application fonctionne sur le protocole HTTP, mais il est recommandé d'utiliser le protocole HTTPS. En Go, cela peut se faire sans problème. Si vous avez reçu un certificat et une clé privée, il suffit d'écrire ListenAndServeTLS avec le certificat et les fichiers de clé corrects.

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

Vous pouvez toujours faire mieux.

Chiffrons donne des certificats gratuits avec possibilité de renouvellement automatique. Pour utiliser le service, vous avez besoin d'un forfait autocert.

Le moyen le plus simple de le configurer consiste à utiliser la méthode autocert.NewListener en combinaison avec http.Serve. La méthode vous permet de recevoir et de renouveler des certificats TLS pendant que le serveur HTTP traite les requêtes :

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

Si nous ouvrons dans le navigateur example.com, nous obtenons une réponse HTTPS "Hello, world!".

Si vous avez besoin d'une configuration plus détaillée, vous devez utiliser le autocert.Manager. Ensuite, nous créons notre propre instance http.Server (jusqu'à présent, nous l'avons utilisée par défaut) et ajoutons le gestionnaire au serveur 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("", "")

Il s'agit d'un moyen simple d'implémenter une prise en charge HTTPS complète avec renouvellement automatique des certificats.

Ajout d'itinéraires personnalisés

Le routeur par défaut inclus dans la bibliothèque standard est sympa, mais très basique. La plupart des applications nécessitent un routage plus complexe, y compris des itinéraires imbriqués et génériques, ou la définition de modèles et de paramètres de chemin.

Dans ce cas, vous devez utiliser des packages gorille/mux и go-chi/chi. Nous allons apprendre à travailler avec ce dernier - un exemple est présenté ci-dessous.

Le fichier api/v1/api.go contenant les routes pour notre API est donné :

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

Nous définissons le préfixe api/vq pour les routes dans le fichier principal.

Nous pouvons ensuite le monter sur notre routeur principal sous le préfixe api/v1/ dans notre application principale :

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

La facilité de travailler avec des routes complexes dans Go permet de simplifier la structuration et la maintenance de grandes applications complexes.

Travailler avec le middleware

Dans le cas d'un traitement intermédiaire, l'encapsulation d'un gestionnaire HTTP avec un autre est utilisée, ce qui permet d'effectuer rapidement l'authentification, la compression, la journalisation et certaines autres fonctions.

À titre d'exemple, considérons l'interface http.Handler, avec son aide, nous allons écrire un gestionnaire avec authentification des utilisateurs du service.

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

Il existe des routeurs tiers, tels que chi, qui vous permettent d'étendre les fonctionnalités du traitement intermédiaire.

Travailler avec des fichiers statiques

La bibliothèque standard de Go comprend des fonctionnalités permettant de travailler avec du contenu statique, y compris des images, ainsi que des fichiers JavaScript et CSS. Ils sont accessibles via la fonction http.FileServer. Il renvoie un gestionnaire qui distribue les fichiers à partir d'un répertoire spécifique.

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

N'oubliez pas que http.Dir affiche le contenu du répertoire s'il ne contient pas le fichier index.html principal. Dans ce cas, afin d'éviter que le répertoire ne soit compromis, vous devez utiliser le package unindexed.

Arrêt correct

Go a également une fonctionnalité telle qu'un arrêt progressif du serveur HTTP. Cela peut être fait en utilisant la méthode Shutdown(). Le serveur est démarré dans une goroutine, puis le canal est écouté pour recevoir un signal d'interruption. Dès que le signal est reçu, le serveur s'éteint, mais pas immédiatement, mais après quelques secondes.

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)

En conclusion

Go est un langage puissant avec une bibliothèque standard presque universelle. Ses capacités par défaut sont très larges et vous pouvez les renforcer à l'aide d'interfaces - cela vous permet de développer des serveurs HTTP vraiment fiables.

Skillbox vous recommande :

Source: habr.com

Ajouter un commentaire