github.com/gogf/gf/v2@v2.7.4/net/ghttp/ghttp_server_graceful.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package ghttp 8 9 import ( 10 "context" 11 "crypto/tls" 12 "fmt" 13 "log" 14 "net" 15 "net/http" 16 "os" 17 "strconv" 18 "sync" 19 "time" 20 21 "github.com/gogf/gf/v2/container/gtype" 22 "github.com/gogf/gf/v2/errors/gcode" 23 "github.com/gogf/gf/v2/errors/gerror" 24 "github.com/gogf/gf/v2/os/gproc" 25 "github.com/gogf/gf/v2/os/gres" 26 "github.com/gogf/gf/v2/text/gstr" 27 ) 28 29 // gracefulServer wraps the net/http.Server with graceful reload/restart feature. 30 type gracefulServer struct { 31 server *Server // Belonged server. 32 fd uintptr // File descriptor for passing to the child process when graceful reload. 33 address string // Listening address like:":80", ":8080". 34 httpServer *http.Server // Underlying http.Server. 35 rawListener net.Listener // Underlying net.Listener. 36 rawLnMu sync.RWMutex // Concurrent safety mutex for `rawListener`. 37 listener net.Listener // Wrapped net.Listener. 38 isHttps bool // Is HTTPS. 39 status *gtype.Int // Status of current server. Using `gtype` to ensure concurrent safety. 40 } 41 42 // newGracefulServer creates and returns a graceful http server with a given address. 43 // The optional parameter `fd` specifies the file descriptor which is passed from parent server. 44 func (s *Server) newGracefulServer(address string, fd ...int) *gracefulServer { 45 // Change port to address like: 80 -> :80 46 if gstr.IsNumeric(address) { 47 address = ":" + address 48 } 49 gs := &gracefulServer{ 50 server: s, 51 address: address, 52 httpServer: s.newHttpServer(address), 53 status: gtype.NewInt(), 54 } 55 if len(fd) > 0 && fd[0] > 0 { 56 gs.fd = uintptr(fd[0]) 57 } 58 if s.config.Listeners != nil { 59 addrArray := gstr.SplitAndTrim(address, ":") 60 addrPort, err := strconv.Atoi(addrArray[len(addrArray)-1]) 61 if err == nil { 62 for _, v := range s.config.Listeners { 63 if listenerPort := (v.Addr().(*net.TCPAddr)).Port; listenerPort == addrPort { 64 gs.rawListener = v 65 break 66 } 67 } 68 } 69 } 70 return gs 71 } 72 73 // newHttpServer creates and returns an underlying http.Server with a given address. 74 func (s *Server) newHttpServer(address string) *http.Server { 75 server := &http.Server{ 76 Addr: address, 77 Handler: http.HandlerFunc(s.config.Handler), 78 ReadTimeout: s.config.ReadTimeout, 79 WriteTimeout: s.config.WriteTimeout, 80 IdleTimeout: s.config.IdleTimeout, 81 MaxHeaderBytes: s.config.MaxHeaderBytes, 82 ErrorLog: log.New(&errorLogger{logger: s.config.Logger}, "", 0), 83 } 84 server.SetKeepAlivesEnabled(s.config.KeepAlive) 85 return server 86 } 87 88 // Fd retrieves and returns the file descriptor of the current server. 89 // It is available ony in *nix like operating systems like linux, unix, darwin. 90 func (s *gracefulServer) Fd() uintptr { 91 if ln := s.getRawListener(); ln != nil { 92 file, err := ln.(*net.TCPListener).File() 93 if err == nil { 94 return file.Fd() 95 } 96 } 97 return 0 98 } 99 100 // CreateListener creates listener on configured address. 101 func (s *gracefulServer) CreateListener() error { 102 ln, err := s.getNetListener() 103 if err != nil { 104 return err 105 } 106 s.listener = ln 107 s.setRawListener(ln) 108 return nil 109 } 110 111 // CreateListenerTLS creates listener on configured address with HTTPS. 112 // The parameter `certFile` and `keyFile` specify the necessary certification and key files for HTTPS. 113 // The optional parameter `tlsConfig` specifies the custom TLS configuration. 114 func (s *gracefulServer) CreateListenerTLS(certFile, keyFile string, tlsConfig ...*tls.Config) error { 115 var config *tls.Config 116 if len(tlsConfig) > 0 && tlsConfig[0] != nil { 117 config = tlsConfig[0] 118 } else if s.httpServer.TLSConfig != nil { 119 config = s.httpServer.TLSConfig 120 } else { 121 config = &tls.Config{} 122 } 123 if config.NextProtos == nil { 124 config.NextProtos = []string{"http/1.1"} 125 } 126 var err error 127 if len(config.Certificates) == 0 { 128 config.Certificates = make([]tls.Certificate, 1) 129 if gres.Contains(certFile) { 130 config.Certificates[0], err = tls.X509KeyPair( 131 gres.GetContent(certFile), 132 gres.GetContent(keyFile), 133 ) 134 } else { 135 config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) 136 } 137 } 138 if err != nil { 139 return gerror.Wrapf(err, `open certFile "%s" and keyFile "%s" failed`, certFile, keyFile) 140 } 141 ln, err := s.getNetListener() 142 if err != nil { 143 return err 144 } 145 146 s.listener = tls.NewListener(ln, config) 147 s.setRawListener(ln) 148 return nil 149 } 150 151 // Serve starts the serving with blocking way. 152 func (s *gracefulServer) Serve(ctx context.Context) error { 153 if s.rawListener == nil { 154 return gerror.NewCode(gcode.CodeInvalidOperation, `call CreateListener/CreateListenerTLS before Serve`) 155 } 156 157 action := "started" 158 if s.fd != 0 { 159 action = "reloaded" 160 } 161 s.server.Logger().Infof( 162 ctx, 163 `pid[%d]: %s server %s listening on [%s]`, 164 gproc.Pid(), s.getProto(), action, s.GetListenedAddress(), 165 ) 166 s.status.Set(ServerStatusRunning) 167 err := s.httpServer.Serve(s.listener) 168 s.status.Set(ServerStatusStopped) 169 return err 170 } 171 172 // GetListenedAddress retrieves and returns the address string which are listened by current server. 173 func (s *gracefulServer) GetListenedAddress() string { 174 if !gstr.Contains(s.address, FreePortAddress) { 175 return s.address 176 } 177 var ( 178 address = s.address 179 listenedPort = s.GetListenedPort() 180 ) 181 address = gstr.Replace(address, FreePortAddress, fmt.Sprintf(`:%d`, listenedPort)) 182 return address 183 } 184 185 // GetListenedPort retrieves and returns one port which is listened to by current server. 186 // Note that this method is only available if the server is listening on one port. 187 func (s *gracefulServer) GetListenedPort() int { 188 if ln := s.getRawListener(); ln != nil { 189 return ln.Addr().(*net.TCPAddr).Port 190 } 191 return -1 192 } 193 194 // getProto retrieves and returns the proto string of current server. 195 func (s *gracefulServer) getProto() string { 196 proto := "http" 197 if s.isHttps { 198 proto = "https" 199 } 200 return proto 201 } 202 203 // getNetListener retrieves and returns the wrapped net.Listener. 204 func (s *gracefulServer) getNetListener() (net.Listener, error) { 205 if s.rawListener != nil { 206 return s.rawListener, nil 207 } 208 var ( 209 ln net.Listener 210 err error 211 ) 212 if s.fd > 0 { 213 f := os.NewFile(s.fd, "") 214 ln, err = net.FileListener(f) 215 if err != nil { 216 err = gerror.Wrap(err, "net.FileListener failed") 217 return nil, err 218 } 219 } else { 220 ln, err = net.Listen("tcp", s.httpServer.Addr) 221 if err != nil { 222 err = gerror.Wrapf(err, `net.Listen address "%s" failed`, s.httpServer.Addr) 223 } 224 } 225 return ln, err 226 } 227 228 // shutdown shuts down the server gracefully. 229 func (s *gracefulServer) shutdown(ctx context.Context) { 230 if s.status.Val() == ServerStatusStopped { 231 return 232 } 233 timeoutCtx, cancelFunc := context.WithTimeout( 234 ctx, 235 time.Duration(s.server.config.GracefulShutdownTimeout)*time.Second, 236 ) 237 defer cancelFunc() 238 if err := s.httpServer.Shutdown(timeoutCtx); err != nil { 239 s.server.Logger().Errorf( 240 ctx, 241 "%d: %s server [%s] shutdown error: %v", 242 gproc.Pid(), s.getProto(), s.address, err, 243 ) 244 } 245 } 246 247 // setRawListener sets `rawListener` with given net.Listener. 248 func (s *gracefulServer) setRawListener(ln net.Listener) { 249 s.rawLnMu.Lock() 250 defer s.rawLnMu.Unlock() 251 s.rawListener = ln 252 } 253 254 // setRawListener returns the `rawListener` of current server. 255 func (s *gracefulServer) getRawListener() net.Listener { 256 s.rawLnMu.RLock() 257 defer s.rawLnMu.RUnlock() 258 return s.rawListener 259 } 260 261 // close shuts down the server forcibly. 262 // for graceful shutdown, please use gracefulServer.shutdown. 263 func (s *gracefulServer) close(ctx context.Context) { 264 if s.status.Val() == ServerStatusStopped { 265 return 266 } 267 if err := s.httpServer.Close(); err != nil { 268 s.server.Logger().Errorf( 269 ctx, 270 "%d: %s server [%s] closed error: %v", 271 gproc.Pid(), s.getProto(), s.address, err, 272 ) 273 } 274 }