DĂ©veloppement de serveurs web en Golang — de simple Ă  complexe

DĂ©veloppement de serveurs web en Golang — de simple Ă  complexe

Il y a cinq ans, j'ai commencé à développer Gophish, ce qui m'a permis d'explorer Golang. J'ai réalisé que Go est un langage puissant, dont les possibilités sont complétées par de nombreuses bibliothÚques. Go est polyvalent : en particulier, il permet de développer sans problÚme des applications serveur.

Cet article est consacré à l'écriture d'un serveur en Go. Nous commencerons par des choses simples, comme « Bonjour monde ! », et nous finirons avec une application ayant les fonctionnalités suivantes :

— Utilisation de Let’s Encrypt pour HTTPS.
— Fonctionnement en tant que routeur API.
— Gestion de middleware.
— Traitement de fichiers statiques.
— ArrĂȘt propre.

Skillbox recommande : Cours pratique « Développeur Python depuis zéro ».

Rappelons-le : pour tous les lecteurs de « Habr » — une rĂ©duction de 10 000 roubles lors de l'inscription Ă  tout cours Skillbox avec le code promo « Habr ».

Hello, world!

Créer un serveur web en Go peut se faire trÚs rapidement. Voici un exemple d'utilisation d'un gestionnaire qui renvoie le fameux « Hello, world ! » mentionné 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, vous verrez immédiatement le texte « Hello, world ! » (bien sûr, si tout fonctionne correctement).

Ensuite, nous utiliserons Ă  plusieurs reprises le gestionnaire, mais d'abord, comprenons comment tout cela fonctionne.

net/http

L'exemple utilise le paquet net/http, qui est l'outil principal en Go pour le développement aussi bien de serveurs que de clients HTTP. Pour comprendre le code, examinons le sens de trois éléments importants : http.Handler, http.ServeMux et http.Server.

Les gestionnaires HTTP

Lorsque nous recevons une requĂȘte, le gestionnaire l'analyse et forme une rĂ©ponse. Les gestionnaires en Go sont implĂ©mentĂ©s de la maniĂšre suivante :

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

Dans le premier exemple, nous utilisons la fonction d'assistance http.HandleFunc. Elle enveloppe une autre fonction qui, Ă  son tour, prend http.ResponseWriter et http.Request dans ServeHTTP.

En d'autres termes, les gestionnaires en Golang sont reprĂ©sentĂ©s par une interface unique, ce qui offre de nombreuses possibilitĂ©s au programmeur. 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Ă© prĂ©cĂ©demment, les gestionnaires ne font que former des rĂ©ponses aux requĂȘtes. Mais quel gestionnaire faut-il utiliser Ă  un moment donnĂ© ?

Routage des requĂȘtes

Pour faire le bon choix, utilisez un multiplexeur HTTP. Dans plusieurs bibliothĂšques, il est appelĂ© muxer ou router, mais tout cela revient au mĂȘme. La fonction du multiplexeur consiste Ă  analyser le chemin de la requĂȘte et Ă  choisir le gestionnaire appropriĂ©.

Si vous avez besoin d'un support pour un routage complexe, il est préférable d'utiliser des bibliothÚques tierces. Parmi les plus avancées, on trouve gorilla/mux et go-chi/chi, ces bibliothÚques permettent d'implémenter un traitement intermédiaire sans trop de problÚmes. Grùce à elles, vous pouvez configurer le routage wildcard et accomplir plusieurs autres tùches. Leur avantage est la compatibilité avec les gestionnaires HTTP standards. En conséquence, vous pouvez écrire un code simple avec la possibilité de le modifier à l'avenir.

Travailler avec des frameworks complexes dans des situations normales nécessitera des solutions non standard, ce qui complique considérablement l'utilisation des gestionnaires par défaut. Pour créer la majorité des applications, une combinaison de la bibliothÚque par défaut et d'un routeur simple suffira.

Traitement des requĂȘtes

De plus, nous avons besoin d'un composant qui va « Ă©couter » les connexions entrantes et rediriger toutes les requĂȘtes vers le bon gestionnaire. Cette tĂąche est facilement gĂ©rĂ©e par http.Server.

