Apache
Go stellt mit dem Modul net/http einen vollständigen HTTP Server in der Standardbibliothek bereit. Dennoch kann es vorteilhaft sein, eine in Go geschriebene Web-Anwendung über einen Apache Webserver zugänglich zu machen; beispielsweise, wenn unterschiedliche Anwendungen oder Sites auf dem gleichen Server laufen sollen (in Unterverzeichnissen oder über Virtual Hosts) oder wenn weitere Konfigurationsmöglichkeiten des Apache HTTPD benötigt werden (z.B. benutzerdefiniertes Logging, Rewrite Regeln oder Authentifikation).
Eine definierte Schnittstelle zwischen Webservern und Anwendungen ist FastCGI. Dabei wird ein Socket (Unix Domain oder TCP) als Kommunikationskanal verwendet. Das Modul net/http/fcgi aus der Go Standardbibliothek stellt die Serverseite einer FastCGI Implementierung bereit.
Für den Apache Webserver gibt es FastCGI-Unterstützung in Form des Moduls mod_fastcgi (Debian-Paket libapache2-mod-fastcgi). Es stammt von Open Market Inc., dem Erfinder des FastCGI Protokolls. Das Modul unterliegt einer etwas eingeschränkten Lizenz und wird daher von Debian unter non-free geführt.
Eine einfache FastCGI Anwendung in Go sieht beispielsweise so aus:
package main
import (
"net/http"
"net/http/fcgi"
"runtime"
)
var reply = []byte("Hello, World!\n")
func handler(w http.ResponseWriter, r *http.Request) {
w.Write(reply)
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // use all CPU cores
err := fcgi.Serve(nil, http.HandlerFunc(handler))
if err != nil { panic(err) }
}
Das Apache-Modul ist unter Debian standardmäßig so konfiguriert, dass es
Anwendungen mit der Endung .fcgi als FastCGI Programme ansieht. Andere
Anwendungen können mit der Direktive FastCgiServer
explizit benannt
werden. Die folgenden Zeilen innerhalb einer VirtualHost Konfiguration
(z.B. bei Debian in /etc/apache2/sites-available/default) starten das
Programm /usr/local/sbin/fcgiprog
und leiten dorthin alle Anfragen,
deren URI mit /fcgiprog
beginnt.
...
FastCgiServer /usr/local/sbin/fcgiprog
RewriteRule ^/fcgiprog /usr/local/sbin/fcgiprog
Wenn der Apache HTTP Server neu gestartet wird oder seine Konfiguration neu einlesen soll, schickt das Modul mod_fastcgi ein TERM Signal an alle laufenden FastCGI-Anwendungen. Das führt bei der oben gezeigten Anwendung zu einem sofortigen Beenden des Programmes und damit zum Abbruch aller laufenden Transaktionen.
Um die Transaktionen sauber zu beenden, bevor das FastCGI Programm gestoppt wird, muss es das Signal SIGTERM abfangen und geeignet darauf reagieren. Dabei sind drei Dinge zu beachten.
-
mod_fastcgi
garantiert, dass nach dem Versand von SIGTERM kein weiterer Request an ein laufendes FastCGI-Programm geschickt wird. Allerdings können sich für eine kurze Zeitspanne noch Requests in der Verarbeitung vonnet/http/fcgi
befinden, für die noch kein HTTP-Handler als Goroutine gestartet wurde. Um diese Fälle abzufangen, sollte im Signal Handler erstmal kurz gewartet werden (in u.g. Beispiel 100 Millisekunden). - Um keine noch laufende Goroutine abzubrechen, wird mit
runtime.NumGoroutine()
regelmäßig geprüft, wie viele Goroutinen noch laufen. Erst wenn diese wieder dem ursprünglichen Wert vor dem Start der FastCGI Requestannahme entsprechen (plus eins für den FCGI-Handler), kann davon ausgegangen werden, dass sicher kein Request mehr verarbeitet wird. - Je nach Programmstruktur kann es möglich sein, dass in der Zwischenzeit weitere Goroutinen gestartet wurden, die mit der Requestbearbeitung nichts zu tun haben; beispielsweise Synchronisationsmechanismen von Drittbibliotheken. Um in diesem Fall nicht endlos zu warten, wird die Warteschleife nach 3 Sekunden (30 Mal 100 Millisekunden) abgebrochen.
Wenn diese Punkte beachtet werden, ist es möglich, in Go geschriebene
Serveranwendungen upzudaten, indem einfach das Binary ersetzt und der
Apache mit apache2ctl graceful
neu initialisiert wird. Selbst unter
Last wird dabei keine Client-Verbindung abgebrochen.
// Example for a FastCGI program that terminates gracefully.
package main
import (
"net/http"
"net/http/fcgi"
"os"
"os/signal"
"runtime"
"syscall"
"time"
)
// a simple request handler
var hello = []byte("Hello, World!\n")
func handler(w http.ResponseWriter, r *http.Request) {
w.Write(hello)
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // use all CPU cores
n := runtime.NumGoroutine() + 1 // initial number of Goroutines
// install signal handler
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGTERM)
// Spawn request handler
go func() {
err := fcgi.Serve(nil, http.HandlerFunc(handler))
if err != nil {
panic(err)
}
}()
// catch signal
_ = <-c
// give pending requests in fcgi.Serve() some time to enter the request handler
time.Sleep(time.Millisecond * 100)
// wait at most 3 seconds for request handlers to finish
for i := 0; i < 30; i++ {
if runtime.NumGoroutine() <= n {
return
}
time.Sleep(time.Millisecond * 100)
}
}
Es gibt ein weiteres Apache-Modul mod_fcgid, das manchmal als Alternative zu mod_fastcgi empfohlen wird, weil es Freie Software ist und bessere Kontrolle über das zu startende Programm bieten soll. Ihm fehlt jedoch die Möglichkeit, mehrere Anfragen parallel von einem Programm beantworten zu lassen. Parallele Anfragen sind nur möglich, wenn man mehrere unabhängige Instanzen des Programms durch den Apache starten lässt. Damit kann die wichtige Eigenschaft von Go, mehrere Anfragen parallel in Goroutinen abarbeiten zu lassen, nicht genutzt werden. mod_fcgid sollte daher nicht für Go-Programme verwendet werden.