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

     1  /*
     2   * MinIO Cloud Storage, (C) 2017 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  	"fmt"
    21  	"io"
    22  	"net"
    23  	"os"
    24  	"sync"
    25  	"syscall"
    26  )
    27  
    28  type acceptResult struct {
    29  	conn net.Conn
    30  	err  error
    31  }
    32  
    33  // httpListener - HTTP listener capable of handling multiple server addresses.
    34  type httpListener struct {
    35  	mutex        sync.Mutex         // to guard Close() method.
    36  	tcpListeners []*net.TCPListener // underlaying TCP listeners.
    37  	acceptCh     chan acceptResult  // channel where all TCP listeners write accepted connection.
    38  	doneCh       chan struct{}      // done channel for TCP listener goroutines.
    39  }
    40  
    41  // isRoutineNetErr returns true if error is due to a network timeout,
    42  // connect reset or io.EOF and false otherwise
    43  func isRoutineNetErr(err error) bool {
    44  	if err == nil {
    45  		return false
    46  	}
    47  	if nErr, ok := err.(*net.OpError); ok {
    48  		// Check if the error is a tcp connection reset
    49  		if syscallErr, ok := nErr.Err.(*os.SyscallError); ok {
    50  			if errno, ok := syscallErr.Err.(syscall.Errno); ok {
    51  				return errno == syscall.ECONNRESET
    52  			}
    53  		}
    54  		// Check if the error is a timeout
    55  		return nErr.Timeout()
    56  	}
    57  	// check for io.EOF and also some times io.EOF is wrapped is another error type.
    58  	return err == io.EOF || err.Error() == "EOF"
    59  }
    60  
    61  // start - starts separate goroutine for each TCP listener.  A valid new connection is passed to httpListener.acceptCh.
    62  func (listener *httpListener) start() {
    63  	listener.acceptCh = make(chan acceptResult)
    64  	listener.doneCh = make(chan struct{})
    65  
    66  	// Closure to send acceptResult to acceptCh.
    67  	// It returns true if the result is sent else false if returns when doneCh is closed.
    68  	send := func(result acceptResult, doneCh <-chan struct{}) bool {
    69  		select {
    70  		case listener.acceptCh <- result:
    71  			// Successfully written to acceptCh
    72  			return true
    73  		case <-doneCh:
    74  			// As stop signal is received, close accepted connection.
    75  			if result.conn != nil {
    76  				result.conn.Close()
    77  			}
    78  			return false
    79  		}
    80  	}
    81  
    82  	// Closure to handle single connection.
    83  	handleConn := func(tcpConn *net.TCPConn, doneCh <-chan struct{}) {
    84  		tcpConn.SetKeepAlive(true)
    85  		send(acceptResult{tcpConn, nil}, doneCh)
    86  	}
    87  
    88  	// Closure to handle TCPListener until done channel is closed.
    89  	handleListener := func(tcpListener *net.TCPListener, doneCh <-chan struct{}) {
    90  		for {
    91  			tcpConn, err := tcpListener.AcceptTCP()
    92  			if err != nil {
    93  				// Returns when send fails.
    94  				if !send(acceptResult{nil, err}, doneCh) {
    95  					return
    96  				}
    97  			} else {
    98  				go handleConn(tcpConn, doneCh)
    99  			}
   100  		}
   101  	}
   102  
   103  	// Start separate goroutine for each TCP listener to handle connection.
   104  	for _, tcpListener := range listener.tcpListeners {
   105  		go handleListener(tcpListener, listener.doneCh)
   106  	}
   107  }
   108  
   109  // Accept - reads from httpListener.acceptCh for one of previously accepted TCP connection and returns the same.
   110  func (listener *httpListener) Accept() (conn net.Conn, err error) {
   111  	result, ok := <-listener.acceptCh
   112  	if ok {
   113  		return result.conn, result.err
   114  	}
   115  
   116  	return nil, syscall.EINVAL
   117  }
   118  
   119  // Close - closes underneath all TCP listeners.
   120  func (listener *httpListener) Close() (err error) {
   121  	listener.mutex.Lock()
   122  	defer listener.mutex.Unlock()
   123  	if listener.doneCh == nil {
   124  		return syscall.EINVAL
   125  	}
   126  
   127  	for i := range listener.tcpListeners {
   128  		listener.tcpListeners[i].Close()
   129  	}
   130  	close(listener.doneCh)
   131  
   132  	listener.doneCh = nil
   133  	return nil
   134  }
   135  
   136  // Addr - net.Listener interface compatible method returns net.Addr.  In case of multiple TCP listeners, it returns '0.0.0.0' as IP address.
   137  func (listener *httpListener) Addr() (addr net.Addr) {
   138  	addr = listener.tcpListeners[0].Addr()
   139  	if len(listener.tcpListeners) == 1 {
   140  		return addr
   141  	}
   142  
   143  	tcpAddr := addr.(*net.TCPAddr)
   144  	if ip := net.ParseIP("0.0.0.0"); ip != nil {
   145  		tcpAddr.IP = ip
   146  	}
   147  
   148  	addr = tcpAddr
   149  	return addr
   150  }
   151  
   152  // Addrs - returns all address information of TCP listeners.
   153  func (listener *httpListener) Addrs() (addrs []net.Addr) {
   154  	for i := range listener.tcpListeners {
   155  		addrs = append(addrs, listener.tcpListeners[i].Addr())
   156  	}
   157  
   158  	return addrs
   159  }
   160  
   161  // newHTTPListener - creates new httpListener object which is interface compatible to net.Listener.
   162  // httpListener is capable to
   163  // * listen to multiple addresses
   164  // * controls incoming connections only doing HTTP protocol
   165  func newHTTPListener(serverAddrs []string) (listener *httpListener, err error) {
   166  
   167  	var tcpListeners []*net.TCPListener
   168  
   169  	// Close all opened listeners on error
   170  	defer func() {
   171  		if err == nil {
   172  			return
   173  		}
   174  
   175  		for _, tcpListener := range tcpListeners {
   176  			// Ignore error on close.
   177  			tcpListener.Close()
   178  		}
   179  	}()
   180  
   181  	for _, serverAddr := range serverAddrs {
   182  		var l net.Listener
   183  		if l, err = listen("tcp", serverAddr); err != nil {
   184  			if l, err = fallbackListen("tcp", serverAddr); err != nil {
   185  				return nil, err
   186  			}
   187  		}
   188  
   189  		tcpListener, ok := l.(*net.TCPListener)
   190  		if !ok {
   191  			return nil, fmt.Errorf("unexpected listener type found %v, expected net.TCPListener", l)
   192  		}
   193  
   194  		tcpListeners = append(tcpListeners, tcpListener)
   195  	}
   196  
   197  	listener = &httpListener{
   198  		tcpListeners: tcpListeners,
   199  	}
   200  	listener.start()
   201  
   202  	return listener, nil
   203  }