mirror of
https://github.com/pgsty/minio.git
synced 2026-07-19 20:20:25 +03:00
rename all remaining packages to internal/ (#12418)
This is to ensure that there are no projects that try to import `minio/minio/pkg` into their own repo. Any such common packages should go to `https://github.com/minio/pkg`
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// DrainBody close non nil response with any response Body.
|
||||
// convenient wrapper to drain any remaining data on response body.
|
||||
//
|
||||
// Subsequently this allows golang http RoundTripper
|
||||
// to re-use the same connection for future requests.
|
||||
func DrainBody(respBody io.ReadCloser) {
|
||||
// Callers should close resp.Body when done reading from it.
|
||||
// If resp.Body is not closed, the Client's underlying RoundTripper
|
||||
// (typically Transport) may not be able to re-use a persistent TCP
|
||||
// connection to the server for a subsequent "keep-alive" request.
|
||||
if respBody != nil {
|
||||
// Drain any remaining Body and then close the connection.
|
||||
// Without this closing connection would disallow re-using
|
||||
// the same connection for future uses.
|
||||
// - http://stackoverflow.com/a/17961593/4465767
|
||||
defer respBody.Close()
|
||||
io.Copy(ioutil.Discard, respBody)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var randPerm = func(n int) []int {
|
||||
return rand.Perm(n)
|
||||
}
|
||||
|
||||
// DialContextWithDNSCache is a helper function which returns `net.DialContext` function.
|
||||
// It randomly fetches an IP from the DNS cache and dials it by the given dial
|
||||
// function. It dials one by one and returns first connected `net.Conn`.
|
||||
// If it fails to dial all IPs from cache it returns first error. If no baseDialFunc
|
||||
// is given, it sets default dial function.
|
||||
//
|
||||
// You can use returned dial function for `http.Transport.DialContext`.
|
||||
//
|
||||
// In this function, it uses functions from `rand` package. To make it really random,
|
||||
// you MUST call `rand.Seed` and change the value from the default in your application
|
||||
func DialContextWithDNSCache(cache *DNSCache, baseDialCtx DialContext) DialContext {
|
||||
if baseDialCtx == nil {
|
||||
// This is same as which `http.DefaultTransport` uses.
|
||||
baseDialCtx = (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext
|
||||
}
|
||||
return func(ctx context.Context, network, host string) (net.Conn, error) {
|
||||
h, p, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fetch DNS result from cache.
|
||||
//
|
||||
// ctxLookup is only used for canceling DNS Lookup.
|
||||
ctxLookup, cancelF := context.WithTimeout(ctx, cache.lookupTimeout)
|
||||
defer cancelF()
|
||||
addrs, err := cache.Fetch(ctxLookup, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var firstErr error
|
||||
for _, randomIndex := range randPerm(len(addrs)) {
|
||||
conn, err := baseDialCtx(ctx, "tcp", net.JoinHostPort(addrs[randomIndex], p))
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, firstErr
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// cacheSize is initial size of addr and IP list cache map.
|
||||
cacheSize = 64
|
||||
)
|
||||
|
||||
// defaultFreq is default frequency a resolver refreshes DNS cache.
|
||||
var (
|
||||
defaultFreq = 3 * time.Second
|
||||
defaultLookupTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
// DNSCache is DNS cache resolver which cache DNS resolve results in memory.
|
||||
type DNSCache struct {
|
||||
sync.RWMutex
|
||||
|
||||
lookupHostFn func(ctx context.Context, host string) ([]string, error)
|
||||
lookupTimeout time.Duration
|
||||
loggerOnce func(ctx context.Context, err error, id interface{}, errKind ...interface{})
|
||||
|
||||
cache map[string][]string
|
||||
doneOnce sync.Once
|
||||
doneCh chan struct{}
|
||||
}
|
||||
|
||||
// NewDNSCache initializes DNS cache resolver and starts auto refreshing
|
||||
// in a new goroutine. To stop auto refreshing, call `Stop()` function.
|
||||
// Once `Stop()` is called auto refreshing cannot be resumed.
|
||||
func NewDNSCache(freq time.Duration, lookupTimeout time.Duration, loggerOnce func(ctx context.Context, err error, id interface{}, errKind ...interface{})) *DNSCache {
|
||||
if freq <= 0 {
|
||||
freq = defaultFreq
|
||||
}
|
||||
|
||||
if lookupTimeout <= 0 {
|
||||
lookupTimeout = defaultLookupTimeout
|
||||
}
|
||||
|
||||
r := &DNSCache{
|
||||
lookupHostFn: net.DefaultResolver.LookupHost,
|
||||
lookupTimeout: lookupTimeout,
|
||||
loggerOnce: loggerOnce,
|
||||
cache: make(map[string][]string, cacheSize),
|
||||
doneCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
timer := time.NewTimer(freq)
|
||||
go func() {
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
// Make sure that refreshes on DNS do not be attempted
|
||||
// at the same time, allows for reduced load on the
|
||||
// DNS servers.
|
||||
timer.Reset(time.Duration(rnd.Float64() * float64(freq)))
|
||||
|
||||
r.Refresh()
|
||||
case <-r.doneCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// LookupHost lookups address list from DNS server, persist the results
|
||||
// in-memory cache. `Fetch` is used to obtain the values for a given host.
|
||||
func (r *DNSCache) LookupHost(ctx context.Context, host string) ([]string, error) {
|
||||
addrs, err := r.lookupHostFn(ctx, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.Lock()
|
||||
r.cache[host] = addrs
|
||||
r.Unlock()
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// Fetch fetches IP list from the cache. If IP list of the given addr is not in the cache,
|
||||
// then it lookups from DNS server by `Lookup` function.
|
||||
func (r *DNSCache) Fetch(ctx context.Context, host string) ([]string, error) {
|
||||
r.RLock()
|
||||
addrs, ok := r.cache[host]
|
||||
r.RUnlock()
|
||||
if ok {
|
||||
return addrs, nil
|
||||
}
|
||||
return r.LookupHost(ctx, host)
|
||||
}
|
||||
|
||||
// Refresh refreshes IP list cache, automatically.
|
||||
func (r *DNSCache) Refresh() {
|
||||
r.RLock()
|
||||
hosts := make([]string, 0, len(r.cache))
|
||||
for host := range r.cache {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
r.RUnlock()
|
||||
|
||||
for _, host := range hosts {
|
||||
ctx, cancelF := context.WithTimeout(context.Background(), r.lookupTimeout)
|
||||
if _, err := r.LookupHost(ctx, host); err != nil {
|
||||
r.loggerOnce(ctx, err, host)
|
||||
}
|
||||
cancelF()
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops auto refreshing.
|
||||
func (r *DNSCache) Stop() {
|
||||
r.doneOnce.Do(func() {
|
||||
close(r.doneCh)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
testFreq = 1 * time.Second
|
||||
testDefaultLookupTimeout = 1 * time.Second
|
||||
)
|
||||
|
||||
func logOnce(ctx context.Context, err error, id interface{}, errKind ...interface{}) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
func testDNSCache(t *testing.T) *DNSCache {
|
||||
t.Helper() // skip printing file and line information from this function
|
||||
return NewDNSCache(testFreq, testDefaultLookupTimeout, logOnce)
|
||||
}
|
||||
|
||||
func TestDialContextWithDNSCache(t *testing.T) {
|
||||
resolver := &DNSCache{
|
||||
cache: map[string][]string{
|
||||
"play.min.io": {
|
||||
"127.0.0.1",
|
||||
"127.0.0.2",
|
||||
"127.0.0.3",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
permF func(n int) []int
|
||||
dialF DialContext
|
||||
}{
|
||||
{
|
||||
permF: func(n int) []int {
|
||||
return []int{0}
|
||||
},
|
||||
dialF: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if got, want := addr, net.JoinHostPort("127.0.0.1", "443"); got != want {
|
||||
t.Fatalf("got addr %q, want %q", got, want)
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
permF: func(n int) []int {
|
||||
return []int{1}
|
||||
},
|
||||
dialF: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if got, want := addr, net.JoinHostPort("127.0.0.2", "443"); got != want {
|
||||
t.Fatalf("got addr %q, want %q", got, want)
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
permF: func(n int) []int {
|
||||
return []int{2}
|
||||
},
|
||||
dialF: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if got, want := addr, net.JoinHostPort("127.0.0.3", "443"); got != want {
|
||||
t.Fatalf("got addr %q, want %q", got, want)
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
origFunc := randPerm
|
||||
defer func() {
|
||||
randPerm = origFunc
|
||||
}()
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
randPerm = tc.permF
|
||||
if _, err := DialContextWithDNSCache(resolver, tc.dialF)(context.Background(), "tcp", "play.min.io:443"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDialContextWithDNSCacheRand(t *testing.T) {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
defer func() {
|
||||
rand.Seed(1)
|
||||
}()
|
||||
|
||||
resolver := &DNSCache{
|
||||
cache: map[string][]string{
|
||||
"play.min.io": {
|
||||
"127.0.0.1",
|
||||
"127.0.0.2",
|
||||
"127.0.0.3",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
count := make(map[string]int)
|
||||
dialF := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
count[addr]++
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
if _, err := DialContextWithDNSCache(resolver, dialF)(context.Background(), "tcp", "play.min.io:443"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range count {
|
||||
got := float32(c) / float32(100)
|
||||
if got < float32(0.1) {
|
||||
t.Fatalf("expected 0.1 rate got %f", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify without port Dial fails, Go stdlib net.Dial expects port
|
||||
func TestDialContextWithDNSCacheScenario1(t *testing.T) {
|
||||
resolver := testDNSCache(t)
|
||||
if _, err := DialContextWithDNSCache(resolver, nil)(context.Background(), "tcp", "play.min.io"); err == nil {
|
||||
t.Fatalf("expect to fail") // expected port
|
||||
}
|
||||
}
|
||||
|
||||
// Verify if the host lookup function failed to return addresses
|
||||
func TestDialContextWithDNSCacheScenario2(t *testing.T) {
|
||||
res := testDNSCache(t)
|
||||
originalFunc := res.lookupHostFn
|
||||
defer func() {
|
||||
res.lookupHostFn = originalFunc
|
||||
}()
|
||||
|
||||
res.lookupHostFn = func(ctx context.Context, host string) ([]string, error) {
|
||||
return nil, fmt.Errorf("err")
|
||||
}
|
||||
|
||||
if _, err := DialContextWithDNSCache(res, nil)(context.Background(), "tcp", "min.io:443"); err == nil {
|
||||
t.Fatalf("exect to fail")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify we always return the first error from net.Dial failure
|
||||
func TestDialContextWithDNSCacheScenario3(t *testing.T) {
|
||||
resolver := &DNSCache{
|
||||
cache: map[string][]string{
|
||||
"min.io": {
|
||||
"1.1.1.1",
|
||||
"2.2.2.2",
|
||||
"3.3.3.3",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
origFunc := randPerm
|
||||
randPerm = func(n int) []int {
|
||||
return []int{0, 1, 2}
|
||||
}
|
||||
defer func() {
|
||||
randPerm = origFunc
|
||||
}()
|
||||
|
||||
want := errors.New("error1")
|
||||
dialF := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if addr == net.JoinHostPort("1.1.1.1", "443") {
|
||||
return nil, want // first error should be returned
|
||||
}
|
||||
if addr == net.JoinHostPort("2.2.2.2", "443") {
|
||||
return nil, fmt.Errorf("error2")
|
||||
}
|
||||
if addr == net.JoinHostPort("3.3.3.3", "443") {
|
||||
return nil, fmt.Errorf("error3")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, got := DialContextWithDNSCache(resolver, dialF)(context.Background(), "tcp", "min.io:443")
|
||||
if got != want {
|
||||
t.Fatalf("got error %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// +build linux
|
||||
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func setInternalTCPParameters(c syscall.RawConn) error {
|
||||
return c.Control(func(fdPtr uintptr) {
|
||||
// got socket file descriptor to set parameters.
|
||||
fd := int(fdPtr)
|
||||
|
||||
// Enable TCP fast connect
|
||||
// TCPFastOpenConnect sets the underlying socket to use
|
||||
// the TCP fast open connect. This feature is supported
|
||||
// since Linux 4.11.
|
||||
_ = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, unix.TCP_FASTOPEN_CONNECT, 1)
|
||||
|
||||
// Enable TCP quick ACK, John Nagle says
|
||||
// "Set TCP_QUICKACK. If you find a case where that makes things worse, let me know."
|
||||
_ = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, unix.TCP_QUICKACK, 1)
|
||||
|
||||
// The time (in seconds) the connection needs to remain idle before
|
||||
// TCP starts sending keepalive probes, set this to 5 secs
|
||||
// system defaults to 7200 secs!!!
|
||||
_ = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, 5)
|
||||
|
||||
// Number of probes.
|
||||
// ~ cat /proc/sys/net/ipv4/tcp_keepalive_probes (defaults to 9, we reduce it to 5)
|
||||
// 9
|
||||
_ = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, 5)
|
||||
|
||||
// Wait time after successful probe in seconds.
|
||||
// ~ cat /proc/sys/net/ipv4/tcp_keepalive_intvl (defaults to 75 secs, we reduce it to 3 secs)
|
||||
// 75
|
||||
_ = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, 3)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// DialContext is a function to make custom Dial for internode communications
|
||||
type DialContext func(ctx context.Context, network, address string) (net.Conn, error)
|
||||
|
||||
// NewInternodeDialContext setups a custom dialer for internode communication
|
||||
func NewInternodeDialContext(dialTimeout time.Duration) DialContext {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: dialTimeout,
|
||||
Control: func(network, address string, c syscall.RawConn) error {
|
||||
return setInternalTCPParameters(c)
|
||||
},
|
||||
}
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// NewCustomDialContext setups a custom dialer for any external communication and proxies.
|
||||
func NewCustomDialContext(dialTimeout time.Duration) DialContext {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: dialTimeout,
|
||||
Control: func(network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fdPtr uintptr) {
|
||||
// got socket file descriptor to set parameters.
|
||||
fd := int(fdPtr)
|
||||
|
||||
// Enable TCP fast connect
|
||||
// TCPFastOpenConnect sets the underlying socket to use
|
||||
// the TCP fast open connect. This feature is supported
|
||||
// since Linux 4.11.
|
||||
_ = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, unix.TCP_FASTOPEN_CONNECT, 1)
|
||||
|
||||
// Enable TCP quick ACK, John Nagle says
|
||||
// "Set TCP_QUICKACK. If you find a case where that makes things worse, let me know."
|
||||
_ = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, unix.TCP_QUICKACK, 1)
|
||||
})
|
||||
},
|
||||
}
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// +build !linux
|
||||
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TODO: if possible implement for non-linux platforms, not a priority at the moment
|
||||
//nolint:deadcode
|
||||
func setInternalTCPParameters(c syscall.RawConn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DialContext is a function to make custom Dial for internode communications
|
||||
type DialContext func(ctx context.Context, network, address string) (net.Conn, error)
|
||||
|
||||
// NewInternodeDialContext setups a custom dialer for internode communication
|
||||
var NewInternodeDialContext = NewCustomDialContext
|
||||
|
||||
// NewCustomDialContext configures a custom dialer for internode communications
|
||||
func NewCustomDialContext(dialTimeout time.Duration) DialContext {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: dialTimeout,
|
||||
}
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
// Standard S3 HTTP response constants
|
||||
const (
|
||||
LastModified = "Last-Modified"
|
||||
Date = "Date"
|
||||
ETag = "ETag"
|
||||
ContentType = "Content-Type"
|
||||
ContentMD5 = "Content-Md5"
|
||||
ContentEncoding = "Content-Encoding"
|
||||
Expires = "Expires"
|
||||
ContentLength = "Content-Length"
|
||||
ContentLanguage = "Content-Language"
|
||||
ContentRange = "Content-Range"
|
||||
Connection = "Connection"
|
||||
AcceptRanges = "Accept-Ranges"
|
||||
AmzBucketRegion = "X-Amz-Bucket-Region"
|
||||
ServerInfo = "Server"
|
||||
RetryAfter = "Retry-After"
|
||||
Location = "Location"
|
||||
CacheControl = "Cache-Control"
|
||||
ContentDisposition = "Content-Disposition"
|
||||
Authorization = "Authorization"
|
||||
Action = "Action"
|
||||
Range = "Range"
|
||||
)
|
||||
|
||||
// Non standard S3 HTTP response constants
|
||||
const (
|
||||
XCache = "X-Cache"
|
||||
XCacheLookup = "X-Cache-Lookup"
|
||||
)
|
||||
|
||||
// Standard S3 HTTP request constants
|
||||
const (
|
||||
IfModifiedSince = "If-Modified-Since"
|
||||
IfUnmodifiedSince = "If-Unmodified-Since"
|
||||
IfMatch = "If-Match"
|
||||
IfNoneMatch = "If-None-Match"
|
||||
|
||||
// S3 storage class
|
||||
AmzStorageClass = "x-amz-storage-class"
|
||||
|
||||
// S3 object version ID
|
||||
AmzVersionID = "x-amz-version-id"
|
||||
AmzDeleteMarker = "x-amz-delete-marker"
|
||||
|
||||
// S3 object tagging
|
||||
AmzObjectTagging = "X-Amz-Tagging"
|
||||
AmzTagCount = "x-amz-tagging-count"
|
||||
AmzTagDirective = "X-Amz-Tagging-Directive"
|
||||
|
||||
// S3 transition restore
|
||||
AmzRestore = "x-amz-restore"
|
||||
AmzRestoreExpiryDays = "X-Amz-Restore-Expiry-Days"
|
||||
AmzRestoreRequestDate = "X-Amz-Restore-Request-Date"
|
||||
AmzRestoreOutputPath = "x-amz-restore-output-path"
|
||||
|
||||
// S3 extensions
|
||||
AmzCopySourceIfModifiedSince = "x-amz-copy-source-if-modified-since"
|
||||
AmzCopySourceIfUnmodifiedSince = "x-amz-copy-source-if-unmodified-since"
|
||||
|
||||
AmzCopySourceIfNoneMatch = "x-amz-copy-source-if-none-match"
|
||||
AmzCopySourceIfMatch = "x-amz-copy-source-if-match"
|
||||
|
||||
AmzCopySource = "X-Amz-Copy-Source"
|
||||
AmzCopySourceVersionID = "X-Amz-Copy-Source-Version-Id"
|
||||
AmzCopySourceRange = "X-Amz-Copy-Source-Range"
|
||||
AmzMetadataDirective = "X-Amz-Metadata-Directive"
|
||||
AmzObjectLockMode = "X-Amz-Object-Lock-Mode"
|
||||
AmzObjectLockRetainUntilDate = "X-Amz-Object-Lock-Retain-Until-Date"
|
||||
AmzObjectLockLegalHold = "X-Amz-Object-Lock-Legal-Hold"
|
||||
AmzObjectLockBypassGovernance = "X-Amz-Bypass-Governance-Retention"
|
||||
AmzBucketReplicationStatus = "X-Amz-Replication-Status"
|
||||
AmzSnowballExtract = "X-Amz-Meta-Snowball-Auto-Extract"
|
||||
|
||||
// Multipart parts count
|
||||
AmzMpPartsCount = "x-amz-mp-parts-count"
|
||||
|
||||
// Object date/time of expiration
|
||||
AmzExpiration = "x-amz-expiration"
|
||||
|
||||
// Dummy putBucketACL
|
||||
AmzACL = "x-amz-acl"
|
||||
|
||||
// Signature V4 related contants.
|
||||
AmzContentSha256 = "X-Amz-Content-Sha256"
|
||||
AmzDate = "X-Amz-Date"
|
||||
AmzAlgorithm = "X-Amz-Algorithm"
|
||||
AmzExpires = "X-Amz-Expires"
|
||||
AmzSignedHeaders = "X-Amz-SignedHeaders"
|
||||
AmzSignature = "X-Amz-Signature"
|
||||
AmzCredential = "X-Amz-Credential"
|
||||
AmzSecurityToken = "X-Amz-Security-Token"
|
||||
AmzDecodedContentLength = "X-Amz-Decoded-Content-Length"
|
||||
|
||||
AmzMetaUnencryptedContentLength = "X-Amz-Meta-X-Amz-Unencrypted-Content-Length"
|
||||
AmzMetaUnencryptedContentMD5 = "X-Amz-Meta-X-Amz-Unencrypted-Content-Md5"
|
||||
|
||||
// AWS server-side encryption headers for SSE-S3, SSE-KMS and SSE-C.
|
||||
AmzServerSideEncryption = "X-Amz-Server-Side-Encryption"
|
||||
AmzServerSideEncryptionKmsID = AmzServerSideEncryption + "-Aws-Kms-Key-Id"
|
||||
AmzServerSideEncryptionKmsContext = AmzServerSideEncryption + "-Context"
|
||||
AmzServerSideEncryptionCustomerAlgorithm = AmzServerSideEncryption + "-Customer-Algorithm"
|
||||
AmzServerSideEncryptionCustomerKey = AmzServerSideEncryption + "-Customer-Key"
|
||||
AmzServerSideEncryptionCustomerKeyMD5 = AmzServerSideEncryption + "-Customer-Key-Md5"
|
||||
AmzServerSideEncryptionCopyCustomerAlgorithm = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm"
|
||||
AmzServerSideEncryptionCopyCustomerKey = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key"
|
||||
AmzServerSideEncryptionCopyCustomerKeyMD5 = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5"
|
||||
|
||||
AmzEncryptionAES = "AES256"
|
||||
AmzEncryptionKMS = "aws:kms"
|
||||
|
||||
// Signature v2 related constants
|
||||
AmzSignatureV2 = "Signature"
|
||||
AmzAccessKeyID = "AWSAccessKeyId"
|
||||
|
||||
// Response request id.
|
||||
AmzRequestID = "x-amz-request-id"
|
||||
|
||||
// Deployment id.
|
||||
MinioDeploymentID = "x-minio-deployment-id"
|
||||
|
||||
// Server-Status
|
||||
MinIOServerStatus = "x-minio-server-status"
|
||||
|
||||
// Delete special flag to force delete a bucket
|
||||
MinIOForceDelete = "x-minio-force-delete"
|
||||
|
||||
// Header indicates if the mtime should be preserved by client
|
||||
MinIOSourceMTime = "x-minio-source-mtime"
|
||||
|
||||
// Header indicates if the etag should be preserved by client
|
||||
MinIOSourceETag = "x-minio-source-etag"
|
||||
|
||||
// Writes expected write quorum
|
||||
MinIOWriteQuorum = "x-minio-write-quorum"
|
||||
|
||||
// Reports number of drives currently healing
|
||||
MinIOHealingDrives = "x-minio-healing-drives"
|
||||
|
||||
// Header indicates if the delete marker should be preserved by client
|
||||
MinIOSourceDeleteMarker = "x-minio-source-deletemarker"
|
||||
|
||||
// Header indicates if the delete marker version needs to be purged.
|
||||
MinIOSourceDeleteMarkerDelete = "x-minio-source-deletemarker-delete"
|
||||
|
||||
// Header indicates permanent delete replication status.
|
||||
MinIODeleteReplicationStatus = "X-Minio-Replication-Delete-Status"
|
||||
// Header indicates delete-marker replication status.
|
||||
MinIODeleteMarkerReplicationStatus = "X-Minio-Replication-DeleteMarker-Status"
|
||||
// Header indicates if its a GET/HEAD proxy request for active-active replication
|
||||
MinIOSourceProxyRequest = "X-Minio-Source-Proxy-Request"
|
||||
// Header indicates that this request is a replication request to create a REPLICA
|
||||
MinIOSourceReplicationRequest = "X-Minio-Source-Replication-Request"
|
||||
// predicted date/time of transition
|
||||
MinIOTransition = "X-Minio-Transition"
|
||||
)
|
||||
|
||||
// Common http query params S3 API
|
||||
const (
|
||||
VersionID = "versionId"
|
||||
|
||||
PartNumber = "partNumber"
|
||||
|
||||
UploadID = "uploadId"
|
||||
)
|
||||
@@ -0,0 +1,38 @@
|
||||
// +build linux darwin dragonfly freebsd netbsd openbsd rumprun
|
||||
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/valyala/tcplisten"
|
||||
)
|
||||
|
||||
var cfg = &tcplisten.Config{
|
||||
DeferAccept: true,
|
||||
FastOpen: true,
|
||||
// Bump up the soMaxConn value from 128 to 4096 to
|
||||
// handle large incoming concurrent requests.
|
||||
Backlog: 4096,
|
||||
}
|
||||
|
||||
// Unix listener with special TCP options.
|
||||
var listen = cfg.NewListener
|
||||
var fallbackListen = net.Listen
|
||||
@@ -0,0 +1,26 @@
|
||||
// +build windows plan9 solaris
|
||||
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
import "net"
|
||||
|
||||
// Windows, plan9 specific listener.
|
||||
var listen = net.Listen
|
||||
var fallbackListen = net.Listen
|
||||
@@ -0,0 +1,204 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type acceptResult struct {
|
||||
conn net.Conn
|
||||
err error
|
||||
}
|
||||
|
||||
// httpListener - HTTP listener capable of handling multiple server addresses.
|
||||
type httpListener struct {
|
||||
mutex sync.Mutex // to guard Close() method.
|
||||
tcpListeners []*net.TCPListener // underlaying TCP listeners.
|
||||
acceptCh chan acceptResult // channel where all TCP listeners write accepted connection.
|
||||
doneCh chan struct{} // done channel for TCP listener goroutines.
|
||||
}
|
||||
|
||||
// isRoutineNetErr returns true if error is due to a network timeout,
|
||||
// connect reset or io.EOF and false otherwise
|
||||
func isRoutineNetErr(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if nErr, ok := err.(*net.OpError); ok {
|
||||
// Check if the error is a tcp connection reset
|
||||
if syscallErr, ok := nErr.Err.(*os.SyscallError); ok {
|
||||
if errno, ok := syscallErr.Err.(syscall.Errno); ok {
|
||||
return errno == syscall.ECONNRESET
|
||||
}
|
||||
}
|
||||
// Check if the error is a timeout
|
||||
return nErr.Timeout()
|
||||
}
|
||||
// check for io.EOF and also some times io.EOF is wrapped is another error type.
|
||||
return err == io.EOF || err.Error() == "EOF"
|
||||
}
|
||||
|
||||
// start - starts separate goroutine for each TCP listener. A valid new connection is passed to httpListener.acceptCh.
|
||||
func (listener *httpListener) start() {
|
||||
listener.acceptCh = make(chan acceptResult)
|
||||
listener.doneCh = make(chan struct{})
|
||||
|
||||
// Closure to send acceptResult to acceptCh.
|
||||
// It returns true if the result is sent else false if returns when doneCh is closed.
|
||||
send := func(result acceptResult, doneCh <-chan struct{}) bool {
|
||||
select {
|
||||
case listener.acceptCh <- result:
|
||||
// Successfully written to acceptCh
|
||||
return true
|
||||
case <-doneCh:
|
||||
// As stop signal is received, close accepted connection.
|
||||
if result.conn != nil {
|
||||
result.conn.Close()
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Closure to handle single connection.
|
||||
handleConn := func(tcpConn *net.TCPConn, doneCh <-chan struct{}) {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
send(acceptResult{tcpConn, nil}, doneCh)
|
||||
}
|
||||
|
||||
// Closure to handle TCPListener until done channel is closed.
|
||||
handleListener := func(tcpListener *net.TCPListener, doneCh <-chan struct{}) {
|
||||
for {
|
||||
tcpConn, err := tcpListener.AcceptTCP()
|
||||
if err != nil {
|
||||
// Returns when send fails.
|
||||
if !send(acceptResult{nil, err}, doneCh) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
go handleConn(tcpConn, doneCh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start separate goroutine for each TCP listener to handle connection.
|
||||
for _, tcpListener := range listener.tcpListeners {
|
||||
go handleListener(tcpListener, listener.doneCh)
|
||||
}
|
||||
}
|
||||
|
||||
// Accept - reads from httpListener.acceptCh for one of previously accepted TCP connection and returns the same.
|
||||
func (listener *httpListener) Accept() (conn net.Conn, err error) {
|
||||
result, ok := <-listener.acceptCh
|
||||
if ok {
|
||||
return result.conn, result.err
|
||||
}
|
||||
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
|
||||
// Close - closes underneath all TCP listeners.
|
||||
func (listener *httpListener) Close() (err error) {
|
||||
listener.mutex.Lock()
|
||||
defer listener.mutex.Unlock()
|
||||
if listener.doneCh == nil {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
|
||||
for i := range listener.tcpListeners {
|
||||
listener.tcpListeners[i].Close()
|
||||
}
|
||||
close(listener.doneCh)
|
||||
|
||||
listener.doneCh = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Addr - net.Listener interface compatible method returns net.Addr. In case of multiple TCP listeners, it returns '0.0.0.0' as IP address.
|
||||
func (listener *httpListener) Addr() (addr net.Addr) {
|
||||
addr = listener.tcpListeners[0].Addr()
|
||||
if len(listener.tcpListeners) == 1 {
|
||||
return addr
|
||||
}
|
||||
|
||||
tcpAddr := addr.(*net.TCPAddr)
|
||||
if ip := net.ParseIP("0.0.0.0"); ip != nil {
|
||||
tcpAddr.IP = ip
|
||||
}
|
||||
|
||||
addr = tcpAddr
|
||||
return addr
|
||||
}
|
||||
|
||||
// Addrs - returns all address information of TCP listeners.
|
||||
func (listener *httpListener) Addrs() (addrs []net.Addr) {
|
||||
for i := range listener.tcpListeners {
|
||||
addrs = append(addrs, listener.tcpListeners[i].Addr())
|
||||
}
|
||||
|
||||
return addrs
|
||||
}
|
||||
|
||||
// newHTTPListener - creates new httpListener object which is interface compatible to net.Listener.
|
||||
// httpListener is capable to
|
||||
// * listen to multiple addresses
|
||||
// * controls incoming connections only doing HTTP protocol
|
||||
func newHTTPListener(serverAddrs []string) (listener *httpListener, err error) {
|
||||
|
||||
var tcpListeners []*net.TCPListener
|
||||
|
||||
// Close all opened listeners on error
|
||||
defer func() {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, tcpListener := range tcpListeners {
|
||||
// Ignore error on close.
|
||||
tcpListener.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for _, serverAddr := range serverAddrs {
|
||||
var l net.Listener
|
||||
if l, err = listen("tcp", serverAddr); err != nil {
|
||||
if l, err = fallbackListen("tcp", serverAddr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
tcpListener, ok := l.(*net.TCPListener)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected listener type found %v, expected net.TCPListener", l)
|
||||
}
|
||||
|
||||
tcpListeners = append(tcpListeners, tcpListener)
|
||||
}
|
||||
|
||||
listener = &httpListener{
|
||||
tcpListeners: tcpListeners,
|
||||
}
|
||||
listener.start()
|
||||
|
||||
return listener, nil
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
)
|
||||
|
||||
var serverPort uint32 = 60000
|
||||
|
||||
var getCert = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
certificate, err := getTLSCert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &certificate, nil
|
||||
}
|
||||
|
||||
func getTLSCert() (tls.Certificate, error) {
|
||||
keyPEMBlock := []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEApEkbPrT6wzcWK1W5atQiGptvuBsRdf8MCg4u6SN10QbslA5k
|
||||
6BYRdZfFeRpwAwYyzkumug6+eBJatDZEd7+0FF86yxB7eMTSiHKRZ5Mi5ZyCFsez
|
||||
dndknGBeK6I80s1jd5ZsLLuMKErvbNwSbfX+X6d2mBeYW8Scv9N+qYnNrHHHohvX
|
||||
oxy1gZ18EhhogQhrD22zaqg/jtmOT8ImUiXzB1mKInt2LlSkoRYuBzepkDJrsE1L
|
||||
/cyYZbtcO/ASDj+/qQAuQ66v9pNyJkIQ7bDOUyxaT5Hx9XvbqI1OqUVAdGLLi+eZ
|
||||
IFguFyYd0lemwdN/IDvxftzegTO3cO0D28d1UQIDAQABAoIBAB42x8j3lerTNcOQ
|
||||
h4JLM157WcedSs/NsVQkGaKM//0KbfYo04wPivR6jjngj9suh6eDKE2tqoAAuCfO
|
||||
lzcCzca1YOW5yUuDv0iS8YT//XoHF7HC1pGiEaHk40zZEKCgX3u98XUkpPlAFtqJ
|
||||
euY4SKkk7l24cS/ncACjj/b0PhxJoT/CncuaaJKqmCc+vdL4wj1UcrSNPZqRjDR/
|
||||
sh5DO0LblB0XrqVjpNxqxM60/IkbftB8YTnyGgtO2tbTPr8KdQ8DhHQniOp+WEPV
|
||||
u/iXt0LLM7u62LzadkGab2NDWS3agnmdvw2ADtv5Tt8fZ7WnPqiOpNyD5Bv1a3/h
|
||||
YBw5HsUCgYEA0Sfv6BiSAFEby2KusRoq5UeUjp/SfL7vwpO1KvXeiYkPBh2XYVq2
|
||||
azMnOw7Rz5ixFhtUtto2XhYdyvvr3dZu1fNHtxWo9ITBivqTGGRNwfiaQa58Bugo
|
||||
gy7vCdIE/f6xE5LYIovBnES2vs/ZayMyhTX84SCWd0pTY0kdDA8ePGsCgYEAyRSA
|
||||
OTzX43KUR1G/trpuM6VBc0W6YUNYzGRa1TcUxBP4K7DfKMpPGg6ulqypfoHmu8QD
|
||||
L+z+iQmG9ySSuvScIW6u8LgkrTwZga8y2eb/A2FAVYY/bnelef1aMkis+bBX2OQ4
|
||||
QAg2uq+pkhpW1k5NSS9lVCPkj4e5Ur9RCm9fRDMCgYAf3CSIR03eLHy+Y37WzXSh
|
||||
TmELxL6sb+1Xx2Y+cAuBCda3CMTpeIb3F2ivb1d4dvrqsikaXW0Qse/B3tQUC7kA
|
||||
cDmJYwxEiwBsajUD7yuFE5hzzt9nse+R5BFXfp1yD1zr7V9tC7rnUfRAZqrozgjB
|
||||
D/NAW9VvwGupYRbCon7plwKBgQCRPfeoYGRoa9ji8w+Rg3QaZeGyy8jmfGjlqg9a
|
||||
NyEOyIXXuThYFFmyrqw5NZpwQJBTTDApK/xnK7SLS6WY2Rr1oydFxRzo7KJX5B7M
|
||||
+md1H4gCvqeOuWmThgbij1AyQsgRaDehOM2fZ0cKu2/B+Gkm1c9RSWPMsPKR7JMz
|
||||
AGNFtQKBgQCRCFIdGJHnvz35vJfLoihifCejBWtZbAnZoBHpF3xMCtV755J96tUf
|
||||
k1Tv9hz6WfSkOSlwLq6eGZY2dCENJRW1ft1UelpFvCjbfrfLvoFFLs3gu0lfqXHi
|
||||
CS6fjhn9Ahvz10yD6fd4ixRUjoJvULzI0Sxc1O95SYVF1lIAuVr9Hw==
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
certPEMBlock := []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAKlqK5HKlo9MMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTcwNjE5MTA0MzEyWhcNMjcwNjE3MTA0MzEyWjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEApEkbPrT6wzcWK1W5atQiGptvuBsRdf8MCg4u6SN10QbslA5k6BYRdZfF
|
||||
eRpwAwYyzkumug6+eBJatDZEd7+0FF86yxB7eMTSiHKRZ5Mi5ZyCFsezdndknGBe
|
||||
K6I80s1jd5ZsLLuMKErvbNwSbfX+X6d2mBeYW8Scv9N+qYnNrHHHohvXoxy1gZ18
|
||||
EhhogQhrD22zaqg/jtmOT8ImUiXzB1mKInt2LlSkoRYuBzepkDJrsE1L/cyYZbtc
|
||||
O/ASDj+/qQAuQ66v9pNyJkIQ7bDOUyxaT5Hx9XvbqI1OqUVAdGLLi+eZIFguFyYd
|
||||
0lemwdN/IDvxftzegTO3cO0D28d1UQIDAQABo1AwTjAdBgNVHQ4EFgQUqMVdMIA1
|
||||
68Dv+iwGugAaEGUSd0IwHwYDVR0jBBgwFoAUqMVdMIA168Dv+iwGugAaEGUSd0Iw
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAjQVoqRv2HlE5PJIX/qk5
|
||||
oMOKZlHTyJP+s2HzOOVt+eCE/jNdfC7+8R/HcPldQs7p9GqH2F6hQ9aOtDhJVEaU
|
||||
pjxCi4qKeZ1kWwqv8UMBXW92eHGysBvE2Gmm/B1JFl8S2GR5fBmheZVnYW893MoI
|
||||
gp+bOoCcIuMJRqCra4vJgrOsQjgRElQvd2OlP8qQzInf/fRqO/AnZPwMkGr3+KZ0
|
||||
BKEOXtmSZaPs3xEsnvJd8wrTgA0NQK7v48E+gHSXzQtaHmOLqisRXlUOu2r1gNCJ
|
||||
rr3DRiUP6V/10CZ/ImeSJ72k69VuTw9vq2HzB4x6pqxF2X7JQSLUCS2wfNN13N0d
|
||||
9A==
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
return tls.X509KeyPair(certPEMBlock, keyPEMBlock)
|
||||
}
|
||||
|
||||
func getNextPort() string {
|
||||
return strconv.Itoa(int(atomic.AddUint32(&serverPort, 1)))
|
||||
}
|
||||
|
||||
func getNonLoopBackIP(t *testing.T) string {
|
||||
localIP4 := set.NewStringSet()
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
t.Fatalf("%s. Unable to get IP addresses of this host.", err)
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
|
||||
if ip.To4() != nil {
|
||||
localIP4.Add(ip.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Filter ipList by IPs those do not start with '127.'.
|
||||
nonLoopBackIPs := localIP4.FuncMatch(func(ip string, matchString string) bool {
|
||||
return !strings.HasPrefix(ip, "127.")
|
||||
}, "")
|
||||
if len(nonLoopBackIPs) == 0 {
|
||||
t.Fatalf("No non-loop back IP address found for this host")
|
||||
}
|
||||
nonLoopBackIP := nonLoopBackIPs.ToSlice()[0]
|
||||
return nonLoopBackIP
|
||||
}
|
||||
|
||||
func TestNewHTTPListener(t *testing.T) {
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tcpKeepAliveTimeout time.Duration
|
||||
readTimeout time.Duration
|
||||
writeTimeout time.Duration
|
||||
expectedErr bool
|
||||
}{
|
||||
{[]string{"93.184.216.34:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true},
|
||||
{[]string{"example.org:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true},
|
||||
{[]string{"unknown-host"}, time.Duration(0), time.Duration(0), time.Duration(0), true},
|
||||
{[]string{"unknown-host:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true},
|
||||
{[]string{"localhost:65432", "93.184.216.34:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true},
|
||||
{[]string{"localhost:65432", "unknown-host:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true},
|
||||
{[]string{"localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), false},
|
||||
{[]string{"localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), false},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
)
|
||||
|
||||
if !testCase.expectedErr {
|
||||
if err != nil {
|
||||
t.Fatalf("error: expected = <nil>, got = %v", err)
|
||||
}
|
||||
} else if err == nil {
|
||||
t.Fatalf("error: expected = %v, got = <nil>", testCase.expectedErr)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerStartClose(t *testing.T) {
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
}{
|
||||
{[]string{"localhost:0"}},
|
||||
{[]string{nonLoopBackIP + ":0"}},
|
||||
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}},
|
||||
{[]string{"localhost:0"}},
|
||||
{[]string{nonLoopBackIP + ":0"}},
|
||||
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "The requested address is not valid in its context") {
|
||||
// Ignore if IP is unbindable.
|
||||
continue
|
||||
}
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
for _, serverAddr := range listener.Addrs() {
|
||||
conn, err := net.Dial("tcp", serverAddr.String())
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAddr(t *testing.T) {
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
var casePorts []string
|
||||
for i := 0; i < 6; i++ {
|
||||
casePorts = append(casePorts, getNextPort())
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
expectedAddr string
|
||||
}{
|
||||
{[]string{"localhost:" + casePorts[0]}, "127.0.0.1:" + casePorts[0]},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[1]}, nonLoopBackIP + ":" + casePorts[1]},
|
||||
{[]string{"127.0.0.1:" + casePorts[2], nonLoopBackIP + ":" + casePorts[2]}, "0.0.0.0:" + casePorts[2]},
|
||||
{[]string{"localhost:" + casePorts[3]}, "127.0.0.1:" + casePorts[3]},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[4]}, nonLoopBackIP + ":" + casePorts[4]},
|
||||
{[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, "0.0.0.0:" + casePorts[5]},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "The requested address is not valid in its context") {
|
||||
// Ignore if IP is unbindable.
|
||||
continue
|
||||
}
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
addr := listener.Addr()
|
||||
if addr.String() != testCase.expectedAddr {
|
||||
t.Fatalf("Test %d: addr: expected = %v, got = %v", i+1, testCase.expectedAddr, addr)
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAddrs(t *testing.T) {
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
var casePorts []string
|
||||
for i := 0; i < 6; i++ {
|
||||
casePorts = append(casePorts, getNextPort())
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
expectedAddrs set.StringSet
|
||||
}{
|
||||
{[]string{"localhost:" + casePorts[0]}, set.CreateStringSet("127.0.0.1:" + casePorts[0])},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[1]}, set.CreateStringSet(nonLoopBackIP + ":" + casePorts[1])},
|
||||
{[]string{"127.0.0.1:" + casePorts[2], nonLoopBackIP + ":" + casePorts[2]}, set.CreateStringSet("127.0.0.1:"+casePorts[2], nonLoopBackIP+":"+casePorts[2])},
|
||||
{[]string{"localhost:" + casePorts[3]}, set.CreateStringSet("127.0.0.1:" + casePorts[3])},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[4]}, set.CreateStringSet(nonLoopBackIP + ":" + casePorts[4])},
|
||||
{[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, set.CreateStringSet("127.0.0.1:"+casePorts[5], nonLoopBackIP+":"+casePorts[5])},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "The requested address is not valid in its context") {
|
||||
// Ignore if IP is unbindable.
|
||||
continue
|
||||
}
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
addrs := listener.Addrs()
|
||||
addrSet := set.NewStringSet()
|
||||
for _, addr := range addrs {
|
||||
addrSet.Add(addr.String())
|
||||
}
|
||||
|
||||
if !addrSet.Equals(testCase.expectedAddrs) {
|
||||
t.Fatalf("Test %d: addr: expected = %v, got = %v", i+1, testCase.expectedAddrs, addrs)
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
type myTimeoutErr struct {
|
||||
timeout bool
|
||||
}
|
||||
|
||||
func (m *myTimeoutErr) Error() string { return fmt.Sprintf("myTimeoutErr: %v", m.timeout) }
|
||||
func (m *myTimeoutErr) Timeout() bool { return m.timeout }
|
||||
|
||||
// Test for ignoreErr helper function
|
||||
func TestIgnoreErr(t *testing.T) {
|
||||
testCases := []struct {
|
||||
err error
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
err: io.EOF,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
err: &net.OpError{Err: &myTimeoutErr{timeout: true}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
err: errors.New("EOF"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
err: &net.OpError{Err: &myTimeoutErr{timeout: false}},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
err: io.ErrUnexpectedEOF,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
err: nil,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
if actual := isRoutineNetErr(tc.err); actual != tc.want {
|
||||
t.Errorf("Test case %d: Expected %v but got %v for %v", i+1,
|
||||
tc.want, actual, tc.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/config/api"
|
||||
"github.com/minio/minio/internal/fips"
|
||||
"github.com/minio/pkg/certs"
|
||||
"github.com/minio/pkg/env"
|
||||
)
|
||||
|
||||
const (
|
||||
serverShutdownPoll = 500 * time.Millisecond
|
||||
|
||||
// DefaultShutdownTimeout - default shutdown timeout used for graceful http server shutdown.
|
||||
DefaultShutdownTimeout = 5 * time.Second
|
||||
|
||||
// DefaultMaxHeaderBytes - default maximum HTTP header size in bytes.
|
||||
DefaultMaxHeaderBytes = 1 * humanize.MiByte
|
||||
)
|
||||
|
||||
// Server - extended http.Server supports multiple addresses to serve and enhanced connection handling.
|
||||
type Server struct {
|
||||
http.Server
|
||||
Addrs []string // addresses on which the server listens for new connection.
|
||||
ShutdownTimeout time.Duration // timeout used for graceful server shutdown.
|
||||
listenerMutex sync.Mutex // to guard 'listener' field.
|
||||
listener *httpListener // HTTP listener for all 'Addrs' field.
|
||||
inShutdown uint32 // indicates whether the server is in shutdown or not
|
||||
requestCount int32 // counter holds no. of request in progress.
|
||||
}
|
||||
|
||||
// GetRequestCount - returns number of request in progress.
|
||||
func (srv *Server) GetRequestCount() int {
|
||||
return int(atomic.LoadInt32(&srv.requestCount))
|
||||
}
|
||||
|
||||
// Start - start HTTP server
|
||||
func (srv *Server) Start() (err error) {
|
||||
// Take a copy of server fields.
|
||||
var tlsConfig *tls.Config
|
||||
if srv.TLSConfig != nil {
|
||||
tlsConfig = srv.TLSConfig.Clone()
|
||||
}
|
||||
handler := srv.Handler // if srv.Handler holds non-synced state -> possible data race
|
||||
|
||||
addrs := set.CreateStringSet(srv.Addrs...).ToSlice() // copy and remove duplicates
|
||||
|
||||
// Create new HTTP listener.
|
||||
var listener *httpListener
|
||||
listener, err = newHTTPListener(
|
||||
addrs,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wrap given handler to do additional
|
||||
// * return 503 (service unavailable) if the server in shutdown.
|
||||
wrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// If server is in shutdown.
|
||||
if atomic.LoadUint32(&srv.inShutdown) != 0 {
|
||||
// To indicate disable keep-alives
|
||||
w.Header().Set("Connection", "close")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte(http.ErrServerClosed.Error()))
|
||||
w.(http.Flusher).Flush()
|
||||
return
|
||||
}
|
||||
|
||||
atomic.AddInt32(&srv.requestCount, 1)
|
||||
defer atomic.AddInt32(&srv.requestCount, -1)
|
||||
|
||||
// Handle request using passed handler.
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
srv.listenerMutex.Lock()
|
||||
srv.Handler = wrappedHandler
|
||||
srv.listener = listener
|
||||
srv.listenerMutex.Unlock()
|
||||
|
||||
// Start servicing with listener.
|
||||
if tlsConfig != nil {
|
||||
return srv.Server.Serve(tls.NewListener(listener, tlsConfig))
|
||||
}
|
||||
return srv.Server.Serve(listener)
|
||||
}
|
||||
|
||||
// Shutdown - shuts down HTTP server.
|
||||
func (srv *Server) Shutdown() error {
|
||||
srv.listenerMutex.Lock()
|
||||
if srv.listener == nil {
|
||||
srv.listenerMutex.Unlock()
|
||||
return http.ErrServerClosed
|
||||
}
|
||||
srv.listenerMutex.Unlock()
|
||||
|
||||
if atomic.AddUint32(&srv.inShutdown, 1) > 1 {
|
||||
// shutdown in progress
|
||||
return http.ErrServerClosed
|
||||
}
|
||||
|
||||
// Close underneath HTTP listener.
|
||||
srv.listenerMutex.Lock()
|
||||
err := srv.listener.Close()
|
||||
srv.listenerMutex.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for opened connection to be closed up to Shutdown timeout.
|
||||
shutdownTimeout := srv.ShutdownTimeout
|
||||
shutdownTimer := time.NewTimer(shutdownTimeout)
|
||||
ticker := time.NewTicker(serverShutdownPoll)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-shutdownTimer.C:
|
||||
// Write all running goroutines.
|
||||
tmp, err := ioutil.TempFile("", "minio-goroutines-*.txt")
|
||||
if err == nil {
|
||||
_ = pprof.Lookup("goroutine").WriteTo(tmp, 1)
|
||||
tmp.Close()
|
||||
return errors.New("timed out. some connections are still active. goroutines written to " + tmp.Name())
|
||||
}
|
||||
return errors.New("timed out. some connections are still active")
|
||||
case <-ticker.C:
|
||||
if atomic.LoadInt32(&srv.requestCount) <= 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewServer - creates new HTTP server using given arguments.
|
||||
func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificateFunc) *Server {
|
||||
secureCiphers := env.Get(api.EnvAPISecureCiphers, config.EnableOn) == config.EnableOn
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
if getCert != nil {
|
||||
tlsConfig = &tls.Config{
|
||||
PreferServerCipherSuites: true,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"http/1.1", "h2"},
|
||||
GetCertificate: getCert,
|
||||
}
|
||||
if secureCiphers || fips.Enabled {
|
||||
tlsConfig.CipherSuites = fips.CipherSuitesTLS()
|
||||
tlsConfig.CurvePreferences = fips.EllipticCurvesTLS()
|
||||
}
|
||||
}
|
||||
|
||||
httpServer := &Server{
|
||||
Addrs: addrs,
|
||||
ShutdownTimeout: DefaultShutdownTimeout,
|
||||
}
|
||||
httpServer.Handler = handler
|
||||
httpServer.TLSConfig = tlsConfig
|
||||
httpServer.MaxHeaderBytes = DefaultMaxHeaderBytes
|
||||
|
||||
return httpServer
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/pkg/certs"
|
||||
)
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, world")
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
addrs []string
|
||||
handler http.Handler
|
||||
certFn certs.GetCertificateFunc
|
||||
}{
|
||||
{[]string{"127.0.0.1:9000"}, handler, nil},
|
||||
{[]string{nonLoopBackIP + ":9000"}, handler, nil},
|
||||
{[]string{"127.0.0.1:9000", nonLoopBackIP + ":9000"}, handler, nil},
|
||||
{[]string{"127.0.0.1:9000"}, handler, getCert},
|
||||
{[]string{nonLoopBackIP + ":9000"}, handler, getCert},
|
||||
{[]string{"127.0.0.1:9000", nonLoopBackIP + ":9000"}, handler, getCert},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
server := NewServer(testCase.addrs, testCase.handler, testCase.certFn)
|
||||
if server == nil {
|
||||
t.Fatalf("Case %v: server: expected: <non-nil>, got: <nil>", (i + 1))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(server.Addrs, testCase.addrs) {
|
||||
t.Fatalf("Case %v: server.Addrs: expected: %v, got: %v", (i + 1), testCase.addrs, server.Addrs)
|
||||
}
|
||||
|
||||
// Interfaces are not comparable even with reflection.
|
||||
// if !reflect.DeepEqual(server.Handler, testCase.handler) {
|
||||
// t.Fatalf("Case %v: server.Handler: expected: %v, got: %v", (i + 1), testCase.handler, server.Handler)
|
||||
// }
|
||||
|
||||
if testCase.certFn == nil {
|
||||
if server.TLSConfig != nil {
|
||||
t.Fatalf("Case %v: server.TLSConfig: expected: <nil>, got: %v", (i + 1), server.TLSConfig)
|
||||
}
|
||||
} else {
|
||||
if server.TLSConfig == nil {
|
||||
t.Fatalf("Case %v: server.TLSConfig: expected: <non-nil>, got: <nil>", (i + 1))
|
||||
}
|
||||
}
|
||||
|
||||
if server.ShutdownTimeout != DefaultShutdownTimeout {
|
||||
t.Fatalf("Case %v: server.ShutdownTimeout: expected: %v, got: %v", (i + 1), DefaultShutdownTimeout, server.ShutdownTimeout)
|
||||
}
|
||||
|
||||
if server.MaxHeaderBytes != DefaultMaxHeaderBytes {
|
||||
t.Fatalf("Case %v: server.MaxHeaderBytes: expected: %v, got: %v", (i + 1), DefaultMaxHeaderBytes, server.MaxHeaderBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package stats
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// IncomingTrafficMeter counts the incoming bytes from the underlying request.Body.
|
||||
type IncomingTrafficMeter struct {
|
||||
io.ReadCloser
|
||||
countBytes int
|
||||
}
|
||||
|
||||
// Read calls the underlying Read and counts the transferred bytes.
|
||||
func (r *IncomingTrafficMeter) Read(p []byte) (n int, err error) {
|
||||
n, err = r.ReadCloser.Read(p)
|
||||
r.countBytes += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
// BytesCount returns the number of transferred bytes
|
||||
func (r IncomingTrafficMeter) BytesCount() int {
|
||||
return r.countBytes
|
||||
}
|
||||
|
||||
// OutgoingTrafficMeter counts the outgoing bytes through the responseWriter.
|
||||
type OutgoingTrafficMeter struct {
|
||||
// wrapper for underlying http.ResponseWriter.
|
||||
http.ResponseWriter
|
||||
countBytes int
|
||||
}
|
||||
|
||||
// Write calls the underlying write and counts the output bytes
|
||||
func (w *OutgoingTrafficMeter) Write(p []byte) (n int, err error) {
|
||||
n, err = w.ResponseWriter.Write(p)
|
||||
w.countBytes += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Flush calls the underlying Flush.
|
||||
func (w *OutgoingTrafficMeter) Flush() {
|
||||
w.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
// BytesCount returns the number of transferred bytes
|
||||
func (w OutgoingTrafficMeter) BytesCount() int {
|
||||
return w.countBytes
|
||||
}
|
||||
Reference in New Issue
Block a user