diff options
author | V <v@anomalous.eu> | 2021-08-14 13:19:03 +0200 |
---|---|---|
committer | V <v@anomalous.eu> | 2021-08-14 13:19:03 +0200 |
commit | 5b28e02783c9048305ba651c6d9b843368534410 (patch) | |
tree | d96162a3a657a9be6ab9345e2d4456864abd789b /main.go | |
download | cgiserver-1.0.0.tar.zst |
Diffstat (limited to 'main.go')
-rw-r--r-- | main.go | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/main.go b/main.go new file mode 100644 index 0000000..2019888 --- /dev/null +++ b/main.go @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: V <v@anomalous.eu> +// 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) + } +} |