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  }