mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 10:11:09 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			345 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			345 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
 * Minio Cloud Storage, (C) 2016 Minio, Inc.
 | 
						|
 *
 | 
						|
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
 * you may not use this file except in compliance with the License.
 | 
						|
 * You may obtain a copy of the License at
 | 
						|
 *
 | 
						|
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 *
 | 
						|
 * Unless required by applicable law or agreed to in writing, software
 | 
						|
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
 * See the License for the specific language governing permissions and
 | 
						|
 * limitations under the License.
 | 
						|
 */
 | 
						|
 | 
						|
package cmd
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/tls"
 | 
						|
	"errors"
 | 
						|
	"io"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
var defaultHTTP2Methods = []string{
 | 
						|
	"PRI",
 | 
						|
}
 | 
						|
 | 
						|
var defaultHTTP1Methods = []string{
 | 
						|
	"OPTIONS",
 | 
						|
	"GET",
 | 
						|
	"HEAD",
 | 
						|
	"POST",
 | 
						|
	"PUT",
 | 
						|
	"DELETE",
 | 
						|
	"TRACE",
 | 
						|
	"CONNECT",
 | 
						|
}
 | 
						|
 | 
						|
// ConnBuf - contains network buffer to record data
 | 
						|
type ConnBuf struct {
 | 
						|
	buffer []byte
 | 
						|
	unRead bool
 | 
						|
	offset int
 | 
						|
}
 | 
						|
 | 
						|
// ConnMux - implements a Read() which streams twice the firs bytes from
 | 
						|
// the incoming connection, to help peeking protocol
 | 
						|
type ConnMux struct {
 | 
						|
	net.Conn
 | 
						|
	lastError error
 | 
						|
	dataBuf   ConnBuf
 | 
						|
}
 | 
						|
 | 
						|
