github.com/anycable/anycable-go@v1.5.1/server/server.go (about) 1 package server 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "log/slog" 8 "net" 9 "net/http" 10 "sync" 11 "time" 12 13 "github.com/go-chi/chi/v5" 14 "github.com/joomcode/errorx" 15 "golang.org/x/net/netutil" 16 ) 17 18 // HTTPServer is wrapper over http.Server 19 type HTTPServer struct { 20 server *http.Server 21 addr string 22 secured bool 23 shutdown bool 24 started bool 25 maxConn int 26 mu sync.Mutex 27 log *slog.Logger 28 29 shutdownCtx context.Context 30 shutdownFn context.CancelFunc 31 32 mux *chi.Mux 33 } 34 35 var ( 36 allServers map[string]*HTTPServer = make(map[string]*HTTPServer) 37 allServersMu sync.Mutex 38 // Host is a default bind address for HTTP servers 39 Host string = "localhost" 40 // SSL is a default configuration for HTTP servers 41 SSL *SSLConfig 42 // MaxConn is a default configuration for maximum connections 43 MaxConn int 44 // Default logger 45 Logger *slog.Logger = slog.Default() 46 ) 47 48 // ForPort creates new or returns the existing server for the specified port 49 func ForPort(port string) (*HTTPServer, error) { 50 allServersMu.Lock() 51 defer allServersMu.Unlock() 52 53 if _, ok := allServers[port]; !ok { 54 server, err := NewServer(Host, port, SSL, MaxConn) 55 if err != nil { 56 return nil, err 57 } 58 allServers[port] = server 59 } 60 61 return allServers[port], nil 62 } 63 64 // NewServer builds HTTPServer from config params 65 func NewServer(host string, port string, ssl *SSLConfig, maxConn int) (*HTTPServer, error) { 66 router := chi.NewRouter() 67 addr := net.JoinHostPort(host, port) 68 69 server := &http.Server{Addr: addr, Handler: router, ReadHeaderTimeout: 5 * time.Second} 70 71 secured := (ssl != nil) && ssl.Available() 72 73 if secured { 74 cer, err := tls.LoadX509KeyPair(ssl.CertPath, ssl.KeyPath) 75 if err != nil { 76 return nil, errorx.Decorate(err, "failed to load SSL certificate") 77 } 78 79 server.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cer}, MinVersion: tls.VersionTLS12} 80 } 81 82 shutdownCtx, shutdownFn := context.WithCancel(context.Background()) 83 84 return &HTTPServer{ 85 server: server, 86 addr: addr, 87 mux: router, 88 secured: secured, 89 shutdown: false, 90 started: false, 91 shutdownCtx: shutdownCtx, 92 shutdownFn: shutdownFn, 93 maxConn: maxConn, 94 log: Logger.With("context", "http"), 95 }, nil 96 } 97 98 // Start server 99 func (s *HTTPServer) Start() error { 100 s.mu.Lock() 101 if s.Running() { 102 s.mu.Unlock() 103 return nil 104 } 105 106 s.started = true 107 s.mu.Unlock() 108 109 ln, err := net.Listen("tcp", s.addr) 110 if err != nil { 111 return err 112 } 113 defer ln.Close() 114 115 if s.maxConn > 0 { 116 ln = netutil.LimitListener(ln, s.maxConn) 117 } 118 119 if s.secured { 120 return s.server.ServeTLS(ln, "", "") 121 } 122 123 return s.server.Serve(ln) 124 } 125 126 // StartAndAnnounce prints server info and starts server 127 func (s *HTTPServer) StartAndAnnounce(name string) error { 128 s.mu.Lock() 129 if s.Running() { 130 s.mu.Unlock() 131 s.log.Debug("HTTP server has been already started", "name", name, "addr", s.Address()) 132 return nil 133 } 134 135 s.log.Debug("starting HTTP server", "name", name, "addr", s.Address()) 136 s.mu.Unlock() 137 138 return s.Start() 139 } 140 141 // Running returns true if server has been started 142 func (s *HTTPServer) Running() bool { 143 return s.started 144 } 145 146 // SetupHandler adds new handler to mux 147 func (s *HTTPServer) SetupHandler(path string, handler http.Handler) { 148 s.mux.Handle(path, handler) 149 } 150 151 // Shutdown shuts down server gracefully. 152 func (s *HTTPServer) Shutdown(ctx context.Context) error { 153 s.mu.Lock() 154 if s.shutdown { 155 s.mu.Unlock() 156 return nil 157 } 158 s.shutdown = true 159 s.mu.Unlock() 160 161 s.shutdownFn() 162 163 return s.server.Shutdown(ctx) 164 } 165 166 // ShutdownCtx returns context for graceful shutdown. 167 // It must be used by HTTP handlers to termniates long-running requests (SSE, long-polling). 168 func (s *HTTPServer) ShutdownCtx() context.Context { 169 return s.shutdownCtx 170 } 171 172 // Stopped return true iff server has been stopped by user 173 func (s *HTTPServer) Stopped() bool { 174 s.mu.Lock() 175 val := s.shutdown 176 s.mu.Unlock() 177 return val 178 } 179 180 // Address returns server scheme://host:port 181 func (s *HTTPServer) Address() string { 182 var scheme string 183 184 if s.secured { 185 scheme = "https://" 186 } else { 187 scheme = "http://" 188 } 189 190 return fmt.Sprintf("%s%s", scheme, s.addr) 191 }