Ανάπτυξη διακομιστή Ιστού στο Golang - από απλό σε σύνθετο

Ανάπτυξη διακομιστή Ιστού στο Golang - από απλό σε σύνθετο

Πριν από πέντε χρόνια ξεκίνησα ανάπτυξη Gophish, κατέστησε δυνατή την εκμάθηση Golang. Συνειδητοποίησα ότι το Go είναι μια ισχυρή γλώσσα, που συμπληρώνεται από πολλές βιβλιοθήκες. Το Go είναι ευέλικτο: συγκεκριμένα, μπορεί να χρησιμοποιηθεί για την ανάπτυξη εφαρμογών από την πλευρά του διακομιστή χωρίς κανένα πρόβλημα.

Αυτό το άρθρο αφορά τη σύνταξη ενός διακομιστή στο Go. Ας ξεκινήσουμε με απλά πράγματα όπως το "Hello world!" και ας τελειώσουμε με μια εφαρμογή με τις ακόλουθες δυνατότητες:

- Χρήση Let's Encrypt για HTTPS.
- Εργαστείτε ως δρομολογητής API.
- Εργασία με ενδιάμεσο λογισμικό.
— Επεξεργασία στατικών αρχείων.
— Σωστό κλείσιμο.

Το Skillbox προτείνει: Πρακτικό μάθημα "Προγραμματιστής Python από την αρχή".

Υπενθύμιση: για όλους τους αναγνώστες του "Habr" - έκπτωση 10 ρούβλια κατά την εγγραφή σε οποιοδήποτε μάθημα Skillbox χρησιμοποιώντας τον κωδικό προσφοράς "Habr".

Γειά σου Κόσμε!

Η δημιουργία ενός διακομιστή ιστού στο Go μπορεί να είναι πολύ γρήγορη. Ακολουθεί ένα παράδειγμα χρήσης ενός χειριστή που επιστρέφει το "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)
}

Μετά από αυτό, εάν εκτελέσετε την εφαρμογή και ανοίξετε τη σελίδα localhost, τότε θα δείτε αμέσως το κείμενο "Γεια, κόσμο!" (αν όλα λειτουργούν σωστά, φυσικά).

Θα χρησιμοποιήσουμε το πρόγραμμα χειρισμού επανειλημμένα στη συνέχεια, αλλά πρώτα ας καταλάβουμε πώς λειτουργεί όλο αυτό.

net/http

Το παράδειγμα χρησιμοποιούσε το πακέτο net/http, είναι το κύριο εργαλείο στο Go για την ανάπτυξη τόσο διακομιστών όσο και πελατών HTTP. Για να κατανοήσουμε τον κώδικα, ας κατανοήσουμε την έννοια τριών σημαντικών στοιχείων: http.Handler, http.ServeMux και http.Server.

Χειριστές HTTP

Όταν λαμβάνουμε ένα αίτημα, ο χειριστής το αναλύει και δημιουργεί μια απάντηση. Οι χειριστές στο Go υλοποιούνται ως εξής:

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

Το πρώτο παράδειγμα χρησιμοποιεί τη βοηθητική συνάρτηση http.HandleFunc. Αναδιπλώνει μια άλλη συνάρτηση, η οποία με τη σειρά της μεταφέρει το http.ResponseWriter και το http.Request στο ServeHTTP.

Με άλλα λόγια, οι χειριστές στο Golang παρουσιάζονται σε μια ενιαία διεπαφή, η οποία δίνει πολλές επιλογές στον προγραμματιστή. Έτσι, για παράδειγμα, το ενδιάμεσο λογισμικό υλοποιείται χρησιμοποιώντας έναν χειριστή, όπου το ServeHTTP πρώτα κάνει κάτι και στη συνέχεια καλεί τη μέθοδο ServeHTTP ενός άλλου χειριστή.

Όπως αναφέρθηκε παραπάνω, οι χειριστές απλώς σχηματίζουν απαντήσεις σε αιτήματα. Αλλά ποιος χειριστής πρέπει να χρησιμοποιείται σε μια συγκεκριμένη στιγμή;

Αίτημα δρομολόγησης