func longestWord(strings []string) int {
 | 
						|
	maxLen := 0
 | 
						|
	for _, m := range defaultHTTP1Methods {
 | 
						|
		if maxLen < len(m) {
 | 
						|
			maxLen = len(m)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, m := range defaultHTTP2Methods {
 | 
						|
		if maxLen < len(m) {
 | 
						|
			maxLen = len(m)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return maxLen
 | 
						|
}
 | 
						|
 | 
						|
// NewConnMux - creates a new ConnMux instance
 | 
						|
func NewConnMux(c net.Conn) *ConnMux {
 | 
						|
	h1 := longestWord(defaultHTTP1Methods)
 | 
						|
	h2 := longestWord(defaultHTTP2Methods)
 | 
						|
	max := h1
 | 
						|
	if h2 > max {
 | 
						|
		max = h2
 | 
						|
	}
 | 
						|
	return &ConnMux{Conn: c, dataBuf: ConnBuf{buffer: make([]byte, max+1)}}
 | 
						|
}
 | 
						|
 | 
						|
// PeekProtocol - reads the first bytes, then checks if it is similar
 | 
						|
// to one of the default http methods
 | 
						|
func (c *ConnMux) PeekProtocol() string {
 | 
						|
	var n int
 | 
						|
	n, c.lastError = c.Conn.Read(c.dataBuf.buffer)
 | 
						|
	if n == 0 || (c.lastError != nil && c.lastError != io.EOF) {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	c.dataBuf.unRead = true
 | 
						|
	for _, m := range defaultHTTP1Methods {
 | 
						|
		if strings.HasPrefix(string(c.dataBuf.buffer), m) {
 | 
						|
			return "http"
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, m := range defaultHTTP2Methods {
 | 
						|
		if strings.HasPrefix(string(c.dataBuf.buffer), m) {
 | 
						|
			return "http2"
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return "tls"
 | 
						|
}
 | 
						|
 | 
						|
// Read - streams the ConnMux buffer when reset flag is activated, otherwise
 | 
						|
// streams from the incoming network connection
 | 
						|
func (c *ConnMux) Read(b []byte) (int, error) {
 | 
						|
	if c.dataBuf.unRead {
 | 
						|
		n := copy(b, c.dataBuf.buffer[c.dataBuf.offset:])
 | 
						|
		c.dataBuf.offset += n
 | 
						|
		if c.dataBuf.offset == len(c.dataBuf.buffer) {
 | 
						|
			// We finished copying all c.buffer, reset all
 | 
						|
			c.dataBuf.unRead = false
 | 
						|
			c.dataBuf.offset = 0
 | 
						|
			c.dataBuf.buffer = c.dataBuf.buffer[:]
 | 
						|
			if n < len(b) {
 | 
						|
				// Continue copying from socket if b still has room for data
 | 
						|
				tmpBuffer := make([]byte, len(b)-n-1)
 | 
						|
				nr, err := c.Conn.Read(tmpBuffer)
 | 
						|
				for idx, val := range tmpBuffer {
 | 
						|
					b[n+idx] = val
 | 
						|
				}
 | 
						|
				return n + nr, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// We here return the last error
 | 
						|
		return n, c.lastError
 | 
						|
	}
 | 
						|
	return c.Conn.Read(b)
 | 
						|
}
 | 
						|
 | 
						|
// ListenerMux - encapuslates the standard net.Listener to inspect
 | 
						|
// the communication protocol upon network connection
 | 
						|
type ListenerMux struct {
 | 
						|
	net.Listener
 | 
						|
	config *tls.Config
 | 
						|
}
 | 
						|
 | 
						|
// Accept - peek the protocol to decide if we should wrap the
 | 
						|
// network stream with the TLS server
 | 
						|
func (l *ListenerMux) Accept() (net.Conn, error) {
 | 
						|
	conn, err := l.Listener.Accept()
 | 
						|
	if err != nil {
 | 
						|
		return conn, err
 | 
						|
	}
 | 
						|
	connMux := NewConnMux(conn)
 | 
						|
	protocol := connMux.PeekProtocol()
 | 
						|
	if protocol == "tls" {
 | 
						|
		return tls.Server(connMux, l.config), nil
 | 
						|
	}
 | 
						|
	return connMux, nil
 | 
						|
}
 | 
						|
 | 
						|
// Close Listener
 | 
						|
func (l *ListenerMux) Close() error {
 | 
						|
	if l == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return l.Listener.Close()
 | 
						|
}
 | 
						|
 | 
						|
// ServerMux - the main mux server
 | 
						|
type ServerMux struct {
 | 
						|
	http.Server
 | 
						|
	listener        *ListenerMux
 | 
						|
	WaitGroup       *sync.WaitGroup
 | 
						|
	GracefulTimeout time.Duration
 | 
						|
	mu              sync.Mutex // guards closed, conns, and listener
 | 
						|
	closed          bool
 | 
						|
	conns           map[net.Conn]http.ConnState // except terminal states
 | 
						|
}
 | 
						|
 | 
						|
// NewServerMux constructor to create a ServerMux
 | 
						|
func NewServerMux(addr string, handler http.Handler) *ServerMux {
 | 
						|
	m := &ServerMux{
 | 
						|
		Server: http.Server{
 | 
						|
			Addr: addr,
 | 
						|
			// Do not add any timeouts Golang net.Conn
 | 
						|
			// closes connections right after 10mins even
 | 
						|
			// if they are not idle.
 | 
						|
			Handler:        handler,
 | 
						|
			MaxHeaderBytes: 1 << 20,
 | 
						|
		},
 | 
						|
		WaitGroup:       &sync.WaitGroup{},
 | 
						|
		GracefulTimeout: 5 * time.Second,
 | 
						|
	}
 | 
						|
 | 
						|
	// Track connection state
 | 
						|
	m.connState()
 | 
						|
 | 
						|
	// Returns configured HTTP server.
 | 
						|
	return m
 | 
						|
}
 | 
						|
 | 
						|
// ListenAndServeTLS - similar to the http.Server version. However, it has the
 | 
						|
// ability to redirect http requests to the correct HTTPS url if the client
 | 
						|
// mistakenly initiates a http connection over the https port
 | 
						|
func (m *ServerMux) ListenAndServeTLS(certFile, keyFile string) error {
 | 
						|
	listener, err := net.Listen("tcp", m.Server.Addr)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	config := &tls.Config{} // Always instantiate.
 | 
						|
	if config.NextProtos == nil {
 | 
						|
		config.NextProtos = []string{"http/1.1", "h2"}
 | 
						|
	}
 | 
						|
	config.Certificates = make([]tls.Certificate, 1)
 | 
						|
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	listenerMux := &ListenerMux{Listener: listener, config: config}
 | 
						|
 | 
						|
	m.mu.Lock()
 | 
						|
	m.listener = listenerMux
 | 
						|
	m.mu.Unlock()
 | 
						|
 | 
						|
	err = http.Serve(listenerMux,
 | 
						|
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
			// We reach here when ListenerMux.ConnMux is not wrapped with tls.Server
 | 
						|
			if r.TLS == nil {
 | 
						|
				u := url.URL{
 | 
						|
					Scheme:   "https",
 | 
						|
					Opaque:   r.URL.Opaque,
 | 
						|
					User:     r.URL.User,
 | 
						|
					Host:     r.Host,
 | 
						|
					Path:     r.URL.Path,
 | 
						|
					RawQuery: r.URL.RawQuery,
 | 
						|
					Fragment: r.URL.Fragment,
 | 
						|
				}
 | 
						|
				http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
 | 
						|
			} else {
 | 
						|
				// Execute registered handlers
 | 
						|
				m.Server.Handler.ServeHTTP(w, r)
 | 
						|
			}
 | 
						|
		}))
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// ListenAndServe - Same as the http.Server version
 | 
						|
func (m *ServerMux) ListenAndServe() error {
 | 
						|
	listener, err := net.Listen("tcp", m.Server.Addr)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	listenerMux := &ListenerMux{Listener: listener, config: &tls.Config{}}
 | 
						|
 | 
						|
	m.mu.Lock()
 | 
						|
	m.listener = listenerMux
 | 
						|
	m.mu.Unlock()
 | 
						|
 | 
						|
	return m.Server.Serve(listenerMux)
 | 
						|
}
 | 
						|
 | 
						|
// Close initiates the graceful shutdown
 | 
						|
func (m *ServerMux) Close() error {
 | 
						|
	m.mu.Lock()
 | 
						|
	if m.closed {
 | 
						|
		return errors.New("Server has been closed")
 | 
						|
	}
 | 
						|
	m.closed = true
 | 
						|
 | 
						|
	// Make sure a listener was set
 | 
						|
	if err := m.listener.Close(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	m.SetKeepAlivesEnabled(false)
 | 
						|
	for c, st := range m.conns {
 | 
						|
		// Force close any idle and new connections. Waiting for other connections
 | 
						|
		// to close on their own (within the timeout period)
 | 
						|
		if st == http.StateIdle || st == http.StateNew {
 | 
						|
			c.Close()
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If the GracefulTimeout happens then forcefully close all connections
 | 
						|
	t := time.AfterFunc(m.GracefulTimeout, func() {
 | 
						|
		for c := range m.conns {
 | 
						|
			c.Close()
 | 
						|
		}
 | 
						|
	})
 | 
						|
	defer t.Stop()
 | 
						|
 | 
						|
	m.mu.Unlock()
 | 
						|
 | 
						|
	// Block until all connections are closed
 | 
						|
	m.WaitGroup.Wait()
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// connState setups the ConnState tracking hook to know which connections are idle
 | 
						|
func (m *ServerMux) connState() {
 | 
						|
	// Set our ConnState to track idle connections
 | 
						|
	m.Server.ConnState = func(c net.Conn, cs http.ConnState) {
 | 
						|
		m.mu.Lock()
 | 
						|
		defer m.mu.Unlock()
 | 
						|
 | 
						|
		switch cs {
 | 
						|
		case http.StateNew:
 | 
						|
			// New connections increment the WaitGroup and are added the the conns dictionary
 | 
						|
			m.WaitGroup.Add(1)
 | 
						|
			if m.conns == nil {
 | 
						|
				m.conns = make(map[net.Conn]http.ConnState)
 | 
						|
			}
 | 
						|
			m.conns[c] = cs
 | 
						|
		case http.StateActive:
 | 
						|
			// Only update status to StateActive if it's in the conns dictionary
 | 
						|
			if _, ok := m.conns[c]; ok {
 | 
						|
				m.conns[c] = cs
 | 
						|
			}
 | 
						|
		case http.StateIdle:
 | 
						|
			// Only update status to StateIdle if it's in the conns dictionary
 | 
						|
			if _, ok := m.conns[c]; ok {
 | 
						|
				m.conns[c] = cs
 | 
						|
			}
 | 
						|
 | 
						|
			// If we've already closed then we need to close this connection.
 | 
						|
			// We don't allow connections to become idle after server is closed
 | 
						|
			if m.closed {
 | 
						|
				c.Close()
 | 
						|
			}
 | 
						|
		case http.StateHijacked, http.StateClosed:
 | 
						|
			// If the connection is hijacked or closed we forget it
 | 
						|
			m.forgetConn(c)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// forgetConn removes c from conns and decrements WaitGroup
 | 
						|
func (m *ServerMux) forgetConn(c net.Conn) {
 | 
						|
	if _, ok := m.conns[c]; ok {
 | 
						|
		delete(m.conns, c)
 | 
						|
		m.WaitGroup.Done()
 | 
						|
	}
 | 
						|
}
 |