src

Go monorepo.
Log | Files | Refs

commit ea5a6aaa08ff4998be3dc8fab12d51212f3e173a
parent cf572d822632779429ea666b57c67a76c11b8b6b
Author: dwrz <dwrz@dwrz.net>
Date:   Thu, 12 Jan 2023 20:32:30 +0000

Add server pkg

Diffstat:
Apkg/server/config.go | 27+++++++++++++++++++++++++++
Apkg/server/new.go | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apkg/server/redirect/redirect.go | 15+++++++++++++++
Apkg/server/server.go | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 248 insertions(+), 0 deletions(-)

diff --git a/pkg/server/config.go b/pkg/server/config.go @@ -0,0 +1,27 @@ +package server + +import "time" + +type Config struct { + // CertPath is the path to the x509 certificate. + CertPath string `json:"certPath"` + + // KeyPath is the path to the x509 key. + KeyPath string `json:"keyPath"` + + MaxHeaderBytes int `json:"maxHeaderBytes"` + Ports Ports `json:"ports"` + Timeouts Timeouts `json:"timeouts"` +} + +type Ports struct { + HTTP string `json:"http"` + HTTPS string `json:"https"` +} + +type Timeouts struct { + Idle time.Duration `json:"idle"` + Read time.Duration `json:"read"` + Shutdown time.Duration `json:"shutdown"` + Write time.Duration `json:"write"` +} diff --git a/pkg/server/new.go b/pkg/server/new.go @@ -0,0 +1,89 @@ +package server + +import ( + "crypto/tls" + "fmt" + "net/http" + + "code.dwrz.net/src/pkg/log" + "code.dwrz.net/src/pkg/server/redirect" +) + +type Parameters struct { + Config Config + Handler http.Handler + Log *log.Logger +} + +func (p Parameters) Validate() error { + if p.Handler == nil { + return fmt.Errorf("missing handler") + } + if p.Log == nil { + return fmt.Errorf("missing logger") + } + + return nil +} + +func New(p Parameters) (*Server, error) { + if err := p.Validate(); err != nil { + return nil, fmt.Errorf("invalid server parameters: %v", err) + } + + var server = &Server{ + cfg: &p.Config, + log: p.Log, + } + + // Load TLS certificates, if provided. + var certs tls.Certificate + if p.Config.CertPath != "" && p.Config.KeyPath != "" { + var err error + certs, err = tls.LoadX509KeyPair( + p.Config.CertPath, p.Config.KeyPath, + ) + if err != nil { + server.log.Error.Fatalf( + "failed to load x509 key pair: %v", err, + ) + } + } + + if p.Config.Ports.HTTPS != "" { + server.https = &http.Server{ + Addr: fmt.Sprintf( + ":%s", p.Config.Ports.HTTPS, + ), + ErrorLog: p.Log.Error, + Handler: p.Handler, + ReadTimeout: p.Config.Timeouts.Read, + MaxHeaderBytes: p.Config.MaxHeaderBytes, + IdleTimeout: p.Config.Timeouts.Idle, + TLSConfig: &tls.Config{ + Certificates: []tls.Certificate{certs}, + }, + WriteTimeout: p.Config.Timeouts.Write, + } + } + if p.Config.Ports.HTTP != "" { + server.http = &http.Server{ + Addr: fmt.Sprintf(":%s", p.Config.Ports.HTTP), + ErrorLog: p.Log.Error, + ReadTimeout: p.Config.Timeouts.Read, + MaxHeaderBytes: p.Config.MaxHeaderBytes, + IdleTimeout: p.Config.Timeouts.Idle, + WriteTimeout: p.Config.Timeouts.Write, + } + + // If serving HTTPS, redirect from HTTP. + // Otherwise, serve the passed-in handler over HTTP. + if p.Config.Ports.HTTPS != "" { + server.http.Handler = &redirect.Handler{} + } else { + server.http.Handler = p.Handler + } + } + + return server, nil +} diff --git a/pkg/server/redirect/redirect.go b/pkg/server/redirect/redirect.go @@ -0,0 +1,15 @@ +package redirect + +import ( + "net/http" +) + +type Handler struct{} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Connection", "close") + + var url = "https://" + r.Host + r.URL.String() + + http.Redirect(w, r, url, http.StatusMovedPermanently) +} diff --git a/pkg/server/server.go b/pkg/server/server.go @@ -0,0 +1,117 @@ +package server + +import ( + "context" + "fmt" + "net/http" + "runtime/debug" + + "code.dwrz.net/src/pkg/log" +) + +type Server struct { + cfg *Config + log *log.Logger + http *http.Server + https *http.Server +} + +func (s *Server) Serve() { + if s.http != nil { + s.log.Debug.Printf("serving http") + + go func() { + if msg := recover(); msg != nil { + s.log.Error.Printf( + "panic: %v:\n%s\n", msg, debug.Stack(), + ) + } + + s.serveHTTP() + }() + } + if s.https != nil { + s.log.Debug.Printf("serving https") + + go func() { + if msg := recover(); msg != nil { + s.log.Error.Printf( + "panic: %v:\n%s\n", msg, debug.Stack(), + ) + } + + s.serveHTTPS() + }() + + } + + s.log.Debug.Printf("server ready") +} + +func (s *Server) serveHTTP() { + s.log.Debug.Printf("starting http server (%s)", s.http.Addr) + + if err := s.http.ListenAndServe(); err != http.ErrServerClosed { + s.log.Error.Fatalf( + "failed to listen and serve: %v", err, + ) + } +} + +func (s *Server) serveHTTPS() { + s.log.Debug.Printf("starting https server (%s)", s.https.Addr) + + if err := s.https.ListenAndServeTLS( + "", "", + ); err != http.ErrServerClosed { + s.log.Error.Fatalf( + "failed to listen and serve: %v", err, + ) + } +} + +func (s *Server) Shutdown() error { + s.log.Debug.Printf("shutting down server") + + ctxShutdown, cancel := context.WithTimeout( + context.Background(), s.cfg.Timeouts.Shutdown, + ) + defer cancel() + + var httpShutdownErr, httpsShutdownErr error + if s.http != nil { + s.http.SetKeepAlivesEnabled(false) + + if err := s.http.Shutdown(ctxShutdown); err != nil { + httpShutdownErr = err + } + } + if s.https != nil { + s.https.SetKeepAlivesEnabled(false) + + if err := s.https.Shutdown(ctxShutdown); err != nil { + httpsShutdownErr = err + } + } + + switch { + case httpShutdownErr != nil && httpsShutdownErr != nil: + return fmt.Errorf( + "failed to shut down http and https servers: %v, %v", + httpShutdownErr, httpsShutdownErr, + ) + case httpShutdownErr != nil: + return fmt.Errorf( + "failed to shut down http server: %v", httpShutdownErr, + ) + case httpsShutdownErr != nil: + return fmt.Errorf( + "failed to shut down https server: %v", + httpsShutdownErr, + ) + default: + s.log.Debug.Printf("server shutdown") + + return nil + } +}