Einen Go Webserver von Grund auf erstellen
Daniel Hayes
Full-Stack Engineer · Leapcell

Verschiedene Möglichkeiten, einen Webserver in Go zu schreiben
I. Der Unterschied zwischen einem Webserver und einem HTTP-Server
Ein HTTP-Server ist, wie der Name schon sagt, ein Server, der das HTTP-Protokoll unterstützt. Ein Webserver kann jedoch zusätzlich zur Unterstützung des HTTP-Protokolls auch andere Netzwerkprotokolle unterstützen. Dieser Artikel konzentriert sich auf die Vorstellung verschiedener gängiger Methoden zum Schreiben eines Webservers mit dem offiziellen Paket von Golang.
II. Der einfachste HTTP-Server
Dies ist die einfachste Möglichkeit, einen Webserver zu implementieren. Das folgende ist der Beispielcode:
package main import ( "fmt" "log" "net/http" ) func myHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hallo!") } func main() { http.HandleFunc("/", myHandler) // Setze die Zugriffsroute log.Fatal(http.ListenAndServe(":8080", nil)) }
Nach dem Starten dieses Programms können Sie ein weiteres Terminal öffnen und den Befehl curl localhost:8080
eingeben oder localhost:8080
direkt in einem Browser öffnen, und Sie sehen das Ausgaberesultat Hallo!
.
Die Funktion der Funktion ListenAndServe
besteht darin, Verbindungen zu überwachen und zu verarbeiten. Seine interne Verarbeitungsmethode besteht darin, für jede Verbindung eine Goroutine zu starten, um sie zu verarbeiten. Dies ist jedoch keine ideale Verarbeitungsmethode. Studenten, die Betriebssysteme studiert haben, wissen, dass die Kosten für den Prozess- oder Threadwechsel enorm sind. Obwohl Goroutinen Lightweight-Threads auf Benutzerebene sind und deren Umschaltung keinen Wechsel zwischen Benutzermodus und Kernelmodus verursacht, sind die Kosten für die Umschaltung immer noch nicht zu vernachlässigen, wenn die Anzahl der Goroutinen groß ist. Ein besserer Weg ist die Verwendung eines Goroutine-Pools, aber dieser Artikel wird dies vorerst nicht weiter ausführen.
III. Verwenden der Handler-Schnittstelle
Die vorherige Methode hat eine schlechte Skalierbarkeit. Zum Beispiel ist es unmöglich, das Timeout des Servers einzustellen. Zu diesem Zeitpunkt können wir einen benutzerdefinierten Server verwenden.
Die Server
-Struktur ist wie folgt definiert:
type Server struct { Addr string // TCP-Adresse, auf die gehört werden soll Handler Handler // Handler, der aufgerufen werden soll ReadTimeout time.Duration // Maximale Dauer, bevor das Lesen der Anfrage ein Timeout verursacht WriteTimeout time.Duration // Maximale Dauer, bevor das Schreiben der Antwort ein Timeout verursacht TLSConfig *tls.Config // ... }
Der Handler
ist eine Schnittstelle, die wie folgt definiert ist:
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
Solange wir also die ServeHTTP
-Methode der Handler
-Schnittstelle implementieren, können wir den Server anpassen. Der Beispielcode lautet wie folgt:
package main import ( "log" "net/http" "time" ) type myHandler struct{} func (this myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Spezifische Verarbeitungslogik } func main() { server := http.Server{ Addr: ":8080", Handler: &myHandler{}, ReadTimeout: 3 * time.Second, // ... } log.Fatal(server.ListenAndServe()) }
IV. Direktes Verarbeiten der Verbindung
Manchmal müssen wir die Verbindung auf einer grundlegenderen Ebene verarbeiten, und zu diesem Zeitpunkt können wir das Paket net
verwenden. Der serverseitige Code ist einfach wie folgt implementiert:
package main import ( "log" "net" ) func handleConn(conn net.Conn) { // Spezifische Logik für die Verarbeitung der Verbindung } func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { // Behandle den Fehler } go handleConn(conn) } }
Der Einfachheit halber wird hier für jede Verbindung eine Goroutine gestartet, um sie zu verarbeiten. In der praktischen Anwendung ist die Verwendung eines Goroutine-Pools in der Regel eine bessere Wahl. Für die Rückgabeinformationen an den Client müssen wir diese nur zurück in die conn
schreiben.
V. Proxy
Die vorherigen Abschnitte haben verschiedene Möglichkeiten zur Implementierung eines Webservers vorgestellt. Lassen Sie uns nun einfach einen HTTP-Proxy implementieren. Das Schreiben eines Proxys in Golang ist sehr einfach. Wir müssen nur die Verbindung weiterleiten.
package main import ( "io" "log" "net" ) func handleConn(from net.Conn) { to, err := net.Dial("tcp", ":8001") // Baue eine Verbindung mit dem Zielserver auf if err != nil { // Behandle den Fehler } done := make(chan struct{}) go func() { defer from.Close() defer to.Close() io.Copy(from, to) close(done) }() go func() { defer from.Close() defer to.Close() io.Copy(to, from) close(done) }() <-done <-done } func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { // Behandle den Fehler } go handleConn(conn) } }
Durch eine leichte Verbesserung des Proxys können weitere Funktionen implementiert werden.
Leapcell: Die Serverless-Plattform der nächsten Generation für das Golang-App-Hosting
Schließlich möchte ich eine Plattform empfehlen, die sich am besten für die Bereitstellung von Go-Diensten eignet: Leapcell
1. Multi-Sprachen Unterstützung
- Entwickeln Sie mit JavaScript, Python, Go oder Rust.
2. Stellen Sie unbegrenzt Projekte kostenlos bereit
- zahlen Sie nur für die Nutzung - keine Anfragen, keine Gebühren.
3. Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne Leerlaufgebühren.
- Beispiel: 25 US-Dollar unterstützen 6,94 Millionen Anfragen bei einer durchschnittlichen Reaktionszeit von 60 ms.
4. Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für mühelose Einrichtung.
- Vollautomatische CI/CD-Pipelines und GitOps-Integration.
- Echtzeitmetriken und Protokollierung für umsetzbare Erkenntnisse.
5. Mühelose Skalierbarkeit und hohe Leistung
- Automatische Skalierung zur einfachen Bewältigung hoher Parallelität.
- Kein Betriebsaufwand - konzentrieren Sie sich einfach auf das Bauen.
Erfahren Sie mehr in der Dokumentation!
Leapcell Twitter: https://x.com/LeapcellHQ