// Copyright 2019 Drone.IO Inc. All rights reserved. // Use of this source code is governed by the Polyform License // that can be found in the LICENSE file. // Package server provides an HTTP server with support for TLS // and graceful shutdown. package server import ( "context" "crypto/tls" "net/http" "os" "path/filepath" "golang.org/x/crypto/acme/autocert" "golang.org/x/sync/errgroup" ) // A Server defines parameters for running an HTTP server. type Server struct { Acme bool Addr string Cert string Key string Host string Handler http.Handler } // ListenAndServe initializes a server to respond to HTTP network requests. func (s Server) ListenAndServe(ctx context.Context) error { if s.Acme { return s.listenAndServeAcme(ctx) } else if s.Key != "" { return s.listenAndServeTLS(ctx) } return s.listenAndServe(ctx) } func (s Server) listenAndServe(ctx context.Context) error { var g errgroup.Group s1 := &http.Server{ Addr: s.Addr, Handler: s.Handler, } g.Go(func() error { <-ctx.Done() return s1.Shutdown(context.Background()) }) g.Go(func() error { return s1.ListenAndServe() }) return g.Wait() } func (s Server) listenAndServeTLS(ctx context.Context) error { var g errgroup.Group s1 := &http.Server{ Addr: ":http", Handler: s.Handler, } s2 := &http.Server{ Addr: ":https", Handler: s.Handler, } g.Go(func() error { return s1.ListenAndServe() }) g.Go(func() error { return s2.ListenAndServeTLS( s.Cert, s.Key, ) }) g.Go(func() error { <-ctx.Done() var gShutdown errgroup.Group gShutdown.Go(func() error { return s1.Shutdown(context.Background()) }) gShutdown.Go(func() error { return s2.Shutdown(context.Background()) }) return gShutdown.Wait() }) return g.Wait() } func (s Server) listenAndServeAcme(ctx context.Context) error { var g errgroup.Group c := cacheDir() m := &autocert.Manager{ Cache: autocert.DirCache(c), Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(s.Host), } s1 := &http.Server{ Addr: ":http", Handler: m.HTTPHandler(s.Handler), } s2 := &http.Server{ Addr: ":https", Handler: s.Handler, TLSConfig: &tls.Config{ GetCertificate: m.GetCertificate, NextProtos: []string{"h2", "http/1.1"}, MinVersion: tls.VersionTLS12, }, } g.Go(func() error { return s1.ListenAndServe() }) g.Go(func() error { return s2.ListenAndServeTLS("", "") }) g.Go(func() error { <-ctx.Done() var gShutdown errgroup.Group gShutdown.Go(func() error { return s1.Shutdown(context.Background()) }) gShutdown.Go(func() error { return s2.Shutdown(context.Background()) }) return gShutdown.Wait() }) return g.Wait() } func cacheDir() string { const base = "golang-autocert" if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" { return filepath.Join(xdg, base) } return filepath.Join(os.Getenv("HOME"), ".cache", base) }