Για να κάνετε τη σωστή επιλογή, χρησιμοποιήστε έναν πολυπλέκτη HTTP. Σε πολλές βιβλιοθήκες ονομάζεται muxer ή router, αλλά είναι όλες το ίδιο πράγμα. Η λειτουργία του πολυπλέκτη είναι να αναλύει τη διαδρομή αιτήματος και να επιλέγει τον κατάλληλο χειριστή.

Εάν χρειάζεστε υποστήριξη για πολύπλοκη δρομολόγηση, τότε είναι καλύτερο να χρησιμοποιήσετε βιβλιοθήκες τρίτων. Μερικά από τα πιο προηγμένα - γορίλας/μουξ и go-chi/chi, αυτές οι βιβλιοθήκες καθιστούν δυνατή την υλοποίηση ενδιάμεσης επεξεργασίας χωρίς προβλήματα. Με τη βοήθειά τους, μπορείτε να ρυθμίσετε τη δρομολόγηση χαρακτήρων μπαλαντέρ και να εκτελέσετε μια σειρά από άλλες εργασίες. Το πλεονέκτημά τους είναι η συμβατότητα με τυπικούς χειριστές HTTP. Ως αποτέλεσμα, μπορείτε να γράψετε απλό κώδικα που μπορεί να τροποποιηθεί στο μέλλον.

Η εργασία με πολύπλοκα πλαίσια σε μια κανονική κατάσταση θα απαιτεί όχι αρκετά τυπικές λύσεις και αυτό περιπλέκει πολύ τη χρήση των προεπιλεγμένων χειριστών. Ο συνδυασμός της προεπιλεγμένης βιβλιοθήκης και ενός απλού δρομολογητή θα αρκεί για τη δημιουργία της συντριπτικής πλειοψηφίας των εφαρμογών.

Επεξεργασία ερωτήματος

Επιπλέον, χρειαζόμαστε ένα στοιχείο που θα «ακούει» τις εισερχόμενες συνδέσεις και θα ανακατευθύνει όλα τα αιτήματα στον σωστό χειριστή. Ο http.Server μπορεί εύκολα να αντιμετωπίσει αυτήν την εργασία.

Τα παρακάτω δείχνουν ότι ο διακομιστής είναι υπεύθυνος για όλες τις εργασίες που σχετίζονται με την επεξεργασία σύνδεσης. Αυτή είναι, για παράδειγμα, εργασία στο πρωτόκολλο TLS. Ένας τυπικός διακομιστής HTTP χρησιμοποιείται για την υλοποίηση της κλήσης http.ListenAndServer.

Τώρα ας δούμε πιο σύνθετα παραδείγματα.

Προσθήκη Let's Encrypt

Από προεπιλογή, η εφαρμογή μας εκτελείται μέσω του πρωτοκόλλου HTTP, αλλά συνιστάται η χρήση του πρωτοκόλλου HTTPS. Αυτό μπορεί να γίνει χωρίς προβλήματα στο Go. Εάν έχετε λάβει πιστοποιητικό και ιδιωτικό κλειδί, τότε αρκεί να καταχωρήσετε το ListenAndServeTLS με το σωστό πιστοποιητικό και τα σωστά αρχεία κλειδιών.

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

Μπορείτε πάντα να κάνετε καλύτερα.

Ας κρυπτογραφήσουμε παρέχει δωρεάν πιστοποιητικά με αυτόματη ανανέωση. Για να χρησιμοποιήσετε την υπηρεσία, χρειάζεστε ένα πακέτο autocert.

Ο ευκολότερος τρόπος για να το ρυθμίσετε είναι να χρησιμοποιήσετε τη μέθοδο autocert.NewListener σε συνδυασμό με το http.Serve. Η μέθοδος σάς επιτρέπει να αποκτάτε και να ενημερώνετε πιστοποιητικά TLS ενώ ο διακομιστής HTTP επεξεργάζεται αιτήματα:

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

Αν ανοίξουμε στο πρόγραμμα περιήγησης example.com, θα λάβουμε μια απάντηση HTTPS "Γεια, κόσμος!"

Εάν χρειάζεστε πιο λεπτομερή διαμόρφωση, τότε θα πρέπει να χρησιμοποιήσετε το autocert.Manager. Στη συνέχεια, δημιουργούμε το δικό μας παράδειγμα http.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("", "")

Αυτός είναι ένας εύκολος τρόπος για την υλοποίηση πλήρους υποστήριξης HTTPS με αυτόματη ανανέωση πιστοποιητικού.

