github.com/influx6/npkg@v0.8.8/nhttp/server.go (about) 1 package nhttp 2 3 import ( 4 "context" 5 "crypto/tls" 6 "errors" 7 "log" 8 "net" 9 "net/http" 10 "os" 11 "os/signal" 12 "sync" 13 "sync/atomic" 14 "syscall" 15 "time" 16 17 "github.com/influx6/npkg/nerror" 18 19 netutils "github.com/influx6/npkg/nnet" 20 "golang.org/x/crypto/acme/autocert" 21 ) 22 23 const ( 24 shutdownDuration = time.Second * 30 25 ) 26 27 var ( 28 // ErrUnhealthy is returned when a server is considered unhealthy. 29 ErrUnhealthy = errors.New("Service is unhealthy") 30 ) 31 32 // HealthPinger exposes what we expect to provide us a health check for a server. 33 type HealthPinger interface { 34 Ping() error 35 } 36 37 type healthPinger struct { 38 healthy uint32 39 } 40 41 // Ping implements the HealthPinger interface. 42 func (h *healthPinger) Ping() error { 43 if atomic.LoadUint32(&h.healthy) == 1 { 44 return ErrUnhealthy 45 } 46 return nil 47 } 48 49 func (h *healthPinger) setUnhealthy() { 50 atomic.StoreUint32(&h.healthy, 1) 51 } 52 func (h *healthPinger) setHealthy() { 53 atomic.StoreUint32(&h.healthy, 0) 54 } 55 56 // Server implements a http server wrapper. 57 type Server struct { 58 http2 bool 59 shutdownTimeout time.Duration 60 handler http.Handler 61 health *healthPinger 62 server *http.Server 63 listener net.Listener 64 tlsConfig *tls.Config 65 waiter sync.WaitGroup 66 man *autocert.Manager 67 closer chan struct{} 68 } 69 70 // NewServer returns a new server which uses http instead of https. 71 func NewServer(handler http.Handler, shutdown ...time.Duration) *Server { 72 var shutdownDur = shutdownDuration 73 if len(shutdown) != 0 { 74 shutdownDur = shutdown[0] 75 } 76 77 var health healthPinger 78 var server Server 79 server.http2 = false 80 server.health = &health 81 server.handler = handler 82 server.shutdownTimeout = shutdownDur 83 return &server 84 } 85 86 // NewServerWithTLS returns a new server which uses the provided tlsconfig for https connections. 87 func NewServerWithTLS(http2 bool, tconfig *tls.Config, handler http.Handler, shutdown ...time.Duration) *Server { 88 var shutdownDur = shutdownDuration 89 if len(shutdown) != 0 { 90 shutdownDur = shutdown[0] 91 } 92 93 var health healthPinger 94 var server Server 95 server.http2 = http2 96 server.health = &health 97 server.handler = handler 98 server.tlsConfig = tconfig 99 server.shutdownTimeout = shutdownDur 100 return &server 101 } 102 103 // NewServerWithCertMan returns a new server which uses the provided autocert certificate 104 // manager to provide http certificate. 105 func NewServerWithCertMan(http2 bool, man *autocert.Manager, handler http.Handler, shutdown ...time.Duration) *Server { 106 var shutdownDur = shutdownDuration 107 if len(shutdown) != 0 { 108 shutdownDur = shutdown[0] 109 } 110 111 var health healthPinger 112 var server Server 113 server.man = man 114 server.http2 = http2 115 server.health = &health 116 server.handler = handler 117 server.shutdownTimeout = shutdownDur 118 return &server 119 } 120 121 // Listen creates new http listen for giving addr and returns any error 122 // that occurs in attempt to starting the server. 123 func (s *Server) Listen(ctx context.Context, addr string) error { 124 s.closer = make(chan struct{}) 125 126 var tlsConfig = s.tlsConfig 127 if tlsConfig == nil && s.man == nil { 128 tlsConfig = &tls.Config{ 129 GetCertificate: s.man.GetCertificate, 130 } 131 } 132 133 if s.http2 { 134 tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2") 135 } 136 137 var listener, err = netutils.MakeListener("tcp", addr, tlsConfig) 138 if err != nil { 139 return err 140 } 141 142 var server = &http.Server{ 143 Addr: addr, 144 Handler: s.handler, 145 ReadTimeout: 30 * time.Second, 146 WriteTimeout: 30 * time.Second, 147 MaxHeaderBytes: 1 << 20, 148 TLSConfig: tlsConfig, 149 } 150 151 s.health.setHealthy() 152 153 var errs = make(chan error, 1) 154 s.waiter.Add(1) 155 go func() { 156 defer s.waiter.Done() 157 if err := server.Serve(netutils.NewKeepAliveListener(listener)); err != nil { 158 s.health.setUnhealthy() 159 errs <- err 160 } 161 }() 162 163 var signals = make(chan os.Signal, 1) 164 signal.Notify(signals, 165 syscall.SIGTERM, 166 os.Interrupt, 167 syscall.SIGINT, 168 syscall.SIGTERM, 169 syscall.SIGQUIT, 170 syscall.SIGKILL, 171 ) 172 173 s.waiter.Add(1) 174 go func() { 175 defer s.waiter.Done() 176 select { 177 case <-ctx.Done(): 178 // server was called to close. 179 s.gracefulShutdown(server) 180 case <-s.closer: 181 // server was closed intentionally. 182 s.gracefulShutdown(server) 183 case <-signals: 184 // server received signal to close entirely. 185 s.gracefulShutdown(server) 186 } 187 }() 188 189 return <-errs 190 } 191 192 func (s *Server) gracefulShutdown(server *http.Server) { 193 s.health.setUnhealthy() 194 time.Sleep(20 * time.Second) 195 var ctx, cancel = context.WithTimeout(context.Background(), s.shutdownTimeout) 196 if err := server.Shutdown(ctx); err != nil { 197 log.Printf("Close server returned error: %+q", nerror.WrapOnly(err)) 198 } 199 cancel() 200 } 201 202 // Close closes giving server 203 func (s *Server) Close() { 204 select { 205 case <-s.closer: 206 return 207 default: 208 close(s.closer) 209 } 210 } 211 212 // Wait blocks till server is closed. 213 func (s *Server) Wait(after ...func()) { 214 s.waiter.Wait() 215 for _, cb := range after { 216 cb() 217 } 218 } 219 220 // TLSManager returns the autocert.Manager associated with the giving server 221 // for its tls certificates. 222 func (s *Server) TLSManager() *autocert.Manager { 223 return s.man 224 } 225 226 // Health returns the HealthPinger for giving server. 227 func (s *Server) Health() HealthPinger { 228 return s.health 229 }