// SPDX-FileCopyrightText: V // SPDX-License-Identifier: OSL-3.0 package main // import "go.anomalous.eu/cgiserver" import ( "context" "flag" "fmt" "log" "net/http" "net/http/cgi" "os" "os/signal" "syscall" "github.com/coreos/go-systemd/v22/activation" "github.com/gorilla/handlers" ) func main() { // The journal already has timestamps, so let's avoid adding our own. log.SetFlags(0) // The default usage message isn't very helpful, so we provide our own. flag.Usage = func() { fmt.Fprintln(flag.CommandLine.Output(), "Usage: cgiserver /path/to/application.cgi") } // We didn't configure any flags, so this just serves to display an error // if someone tries to pass one. It also automatically handles -h/--help. flag.Parse() if flag.NArg() != 1 { flag.Usage() os.Exit(1) } path := flag.Arg(0) // Socket activation makes our life very easy... lns, err := activation.Listeners() if err != nil { log.Fatal(err) } if len(lns) == 0 { log.Print("One or more listening sockets must be configured.") log.Print("See systemd.socket(5) for more information.") os.Exit(1) } var handler http.Handler // ...and Go's standard library does literally all of the hard work. handler = &cgi.Handler{ Path: path, Env: os.Environ(), // Environment variables aren't inherited by default. } // We want to resolve X-Forwarded-For, etc. handler = handlers.ProxyHeaders(handler) // Additionally, we want to log requests. handler = handlers.CombinedLoggingHandler(os.Stdout, handler) // Catch SIGTERM so we can shutdown gracefully. sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT) // Create a Server so we can call Shutdown() on it later. srv := &http.Server{Handler: handler} doom := make(chan error) for _, ln := range lns { // Loop variables are unsafe to close over with goroutines, // so this just shadows it with a fresh binding. ln := ln go func() { // If any one of these return, it's game over. doom <- srv.Serve(ln) }() } select { case err = <-doom: // Only the first error matters, the rest will be ErrServerClosed. log.Printf("Fatal server error: %v", err) case <-sig: log.Print("Caught signal, shutting down") } // This will block until all existing connections are handled. srv.Shutdown(context.Background()) if err != nil { os.Exit(1) } }