github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/http/listener.go (about) 1 // Copyright (c) 2015-2021 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 "fmt" 23 "net" 24 "syscall" 25 "time" 26 27 "github.com/minio/minio/internal/deadlineconn" 28 ) 29 30 type acceptResult struct { 31 conn net.Conn 32 err error 33 lidx int 34 } 35 36 // httpListener - HTTP listener capable of handling multiple server addresses. 37 type httpListener struct { 38 opts TCPOptions 39 tcpListeners []*net.TCPListener // underlying TCP listeners. 40 acceptCh chan acceptResult // channel where all TCP listeners write accepted connection. 41 ctx context.Context 42 ctxCanceler context.CancelFunc 43 } 44 45 // start - starts separate goroutine for each TCP listener. A valid new connection is passed to httpListener.acceptCh. 46 func (listener *httpListener) start() { 47 // Closure to send acceptResult to acceptCh. 48 // It returns true if the result is sent else false if returns when doneCh is closed. 49 send := func(result acceptResult) bool { 50 select { 51 case listener.acceptCh <- result: 52 // Successfully written to acceptCh 53 return true 54 case <-listener.ctx.Done(): 55 return false 56 } 57 } 58 59 // Closure to handle TCPListener until done channel is closed. 60 handleListener := func(idx int, tcpListener *net.TCPListener) { 61 for { 62 tcpConn, err := tcpListener.AcceptTCP() 63 if tcpConn != nil { 64 tcpConn.SetKeepAlive(true) 65 } 66 send(acceptResult{tcpConn, err, idx}) 67 } 68 } 69 70 // Start separate goroutine for each TCP listener to handle connection. 71 for idx, tcpListener := range listener.tcpListeners { 72 go handleListener(idx, tcpListener) 73 } 74 } 75 76 // Accept - reads from httpListener.acceptCh for one of previously accepted TCP connection and returns the same. 77 func (listener *httpListener) Accept() (conn net.Conn, err error) { 78 select { 79 case result, ok := <-listener.acceptCh: 80 if ok { 81 return deadlineconn.New(result.conn). 82 WithReadDeadline(listener.opts.ClientReadTimeout). 83 WithWriteDeadline(listener.opts.ClientWriteTimeout), result.err 84 } 85 case <-listener.ctx.Done(): 86 } 87 return nil, syscall.EINVAL 88 } 89 90 // Close - closes underneath all TCP listeners. 91 func (listener *httpListener) Close() (err error) { 92 listener.ctxCanceler() 93 94 for i := range listener.tcpListeners { 95 listener.tcpListeners[i].Close() 96 } 97 98 return nil 99 } 100 101 // Addr - net.Listener interface compatible method returns net.Addr. In case of multiple TCP listeners, it returns '0.0.0.0' as IP address. 102 func (listener *httpListener) Addr() (addr net.Addr) { 103 addr = listener.tcpListeners[0].Addr() 104 if len(listener.tcpListeners) == 1 { 105 return addr 106 } 107 108 tcpAddr := addr.(*net.TCPAddr) 109 if ip := net.ParseIP("0.0.0.0"); ip != nil { 110 tcpAddr.IP = ip 111 } 112 113 addr = tcpAddr 114 return addr 115 } 116 117 // Addrs - returns all address information of TCP listeners. 118 func (listener *httpListener) Addrs() (addrs []net.Addr) { 119 for i := range listener.tcpListeners { 120 addrs = append(addrs, listener.tcpListeners[i].Addr()) 121 } 122 123 return addrs 124 } 125 126 // TCPOptions specify customizable TCP optimizations on raw socket 127 type TCPOptions struct { 128 UserTimeout int // this value is expected to be in milliseconds 129 ClientReadTimeout time.Duration // When the net.Conn is idle for more than ReadTimeout duration, we close the connection on the client proactively. 130 ClientWriteTimeout time.Duration // When the net.Conn is idle for more than WriteTimeout duration, we close the connection on the client proactively. 131 Interface string // this is a VRF device passed via `--interface` flag 132 Trace func(msg string) // Trace when starting. 133 } 134 135 // newHTTPListener - creates new httpListener object which is interface compatible to net.Listener. 136 // httpListener is capable to 137 // * listen to multiple addresses 138 // * controls incoming connections only doing HTTP protocol 139 func newHTTPListener(ctx context.Context, serverAddrs []string, opts TCPOptions) (listener *httpListener, listenErrs []error) { 140 tcpListeners := make([]*net.TCPListener, 0, len(serverAddrs)) 141 listenErrs = make([]error, len(serverAddrs)) 142 143 // Unix listener with special TCP options. 144 listenCfg := net.ListenConfig{ 145 Control: setTCPParametersFn(opts), 146 } 147 148 for i, serverAddr := range serverAddrs { 149 var ( 150 l net.Listener 151 e error 152 ) 153 if l, e = listenCfg.Listen(ctx, "tcp", serverAddr); e != nil { 154 if opts.Trace != nil { 155 opts.Trace(fmt.Sprint("listenCfg.Listen: ", e.Error())) 156 } 157 158 listenErrs[i] = e 159 continue 160 } 161 162 tcpListener, ok := l.(*net.TCPListener) 163 if !ok { 164 listenErrs[i] = fmt.Errorf("unexpected listener type found %v, expected net.TCPListener", l) 165 if opts.Trace != nil { 166 opts.Trace(fmt.Sprint("net.TCPListener: ", listenErrs[i].Error())) 167 } 168 continue 169 } 170 if opts.Trace != nil { 171 opts.Trace(fmt.Sprint("adding listener to ", tcpListener.Addr())) 172 } 173 tcpListeners = append(tcpListeners, tcpListener) 174 } 175 176 if len(tcpListeners) == 0 { 177 // No listeners initialized, no need to continue 178 return 179 } 180 181 listener = &httpListener{ 182 tcpListeners: tcpListeners, 183 acceptCh: make(chan acceptResult, len(tcpListeners)), 184 opts: opts, 185 } 186 listener.ctx, listener.ctxCanceler = context.WithCancel(ctx) 187 if opts.Trace != nil { 188 opts.Trace(fmt.Sprint("opening ", len(listener.tcpListeners), " listeners")) 189 } 190 listener.start() 191 192 return 193 }