Προσθήκη προσαρμοσμένων διαδρομών

Ο προεπιλεγμένος δρομολογητής που περιλαμβάνεται στην τυπική βιβλιοθήκη είναι ωραίος, αλλά πολύ βασικός. Οι περισσότερες εφαρμογές χρειάζονται πιο σύνθετη δρομολόγηση, συμπεριλαμβανομένων ένθετων και μπαλαντέρ διαδρομών, ή ρύθμιση μοτίβων και παραμέτρων διαδρομής.

Σε αυτή την περίπτωση, θα πρέπει να χρησιμοποιήσετε πακέτα γορίλας/μουξ и go-chi/chi. Θα μάθουμε πώς να δουλεύουμε με το τελευταίο - ένα παράδειγμα φαίνεται παρακάτω.

Δίνεται το αρχείο api/v1/api.go που περιέχει διαδρομές για το 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
}

Ορίζουμε το πρόθεμα api/vq για τις διαδρομές στο κύριο αρχείο.

Στη συνέχεια, μπορούμε να το προσαρτήσουμε στον κύριο δρομολογητή μας κάτω από το πρόθεμα api/v1/ στην κύρια εφαρμογή μας:

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

Η ευκολία εργασίας του Go με σύνθετες διαδρομές καθιστά δυνατή την απλοποίηση της δομής και της συντήρησης μεγάλων, πολύπλοκων εφαρμογών.

Εργασία με ενδιάμεσο λογισμικό

Η σταδιοποίηση περιλαμβάνει την αναδίπλωση ενός προγράμματος χειρισμού HTTP με έναν άλλο, καθιστώντας δυνατή τη γρήγορη εκτέλεση ελέγχου ταυτότητας, συμπίεσης, καταγραφής και πολλών άλλων λειτουργιών.

Για παράδειγμα, ας εξετάσουμε τη διεπαφή http.Handler, με τη βοήθειά της θα γράψουμε έναν χειριστή με έλεγχο ταυτότητας των χρηστών της υπηρεσίας.

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

Υπάρχουν δρομολογητές τρίτων, όπως το chi, που σας επιτρέπουν να επεκτείνετε τη λειτουργικότητα του ενδιάμεσου λογισμικού.

Εργασία με στατικά αρχεία

Η τυπική βιβλιοθήκη Go περιλαμβάνει δυνατότητες για εργασία με στατικό περιεχόμενο, συμπεριλαμβανομένων εικόνων, JavaScript και αρχείων CSS. Η πρόσβαση σε αυτά είναι δυνατή μέσω της συνάρτησης http.FileServer. Επιστρέφει ένα πρόγραμμα χειρισμού που εξυπηρετεί αρχεία από έναν συγκεκριμένο κατάλογο.

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

Αξίζει σίγουρα να θυμάστε ότι το http.Dir εμφανίζει τα περιεχόμενα του καταλόγου εάν δεν περιέχει το κύριο αρχείο index.html. Σε αυτήν την περίπτωση, για να αποτρέψετε την παραβίαση του καταλόγου, θα πρέπει να χρησιμοποιήσετε το πακέτο unindexed.

Σωστό κλείσιμο

Το Go διαθέτει επίσης μια δυνατότητα που ονομάζεται χαριτωμένος τερματισμός λειτουργίας του διακομιστή HTTP. Αυτό μπορεί να γίνει χρησιμοποιώντας τη μέθοδο Shutdown(). Ο διακομιστής εκκινείται σε μια γορουτίνα και, στη συνέχεια, ακούγεται το κανάλι για να λάβει ένα σήμα διακοπής. Μόλις ληφθεί το σήμα, ο διακομιστής απενεργοποιείται, αλλά όχι αμέσως, αλλά μετά από λίγα δευτερόλεπτα.

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)

Ως συμπέρασμα

Το Go είναι μια ισχυρή γλώσσα με μια σχεδόν καθολική τυπική βιβλιοθήκη. Οι προεπιλεγμένες δυνατότητές του είναι πολύ ευρείες και μπορούν να βελτιωθούν με τη χρήση διεπαφών - αυτό σας επιτρέπει να αναπτύξετε πραγματικά αξιόπιστους διακομιστές HTTP.

Το Skillbox προτείνει:

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο