123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- package engine
- import (
- "errors"
- "net/http"
- "os"
- "strings"
- "sync"
- "time"
- "github.com/caddyserver/certmagic"
- log "github.com/sirupsen/logrus"
- "github.com/tylerb/graceful"
- "github.com/xyproto/env/v2"
- "golang.org/x/net/http2"
- )
- // List of functions to run at shutdown
- var (
- shutdownFunctions []func()
- mut sync.Mutex
- completed bool
- )
- // AtShutdown adds a function to the list of functions that will be ran at shutdown
- func AtShutdown(shutdownFunction func()) {
- mut.Lock()
- defer mut.Unlock()
- shutdownFunctions = append(shutdownFunctions, shutdownFunction)
- }
- // NewGracefulServer creates a new graceful server configuration
- func (ac *Config) NewGracefulServer(mux *http.ServeMux, http2support bool, addr string) *graceful.Server {
- // Server configuration
- s := &http.Server{
- Addr: addr,
- Handler: mux,
- // The timeout values is also the maximum time it can take
- // for a complete page of Server-Sent Events (SSE).
- ReadTimeout: 10 * time.Second,
- WriteTimeout: time.Duration(ac.writeTimeout) * time.Second,
- MaxHeaderBytes: 1 << 20,
- }
- if http2support {
- // Enable HTTP/2 support
- http2.ConfigureServer(s, nil)
- }
- gracefulServer := &graceful.Server{
- Server: s,
- Timeout: ac.shutdownTimeout,
- }
- // Handle ctrl-c
- gracefulServer.ShutdownInitiated = ac.GenerateShutdownFunction(gracefulServer) // for investigating gracefulServer.Interrupted
- return gracefulServer
- }
- // GenerateShutdownFunction generates a function that will run the postponed
- // shutdown functions. Note that gracefulServer can be nil. It's only used for
- // finding out if the server was interrupted (ctrl-c or killed, SIGINT/SIGTERM)
- func (ac *Config) GenerateShutdownFunction(gracefulServer *graceful.Server) func() {
- return func() {
- mut.Lock()
- defer mut.Unlock()
- if completed {
- // The shutdown functions have already been called
- return
- }
- if ac.verboseMode {
- log.Info("Initiating shutdown")
- }
- // Call the shutdown functions in chronological order (FIFO)
- for _, shutdownFunction := range shutdownFunctions {
- shutdownFunction()
- }
- completed = true
- if ac.verboseMode {
- log.Info("Shutdown complete")
- }
- // Forced shutdown
- if gracefulServer != nil {
- if gracefulServer.Interrupted {
- // gracefulServer.Stop(forcedShutdownTimeout)
- ac.fatalExit(errors.New("Interrupted"))
- }
- }
- // TODO: To implement
- //if quicServer != nil {
- //fmt.Println("DEBUG: Has QUIC server at shutdown!")
- //}
- // One final flush
- os.Stdout.Sync()
- }
- }
- // Serve HTTP, HTTP/2 and/or HTTPS. Returns an error if unable to serve, or nil when done serving.
- func (ac *Config) Serve(mux *http.ServeMux, done, ready chan bool) error {
- // If we are not writing internal logs to a file, reduce the verbosity
- http2.VerboseLogs = (ac.internalLogFilename != os.DevNull)
- if ac.onlyLuaMode {
- ready <- true // Send a "ready" message to the REPL
- <-done // Wait for a "done" message from the REPL (or just keep waiting)
- // Serve nothing
- return nil // Done
- }
- // Channel to wait and see if we should just serve regular HTTP instead
- justServeRegularHTTP := make(chan bool)
- servingHTTPS := false
- servingHTTP := false
- // Goroutine that wait for a message to just serve regular HTTP, if needed
- go func() {
- <-justServeRegularHTTP // Wait for a message to just serve regular HTTP
- if strings.HasPrefix(ac.serverAddr, ":") {
- log.Info("Serving HTTP on http://localhost" + ac.serverAddr + "/")
- } else {
- log.Info("Serving HTTP on http://" + ac.serverAddr + "/")
- }
- mut.Lock()
- servingHTTP = true
- mut.Unlock()
- HTTPserver := ac.NewGracefulServer(mux, false, ac.serverAddr)
- // Open the URL before the serving has started, in a short delay
- if ac.openURLAfterServing && ac.luaServerFilename != "" {
- go func() {
- time.Sleep(delayBeforeLaunchingBrowser)
- ac.OpenURL(ac.serverHost, ac.serverAddr, false)
- }()
- }
- // Start serving. Shut down gracefully at exit.
- if err := HTTPserver.ListenAndServe(); err != nil {
- mut.Lock()
- servingHTTP = false
- mut.Unlock()
- // If we can't serve regular HTTP on port 80, give up
- ac.fatalExit(err)
- }
- }()
- // Decide which protocol to listen to
- switch {
- case ac.useCertMagic:
- if len(ac.certMagicDomains) == 0 {
- log.Warnln("Found no directories looking like domains in the given directory.")
- } else if len(ac.certMagicDomains) == 1 {
- log.Infof("Serving one domain with CertMagic: %s", ac.certMagicDomains[0])
- } else {
- log.Infof("Serving %d domains with CertMagic: %s", len(ac.certMagicDomains), strings.Join(ac.certMagicDomains, ", "))
- }
- mut.Lock()
- servingHTTPS = true
- mut.Unlock()
- // TODO: Look at "Advanced use" at https://github.com/caddyserver/certmagic#examples
- // Listen for HTTP and HTTPS requests, for specific domain(s)
- go func() {
- // If $XDG_CONFIG_DIR is not set, use $HOME.
- // If $HOME is not set, use $TMPDIR.
- // If $TMPDIR is not set, use /tmp.
- certStorageDir := env.StrAlt("XDG_CONFIG_DIR", "HOME", env.Str("TMPDIR", "/tmp"))
- defaultEmail := env.Str("LOGNAME", "root") + "@localhost"
- if len(ac.certMagicDomains) > 0 {
- defaultEmail = "webmaster@" + ac.certMagicDomains[0]
- }
- certmagic.DefaultACME.Email = env.Str("EMAIL", defaultEmail)
- // TODO: Find a way for Algernon users to agree on this manually
- certmagic.DefaultACME.Agreed = true
- certmagic.Default.Storage = &certmagic.FileStorage{Path: certStorageDir}
- if err := certmagic.HTTPS(ac.certMagicDomains, mux); err != nil {
- mut.Lock()
- servingHTTPS = false
- mut.Unlock()
- log.Error(err)
- // Don't serve HTTP if CertMagic fails, just quit
- // justServeRegularHTTP <- true
- }
- }()
- case ac.serveJustQUIC: // Just serve QUIC, but fallback to HTTP
- if strings.HasPrefix(ac.serverAddr, ":") {
- log.Info("Serving QUIC on https://localhost" + ac.serverAddr + "/")
- } else {
- log.Info("Serving QUIC on https://" + ac.serverAddr + "/")
- }
- mut.Lock()
- servingHTTPS = true
- mut.Unlock()
- // Start serving over QUIC
- go ac.ListenAndServeQUIC(mux, &mut, justServeRegularHTTP, &servingHTTPS)
- case ac.productionMode:
- // Listen for both HTTPS+HTTP/2 and HTTP requests, on different ports
- if len(ac.serverHost) == 0 {
- log.Info("Serving HTTP/2 on https://localhost/")
- } else {
- log.Info("Serving HTTP/2 on https://" + ac.serverHost + "/")
- }
- mut.Lock()
- servingHTTPS = true
- mut.Unlock()
- go func() {
- // Start serving. Shut down gracefully at exit.
- // Listen for HTTPS + HTTP/2 requests
- HTTPS2server := ac.NewGracefulServer(mux, true, ac.serverHost+":443")
- // Start serving. Shut down gracefully at exit.
- if err := HTTPS2server.ListenAndServeTLS(ac.serverCert, ac.serverKey); err != nil {
- mut.Lock()
- servingHTTPS = false
- mut.Unlock()
- log.Error(err)
- }
- }()
- if len(ac.serverHost) == 0 {
- log.Info("Serving HTTP on http://localhost/")
- } else {
- log.Info("Serving HTTP on http://" + ac.serverHost + "/")
- }
- mut.Lock()
- servingHTTP = true
- mut.Unlock()
- go func() {
- if ac.redirectHTTP {
- // Redirect HTTPS to HTTP
- redirectFunc := func(w http.ResponseWriter, req *http.Request) {
- http.Redirect(w, req, "https://"+req.Host+req.URL.String(), http.StatusMovedPermanently)
- }
- if err := http.ListenAndServe(ac.serverHost+":80", http.HandlerFunc(redirectFunc)); err != nil {
- mut.Lock()
- servingHTTP = false
- mut.Unlock()
- // If we can't serve regular HTTP on port 80, give up
- ac.fatalExit(err)
- }
- } else {
- // Don't redirect, but serve the same contents as the HTTPS server as HTTP on port 80
- HTTPserver := ac.NewGracefulServer(mux, false, ac.serverHost+":80")
- if err := HTTPserver.ListenAndServe(); err != nil {
- mut.Lock()
- servingHTTP = false
- mut.Unlock()
- // If we can't serve regular HTTP on port 80, give up
- ac.fatalExit(err)
- }
- }
- }()
- case ac.serveJustHTTP2: // It's unusual to serve HTTP/2 without HTTPS
- if strings.HasPrefix(ac.serverAddr, ":") {
- log.Warn("Serving HTTP/2 without HTTPS (not recommended!) on http://localhost" + ac.serverAddr + "/")
- } else {
- log.Warn("Serving HTTP/2 without HTTPS (not recommended!) on http://" + ac.serverAddr + "/")
- }
- mut.Lock()
- servingHTTPS = true
- mut.Unlock()
- go func() {
- // Listen for HTTP/2 requests
- HTTP2server := ac.NewGracefulServer(mux, true, ac.serverAddr)
- // Start serving. Shut down gracefully at exit.
- if err := HTTP2server.ListenAndServe(); err != nil {
- mut.Lock()
- servingHTTPS = false
- mut.Unlock()
- justServeRegularHTTP <- true
- log.Error(err)
- }
- }()
- case !ac.serveJustHTTP2 && !ac.serveJustHTTP:
- if strings.HasPrefix(ac.serverAddr, ":") {
- log.Info("Serving HTTP/2 on https://localhost" + ac.serverAddr + "/")
- } else {
- log.Info("Serving HTTP/2 on https://" + ac.serverAddr + "/")
- }
- mut.Lock()
- servingHTTPS = true
- mut.Unlock()
- // Listen for HTTPS + HTTP/2 requests
- HTTPS2server := ac.NewGracefulServer(mux, true, ac.serverAddr)
- // Start serving. Shut down gracefully at exit.
- go func() {
- if err := HTTPS2server.ListenAndServeTLS(ac.serverCert, ac.serverKey); err != nil {
- log.Errorf("%s. Not serving HTTP/2.", err)
- log.Info("Use the -t flag for serving regular HTTP.")
- mut.Lock()
- servingHTTPS = false
- mut.Unlock()
- // If HTTPS failed (perhaps the key + cert are missing),
- // serve plain HTTP instead
- justServeRegularHTTP <- true
- }
- }()
- default:
- mut.Lock()
- servingHTTP = true
- mut.Unlock()
- justServeRegularHTTP <- true
- }
- // Wait just a tiny bit
- time.Sleep(20 * time.Millisecond)
- ready <- true // Send a "ready" message to the REPL
- // Open the URL, if specified
- if ac.openURLAfterServing {
- // Open the https:// URL if both http:// and https:// are being served
- mut.Lock()
- if (!servingHTTP) && (!servingHTTPS) {
- ac.fatalExit(errors.New("serving neither over https:// nor over https://"))
- }
- httpsProtocol := servingHTTPS
- mut.Unlock()
- ac.OpenURL(ac.serverHost, ac.serverAddr, httpsProtocol)
- }
- <-done // Wait for a "done" message from the REPL (or just keep waiting)
- return nil // Done serving
- }
|