Ci-dessous, il est montré que le serveur est responsable de toutes les tùches relatives au traitement des connexions. Cela inclut, par exemple, le fonctionnement avec le protocole TLS. Pour implémenter l'appel http.ListenAndServer, nous utilisons le serveur HTTP standard.

Examinons maintenant des exemples plus complexes.

Ajouter Let’s Encrypt

Par défaut, notre application fonctionne avec le protocole HTTP, mais il est recommandé d'utiliser le protocole HTTPS. En Go, cela peut se faire sans problÚme. Si vous avez obtenu un certificat et une clé privée, il suffit d'appeler ListenAndServeTLS en indiquant les bons fichiers de certificat et de clé.

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

On peut toujours faire mieux.

Let's Encrypt fournit des certificats gratuits avec la possibilité de mise à jour automatique. Pour utiliser le service, vous avez besoin du paquet autocert.

Le moyen le plus simple de le configurer est d’utiliser la mĂ©thode autocert.NewListener en combinaison avec http.Serve. La mĂ©thode permet d’obtenir et de mettre Ă  jour des certificats TLS, tandis que le serveur HTTP traite les requĂȘtes :

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

Si nous ouvrons dans le navigateur example.com, nous obtiendrons la réponse HTTPS « Hello, world! ».

Si vous avez besoin d'une configuration plus minutieuse, il vaut mieux utiliser le gestionnaire 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 dans la configuration TLS du serveur :

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("", "")

C'est une mĂ©thode simple pour mettre en Ɠuvre un support complet de HTTPS avec renouvellement automatique du certificat.

Ajout de routes personnalisées

Le routeur par défaut inclus dans la bibliothÚque standard est bon, mais il est trÚs simple. La plupart des applications nécessitent une routage plus complexe, y compris des routes imbriquées et des routes wildcard ou une procédure d'établissement de motifs et de paramÚtres de chemins.

Dans ce cas, il vaut mieux utiliser les paquets gorilla/mux et go-chi/chi. Avec le dernier, nous apprendrons à travailler — ci-dessous un exemple.

DonnĂ© — le fichier api/v1/api.go, contenant les routes pour notre 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
}

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

Nous pouvons alors 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 simplicité de traitement des routes complexes en Go permet de simplifier la structuration pour le maintien de grandes applications complexes.

Travailler avec le middleware

Dans le cas du traitement intermédiaire, un gestionnaire HTTP est enveloppé dans un autre, ce qui permet d'effectuer rapidement l'authentification, la compression, la journalisation et quelques autres fonctions.

À titre d'exemple, considĂ©rons l'interface http.Handler, avec laquelle nous allons Ă©crire un gestionnaire d'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
        }
        \/\/ En supposant que l'authentification réussie, exécutez le gestionnaire original
        next.ServeHTTP(w, r)
    })
}

Il existe des routeurs tiers, comme chi, qui permettent d'élargir la fonctionnalité du traitement intermédiaire.

Travailler avec des fichiers statiques

La bibliothÚque standard de Go inclut des fonctionnalités pour le traitement de contenu statique, y compris des images, ainsi que des fichiers JavaScript et CSS. On peut y accéder via la fonction http.FileServer. Elle retourne un gestionnaire qui distribue des fichiers depuis un répertoire spécifique.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Configuration for serving static files
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

Il est important de se rappeler que http.Dir affiche le contenu du répertoire si le fichier index.html principal n'est pas présent. Dans ce cas, pour éviter toute compromission du répertoire, il convient d'utiliser le paquet non indexé.

ArrĂȘt correct

En Go, il existe Ă©galement une fonction permettant la fermeture correcte d'un serveur HTTP. Cela peut se faire Ă  l'aide de la mĂ©thode Shutdown(). Le serveur fonctionne dans une goroutine, et ensuite, un canal Ă©coute pour recevoir un signal d'interruption. DĂšs que le signal est reçu, le serveur s'arrĂȘte, mais pas immĂ©diatement, aprĂšs quelques secondes.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Attendre un signal d'interruption
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Essayer un arrĂȘt gracieux
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 assez larges, et on peut les renforcer grĂące Ă  des interfaces — ce qui permet de dĂ©velopper des serveurs HTTP vraiment fiables.

Skillbox recommande :

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster