storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/http/server.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2017, 2018 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package http
    18  
    19  import (
    20  	"crypto/tls"
    21  	"errors"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"runtime/pprof"
    25  	"sync"
    26  	"sync/atomic"
    27  	"time"
    28  
    29  	humanize "github.com/dustin/go-humanize"
    30  	"github.com/minio/minio-go/v7/pkg/set"
    31  
    32  	"storj.io/minio/cmd/config"
    33  	"storj.io/minio/cmd/config/api"
    34  	"storj.io/minio/pkg/certs"
    35  	"storj.io/minio/pkg/env"
    36  	"storj.io/minio/pkg/fips"
    37  )
    38  
    39  const (
    40  	serverShutdownPoll = 500 * time.Millisecond
    41  
    42  	// DefaultShutdownTimeout - default shutdown timeout used for graceful http server shutdown.
    43  	DefaultShutdownTimeout = 5 * time.Second
    44  
    45  	// DefaultMaxHeaderBytes - default maximum HTTP header size in bytes.
    46  	DefaultMaxHeaderBytes = 1 * humanize.MiByte
    47  )
    48  
    49  // Server - extended http.Server supports multiple addresses to serve and enhanced connection handling.
    50  type Server struct {
    51  	http.Server
    52  	Addrs           []string      // addresses on which the server listens for new connection.
    53  	ShutdownTimeout time.Duration // timeout used for graceful server shutdown.
    54  	listenerMutex   sync.Mutex    // to guard 'listener' field.
    55  	listener        *httpListener // HTTP listener for all 'Addrs' field.
    56  	inShutdown      uint32        // indicates whether the server is in shutdown or not
    57  	requestCount    int32         // counter holds no. of request in progress.
    58  }
    59  
    60  // GetRequestCount - returns number of request in progress.
    61  func (srv *Server) GetRequestCount() int {
    62  	return int(atomic.LoadInt32(&srv.requestCount))
    63  }
    64  
    65  // Start - start HTTP server
    66  func (srv *Server) Start() (err error) {
    67  	// Take a copy of server fields.
    68  	var tlsConfig *tls.Config
    69  	if srv.TLSConfig != nil {
    70  		tlsConfig = srv.TLSConfig.Clone()
    71  	}
    72  	handler := srv.Handler // if srv.Handler holds non-synced state -> possible data race
    73  
    74  	addrs := set.CreateStringSet(srv.Addrs...).ToSlice() // copy and remove duplicates
    75  
    76  	// Create new HTTP listener.
    77  	var listener *httpListener
    78  	listener, err = newHTTPListener(
    79  		addrs,
    80  	)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	// Wrap given handler to do additional
    86  	// * return 503 (service unavailable) if the server in shutdown.
    87  	wrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    88  		// If server is in shutdown.
    89  		if atomic.LoadUint32(&srv.inShutdown) != 0 {
    90  			// To indicate disable keep-alives
    91  			w.Header().Set("Connection", "close")
    92  			w.WriteHeader(http.StatusForbidden)
    93  			w.Write([]byte(http.ErrServerClosed.Error()))
    94  			w.(http.Flusher).Flush()
    95  			return
    96  		}
    97  
    98  		atomic.AddInt32(&srv.requestCount, 1)
    99  		defer atomic.AddInt32(&srv.requestCount, -1)
   100  
   101  		// Handle request using passed handler.
   102  		handler.ServeHTTP(w, r)
   103  	})
   104  
   105  	srv.listenerMutex.Lock()
   106  	srv.Handler = wrappedHandler
   107  	srv.listener = listener
   108  	srv.listenerMutex.Unlock()
   109  
   110  	// Start servicing with listener.
   111  	if tlsConfig != nil {
   112  		return srv.Server.Serve(tls.NewListener(listener, tlsConfig))
   113  	}
   114  	return srv.Server.Serve(listener)
   115  }
   116  
   117  // Shutdown - shuts down HTTP server.
   118  func (srv *Server) Shutdown() error {
   119  	srv.listenerMutex.Lock()
   120  	if srv.listener == nil {
   121  		srv.listenerMutex.Unlock()
   122  		return http.ErrServerClosed
   123  	}
   124  	srv.listenerMutex.Unlock()
   125  
   126  	if atomic.AddUint32(&srv.inShutdown, 1) > 1 {
   127  		// shutdown in progress
   128  		return http.ErrServerClosed
   129  	}
   130  
   131  	// Close underneath HTTP listener.
   132  	srv.listenerMutex.Lock()
   133  	err := srv.listener.Close()
   134  	srv.listenerMutex.Unlock()
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	// Wait for opened connection to be closed up to Shutdown timeout.
   140  	shutdownTimeout := srv.ShutdownTimeout
   141  	shutdownTimer := time.NewTimer(shutdownTimeout)
   142  	ticker := time.NewTicker(serverShutdownPoll)
   143  	defer ticker.Stop()
   144  	for {
   145  		select {
   146  		case <-shutdownTimer.C:
   147  			// Write all running goroutines.
   148  			tmp, err := ioutil.TempFile("", "minio-goroutines-*.txt")
   149  			if err == nil {
   150  				_ = pprof.Lookup("goroutine").WriteTo(tmp, 1)
   151  				tmp.Close()
   152  				return errors.New("timed out. some connections are still active. goroutines written to " + tmp.Name())
   153  			}
   154  			return errors.New("timed out. some connections are still active")
   155  		case <-ticker.C:
   156  			if atomic.LoadInt32(&srv.requestCount) <= 0 {
   157  				return nil
   158  			}
   159  		}
   160  	}
   161  }
   162  
   163  // NewServer - creates new HTTP server using given arguments.
   164  func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificateFunc) *Server {
   165  	secureCiphers := env.Get(api.EnvAPISecureCiphers, config.EnableOn) == config.EnableOn
   166  
   167  	var tlsConfig *tls.Config
   168  	if getCert != nil {
   169  		tlsConfig = &tls.Config{
   170  			PreferServerCipherSuites: true,
   171  			MinVersion:               tls.VersionTLS12,
   172  			NextProtos:               []string{"http/1.1", "h2"},
   173  			GetCertificate:           getCert,
   174  		}
   175  		if secureCiphers || fips.Enabled() {
   176  			tlsConfig.CipherSuites = fips.CipherSuitesTLS()
   177  			tlsConfig.CurvePreferences = fips.EllipticCurvesTLS()
   178  		}
   179  	}
   180  
   181  	httpServer := &Server{
   182  		Addrs:           addrs,
   183  		ShutdownTimeout: DefaultShutdownTimeout,
   184  	}
   185  	httpServer.Handler = handler
   186  	httpServer.TLSConfig = tlsConfig
   187  	httpServer.MaxHeaderBytes = DefaultMaxHeaderBytes
   188  
   189  	return httpServer
   190  }