github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/http/server.go (about) 1 // Copyright (c) 2015-2023 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package http 19 20 import ( 21 "context" 22 "crypto/tls" 23 "errors" 24 "log" 25 "math/rand" 26 "net" 27 "net/http" 28 "os" 29 "runtime/pprof" 30 "sync" 31 "sync/atomic" 32 "time" 33 34 "github.com/dustin/go-humanize" 35 ) 36 37 var ( 38 // GlobalMinIOVersion - is sent in the header to all http targets 39 GlobalMinIOVersion string 40 41 // GlobalDeploymentID - is sent in the header to all http targets 42 GlobalDeploymentID string 43 ) 44 45 const ( 46 shutdownPollIntervalMax = 500 * time.Millisecond 47 48 // DefaultShutdownTimeout - default shutdown timeout to gracefully shutdown server. 49 DefaultShutdownTimeout = 5 * time.Second 50 51 // DefaultIdleTimeout for idle inactive connections 52 DefaultIdleTimeout = 30 * time.Second 53 54 // DefaultReadHeaderTimeout for very slow inactive connections 55 DefaultReadHeaderTimeout = 30 * time.Second 56 57 // DefaultMaxHeaderBytes - default maximum HTTP header size in bytes. 58 DefaultMaxHeaderBytes = 1 * humanize.MiByte 59 ) 60 61 // Server - extended http.Server supports multiple addresses to serve and enhanced connection handling. 62 type Server struct { 63 http.Server 64 Addrs []string // addresses on which the server listens for new connection. 65 TCPOptions TCPOptions // all the configurable TCP conn specific configurable options. 66 ShutdownTimeout time.Duration // timeout used for graceful server shutdown. 67 listenerMutex sync.Mutex // to guard 'listener' field. 68 listener *httpListener // HTTP listener for all 'Addrs' field. 69 inShutdown uint32 // indicates whether the server is in shutdown or not 70 requestCount int32 // counter holds no. of request in progress. 71 } 72 73 // GetRequestCount - returns number of request in progress. 74 func (srv *Server) GetRequestCount() int { 75 return int(atomic.LoadInt32(&srv.requestCount)) 76 } 77 78 // Init - init HTTP server 79 func (srv *Server) Init(listenCtx context.Context, listenErrCallback func(listenAddr string, err error)) (serve func() error, err error) { 80 // Take a copy of server fields. 81 var tlsConfig *tls.Config 82 if srv.TLSConfig != nil { 83 tlsConfig = srv.TLSConfig.Clone() 84 } 85 handler := srv.Handler // if srv.Handler holds non-synced state -> possible data race 86 87 // Create new HTTP listener. 88 var listener *httpListener 89 listener, listenErrs := newHTTPListener( 90 listenCtx, 91 srv.Addrs, 92 srv.TCPOptions, 93 ) 94 95 var interfaceFound bool 96 for i := range listenErrs { 97 if listenErrs[i] != nil { 98 listenErrCallback(srv.Addrs[i], listenErrs[i]) 99 } else { 100 interfaceFound = true 101 } 102 } 103 if !interfaceFound { 104 return nil, errors.New("no available interface found") 105 } 106 107 // Wrap given handler to do additional 108 // * return 503 (service unavailable) if the server in shutdown. 109 wrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 110 // If server is in shutdown. 111 if atomic.LoadUint32(&srv.inShutdown) != 0 { 112 // To indicate disable keep-alive, server is shutting down. 113 w.Header().Set("Connection", "close") 114 115 // Add 1 minute retry header, incase-client wants to honor it 116 w.Header().Set(RetryAfter, "60") 117 118 w.WriteHeader(http.StatusServiceUnavailable) 119 w.Write([]byte(http.ErrServerClosed.Error())) 120 return 121 } 122 123 atomic.AddInt32(&srv.requestCount, 1) 124 defer atomic.AddInt32(&srv.requestCount, -1) 125 126 // Handle request using passed handler. 127 handler.ServeHTTP(w, r) 128 }) 129 130 srv.listenerMutex.Lock() 131 srv.Handler = wrappedHandler 132 srv.listener = listener 133 srv.listenerMutex.Unlock() 134 135 var l net.Listener = listener 136 if tlsConfig != nil { 137 l = tls.NewListener(listener, tlsConfig) 138 } 139 140 serve = func() error { 141 return srv.Server.Serve(l) 142 } 143 144 return 145 } 146 147 // Shutdown - shuts down HTTP server. 148 func (srv *Server) Shutdown() error { 149 srv.listenerMutex.Lock() 150 if srv.listener == nil { 151 srv.listenerMutex.Unlock() 152 return http.ErrServerClosed 153 } 154 srv.listenerMutex.Unlock() 155 156 if atomic.AddUint32(&srv.inShutdown, 1) > 1 { 157 // shutdown in progress 158 return http.ErrServerClosed 159 } 160 161 // Close underneath HTTP listener. 162 srv.listenerMutex.Lock() 163 err := srv.listener.Close() 164 srv.listenerMutex.Unlock() 165 if err != nil { 166 return err 167 } 168 169 pollIntervalBase := time.Millisecond 170 nextPollInterval := func() time.Duration { 171 // Add 10% jitter. 172 interval := pollIntervalBase + time.Duration(rand.Intn(int(pollIntervalBase/10))) 173 // Double and clamp for next time. 174 pollIntervalBase *= 2 175 if pollIntervalBase > shutdownPollIntervalMax { 176 pollIntervalBase = shutdownPollIntervalMax 177 } 178 return interval 179 } 180 181 // Wait for opened connection to be closed up to Shutdown timeout. 182 shutdownTimeout := srv.ShutdownTimeout 183 shutdownTimer := time.NewTimer(shutdownTimeout) 184 defer shutdownTimer.Stop() 185 186 timer := time.NewTimer(nextPollInterval()) 187 defer timer.Stop() 188 for { 189 select { 190 case <-shutdownTimer.C: 191 if atomic.LoadInt32(&srv.requestCount) <= 0 { 192 return nil 193 } 194 195 // Write all running goroutines. 196 tmp, err := os.CreateTemp("", "minio-goroutines-*.txt") 197 if err == nil { 198 _ = pprof.Lookup("goroutine").WriteTo(tmp, 1) 199 tmp.Close() 200 return errors.New("timed out. some connections are still active. goroutines written to " + tmp.Name()) 201 } 202 return errors.New("timed out. some connections are still active") 203 case <-timer.C: 204 if atomic.LoadInt32(&srv.requestCount) <= 0 { 205 return nil 206 } 207 timer.Reset(nextPollInterval()) 208 } 209 } 210 } 211 212 // UseShutdownTimeout configure server shutdown timeout 213 func (srv *Server) UseShutdownTimeout(d time.Duration) *Server { 214 srv.ShutdownTimeout = d 215 return srv 216 } 217 218 // UseIdleTimeout configure idle connection timeout 219 func (srv *Server) UseIdleTimeout(d time.Duration) *Server { 220 srv.IdleTimeout = d 221 return srv 222 } 223 224 // UseReadHeaderTimeout configure read header timeout 225 func (srv *Server) UseReadHeaderTimeout(d time.Duration) *Server { 226 srv.ReadHeaderTimeout = d 227 return srv 228 } 229 230 // UseHandler configure final handler for this HTTP *Server 231 func (srv *Server) UseHandler(h http.Handler) *Server { 232 srv.Handler = h 233 return srv 234 } 235 236 // UseTLSConfig pass configured TLSConfig for this HTTP *Server 237 func (srv *Server) UseTLSConfig(cfg *tls.Config) *Server { 238 srv.TLSConfig = cfg 239 return srv 240 } 241 242 // UseBaseContext use custom base context for this HTTP *Server 243 func (srv *Server) UseBaseContext(ctx context.Context) *Server { 244 srv.BaseContext = func(listener net.Listener) context.Context { 245 return ctx 246 } 247 return srv 248 } 249 250 // UseCustomLogger use customized logger for this HTTP *Server 251 func (srv *Server) UseCustomLogger(l *log.Logger) *Server { 252 srv.ErrorLog = l 253 return srv 254 } 255 256 // UseTCPOptions use custom TCP options on raw socket 257 func (srv *Server) UseTCPOptions(opts TCPOptions) *Server { 258 srv.TCPOptions = opts 259 return srv 260 } 261 262 // NewServer - creates new HTTP server using given arguments. 263 func NewServer(addrs []string) *Server { 264 httpServer := &Server{ 265 Addrs: addrs, 266 } 267 // This is not configurable for now. 268 httpServer.MaxHeaderBytes = DefaultMaxHeaderBytes 269 return httpServer 270 } 271 272 // SetMinIOVersion -- MinIO version from the main package is set here 273 func SetMinIOVersion(version string) { 274 GlobalMinIOVersion = version 275 } 276 277 // SetDeploymentID -- Deployment Id from the main package is set here 278 func SetDeploymentID(deploymentID string) { 279 GlobalDeploymentID = deploymentID 280 }