// SPDX-FileCopyrightText: V // SPDX-License-Identifier: OSL-3.0 package main // import "go.anomalous.eu/cgiserver" import ( "context" "flag" "fmt" "log" "net" "net/http" "net/http/cgi" "os" "os/signal" "syscall" "github.com/coreos/go-systemd/v22/activation" "github.com/gorilla/handlers" "golang.org/x/sync/errgroup" ) 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) // Create a Server so we can call Shutdown() on it later. srv := &http.Server{Handler: handler} // TODO(V): comment ctx, cancel := context.WithCancel(context.Background()) group, ctx := errgroup.WithContext(ctx) srv.BaseContext = func(net.Listener) context.Context { return ctx } group.Go(func() error { // Catch SIGTERM so we can shutdown gracefully. sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT) select { case <-sig: log.Print("Caught signal, shutting down") cancel() case <-ctx.Done(): // Nothing to do here } return nil }) for _, ln := range lns { // Loop variables are unsafe to close over with goroutines, // so this just shadows it with a fresh binding. ln := ln group.Go(func() error { err := srv.Serve(ln) if err != http.ErrServerClosed { return err } return nil }) } if err := group.Wait(); err != nil { log.Printf("Fatal server error: %v", err) } // This will block until all existing connections are handled. srv.Shutdown(context.Background()) if err != nil { os.Exit(1) } }