mirror of
https://github.com/minio/minio.git
synced 2025-11-09 13:39:46 -05:00
Refactor HTTP server to address bugs (#4636)
* Refactor HTTP server to address bugs * Remove unnecessary goroutine to start multiple TCP listeners. * HTTP server waits for shutdown to maximum of Server.ShutdownTimeout than per serverShutdownPoll. * Handles new connection errors properly. * Handles read and write timeout properly. * Handles error on start of HTTP server properly by exiting minio process. Fixes #4494 #4476 & fixed review comments
This commit is contained in:
88
pkg/http/bufconn.go
Normal file
88
pkg/http/bufconn.go
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 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 http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BufConn - is a generic stream-oriented network connection supporting buffered reader and read/write timeout.
|
||||
type BufConn struct {
|
||||
net.Conn
|
||||
bufReader *bufio.Reader // buffered reader wraps reader in net.Conn.
|
||||
readTimeout time.Duration // sets the read timeout in the connection.
|
||||
writeTimeout time.Duration // sets the write timeout in the connection.
|
||||
updateBytesReadFunc func(int) // function to be called to update bytes read.
|
||||
updateBytesWrittenFunc func(int) // function to be called to update bytes written.
|
||||
}
|
||||
|
||||
func (c *BufConn) setReadTimeout() {
|
||||
if c.readTimeout != 0 {
|
||||
c.SetReadDeadline(time.Now().UTC().Add(c.readTimeout))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *BufConn) setWriteTimeout() {
|
||||
if c.writeTimeout != 0 {
|
||||
c.SetWriteDeadline(time.Now().UTC().Add(c.writeTimeout))
|
||||
}
|
||||
}
|
||||
|
||||
// Peek - returns the next n bytes without advancing the reader. It just wraps bufio.Reader.Peek().
|
||||
func (c *BufConn) Peek(n int) ([]byte, error) {
|
||||
c.setReadTimeout()
|
||||
return c.bufReader.Peek(n)
|
||||
}
|
||||
|
||||
// Read - reads data from the connection using wrapped buffered reader.
|
||||
func (c *BufConn) Read(b []byte) (n int, err error) {
|
||||
c.setReadTimeout()
|
||||
n, err = c.bufReader.Read(b)
|
||||
|
||||
if err == nil && c.updateBytesReadFunc != nil {
|
||||
c.updateBytesReadFunc(n)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write - writes data to the connection.
|
||||
func (c *BufConn) Write(b []byte) (n int, err error) {
|
||||
c.setWriteTimeout()
|
||||
n, err = c.Conn.Write(b)
|
||||
|
||||
if err == nil && c.updateBytesWrittenFunc != nil {
|
||||
c.updateBytesWrittenFunc(n)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// newBufConn - creates a new connection object wrapping net.Conn.
|
||||
func newBufConn(c net.Conn, readTimeout, writeTimeout time.Duration,
|
||||
updateBytesReadFunc, updateBytesWrittenFunc func(int)) *BufConn {
|
||||
return &BufConn{
|
||||
Conn: c,
|
||||
bufReader: bufio.NewReader(c),
|
||||
readTimeout: readTimeout,
|
||||
writeTimeout: writeTimeout,
|
||||
updateBytesReadFunc: updateBytesReadFunc,
|
||||
updateBytesWrittenFunc: updateBytesWrittenFunc,
|
||||
}
|
||||
}
|
||||
108
pkg/http/bufconn_test.go
Normal file
108
pkg/http/bufconn_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 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 http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Test bufconn handles read timeout properly by reading two messages beyond deadline.
|
||||
func TestBuffConnReadTimeout(t *testing.T) {
|
||||
port := getNextPort()
|
||||
l, err := net.Listen("tcp", "localhost:"+port)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create listener. %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
tcpListener, ok := l.(*net.TCPListener)
|
||||
if !ok {
|
||||
t.Fatalf("failed to assert to net.TCPListener")
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
go func() {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
|
||||
tcpConn, terr := tcpListener.AcceptTCP()
|
||||
if terr != nil {
|
||||
t.Fatalf("failed to accept new connection. %v", terr)
|
||||
}
|
||||
bufconn := newBufConn(tcpConn, 1*time.Second, 1*time.Second, nil, nil)
|
||||
defer bufconn.Close()
|
||||
|
||||
// Read a line
|
||||
var b = make([]byte, 12)
|
||||
_, terr = bufconn.Read(b)
|
||||
if terr != nil {
|
||||
t.Fatalf("failed to read from client. %v", terr)
|
||||
}
|
||||
received := string(b)
|
||||
if received != "message one\n" {
|
||||
t.Fatalf(`server: expected: "message one\n", got: %v`, received)
|
||||
}
|
||||
|
||||
// Wait for more than read timeout to simulate processing.
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
_, terr = bufconn.Read(b)
|
||||
if terr != nil {
|
||||
t.Fatalf("failed to read from client. %v", terr)
|
||||
}
|
||||
received = string(b)
|
||||
if received != "message two\n" {
|
||||
t.Fatalf(`server: expected: "message two\n", got: %v`, received)
|
||||
}
|
||||
|
||||
// Send a response.
|
||||
_, terr = io.WriteString(bufconn, "messages received\n")
|
||||
if terr != nil {
|
||||
t.Fatalf("failed to write to client. %v", terr)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := net.Dial("tcp", "localhost:"+port)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect to server. %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
_, err = io.WriteString(c, "message one\n")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write to server. %v", err)
|
||||
}
|
||||
_, err = io.WriteString(c, "message two\n")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write to server. %v", err)
|
||||
}
|
||||
|
||||
received, err := bufio.NewReader(c).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read from server. %v", err)
|
||||
}
|
||||
if received != "messages received\n" {
|
||||
t.Fatalf(`client: expected: "messages received\n", got: %v`, received)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
320
pkg/http/listener.go
Normal file
320
pkg/http/listener.go
Normal file
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 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 http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sslRequiredErrMsg = []byte("HTTP/1.0 403 Forbidden\r\n\r\nSSL required")
|
||||
|
||||
// HTTP methods.
|
||||
var methods = []string{
|
||||
http.MethodGet,
|
||||
http.MethodHead,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodPatch,
|
||||
http.MethodDelete,
|
||||
http.MethodConnect,
|
||||
http.MethodOptions,
|
||||
http.MethodTrace,
|
||||
"PRI", // HTTP 2 method
|
||||
}
|
||||
|
||||
// maximum length of above methods + one space.
|
||||
var methodMaxLen = getMethodMaxLen() + 1
|
||||
|
||||
func getMethodMaxLen() int {
|
||||
maxLen := 0
|
||||
for _, method := range methods {
|
||||
if len(method) > maxLen {
|
||||
maxLen = len(method)
|
||||
}
|
||||
}
|
||||
|
||||
return maxLen
|
||||
}
|
||||
|
||||
func isHTTPMethod(s string) bool {
|
||||
for _, method := range methods {
|
||||
if s == method {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
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.
|
||||
tlsConfig *tls.Config // TLS configuration
|
||||
tcpKeepAliveTimeout time.Duration
|
||||
readTimeout time.Duration
|
||||
writeTimeout time.Duration
|
||||
updateBytesReadFunc func(int) // function to be called to update bytes read in BufConn.
|
||||
updateBytesWrittenFunc func(int) // function to be called to update bytes written in BufConn.
|
||||
errorLogFunc func(error, string, ...interface{}) // function to be called on errors.
|
||||
}
|
||||
|
||||
// start - starts separate goroutine for each TCP listener. A valid insecure/TLS HTTP 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{}) {
|
||||
// Tune accepted TCP connection.
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(listener.tcpKeepAliveTimeout)
|
||||
|
||||
bufconn := newBufConn(tcpConn, listener.readTimeout, listener.writeTimeout,
|
||||
listener.updateBytesReadFunc, listener.updateBytesWrittenFunc)
|
||||
|
||||
// Peek bytes of maximum length of all HTTP methods.
|
||||
data, err := bufconn.Peek(methodMaxLen)
|
||||
if err != nil {
|
||||
if listener.errorLogFunc != nil {
|
||||
listener.errorLogFunc(err,
|
||||
"Error in reading from new connection %s at server %s",
|
||||
bufconn.RemoteAddr(), bufconn.LocalAddr())
|
||||
}
|
||||
bufconn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Return bufconn if read data is a valid HTTP method.
|
||||
tokens := strings.SplitN(string(data), " ", 2)
|
||||
if isHTTPMethod(tokens[0]) {
|
||||
if listener.tlsConfig == nil {
|
||||
send(acceptResult{bufconn, nil}, doneCh)
|
||||
} else {
|
||||
// As TLS is configured and we got plain text HTTP request,
|
||||
// return 403 (forbidden) error.
|
||||
bufconn.Write(sslRequiredErrMsg)
|
||||
bufconn.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if listener.tlsConfig != nil {
|
||||
// As the listener is configured with TLS, try to do TLS handshake, drop the connection if it fails.
|
||||
tlsConn := tls.Server(bufconn, listener.tlsConfig)
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
if listener.errorLogFunc != nil {
|
||||
listener.errorLogFunc(err,
|
||||
"TLS handshake failed with new connection %s at server %s",
|
||||
bufconn.RemoteAddr(), bufconn.LocalAddr())
|
||||
}
|
||||
bufconn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Check whether the connection contains HTTP request or not.
|
||||
bufconn = newBufConn(tlsConn, listener.readTimeout, listener.writeTimeout,
|
||||
listener.updateBytesReadFunc, listener.updateBytesWrittenFunc)
|
||||
|
||||
// Peek bytes of maximum length of all HTTP methods.
|
||||
data, err := bufconn.Peek(methodMaxLen)
|
||||
if err != nil {
|
||||
if listener.errorLogFunc != nil {
|
||||
listener.errorLogFunc(err,
|
||||
"Error in reading from new TLS connection %s at server %s",
|
||||
bufconn.RemoteAddr(), bufconn.LocalAddr())
|
||||
}
|
||||
bufconn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Return bufconn if read data is a valid HTTP method.
|
||||
tokens := strings.SplitN(string(data), " ", 2)
|
||||
if isHTTPMethod(tokens[0]) {
|
||||
send(acceptResult{bufconn, nil}, doneCh)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if listener.errorLogFunc != nil {
|
||||
listener.errorLogFunc(errors.New("junk message"),
|
||||
"Received non-HTTP message from new connection %s at server %s",
|
||||
bufconn.RemoteAddr(), bufconn.LocalAddr())
|
||||
}
|
||||
|
||||
bufconn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// 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,
|
||||
tlsConfig *tls.Config,
|
||||
tcpKeepAliveTimeout time.Duration,
|
||||
readTimeout time.Duration,
|
||||
writeTimeout time.Duration,
|
||||
updateBytesReadFunc func(int),
|
||||
updateBytesWrittenFunc func(int),
|
||||
errorLogFunc func(error, string, ...interface{})) (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 = net.Listen("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,
|
||||
tlsConfig: tlsConfig,
|
||||
tcpKeepAliveTimeout: tcpKeepAliveTimeout,
|
||||
readTimeout: readTimeout,
|
||||
writeTimeout: writeTimeout,
|
||||
updateBytesReadFunc: updateBytesReadFunc,
|
||||
updateBytesWrittenFunc: updateBytesWrittenFunc,
|
||||
errorLogFunc: errorLogFunc,
|
||||
}
|
||||
listener.start()
|
||||
|
||||
return listener, nil
|
||||
}
|
||||
817
pkg/http/listener_test.go
Normal file
817
pkg/http/listener_test.go
Normal file
@@ -0,0 +1,817 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 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 http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
)
|
||||
|
||||
var serverPort uint32 = 60000
|
||||
|
||||
func getNextPort() string {
|
||||
return strconv.Itoa(int(atomic.AddUint32(&serverPort, 1)))
|
||||
}
|
||||
|
||||
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 getTLSConfig(t *testing.T) *tls.Config {
|
||||
tlsCert, err := getTLSCert()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to parse private/certificate data. %v\n", err)
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
PreferServerCipherSuites: true,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"http/1.1", "h2"},
|
||||
}
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCert)
|
||||
|
||||
return tlsConfig
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Test getMethodMaxLen()
|
||||
func TestGetMethodMaxLen(t *testing.T) {
|
||||
l := getMethodMaxLen()
|
||||
if l != (methodMaxLen - 1) {
|
||||
t.Fatalf("expected: %v, got: %v", (methodMaxLen - 1), l)
|
||||
}
|
||||
}
|
||||
|
||||
// Test isHTTPMethod()
|
||||
func TestIsHTTPMethod(t *testing.T) {
|
||||
testCases := []struct {
|
||||
method string
|
||||
expectedResult bool
|
||||
}{
|
||||
{"", false},
|
||||
{"get", false},
|
||||
{"put", false},
|
||||
{"UPLOAD", false},
|
||||
{"OPTIONS", true},
|
||||
{"GET", true},
|
||||
{"HEAD", true},
|
||||
{"POST", true},
|
||||
{"PUT", true},
|
||||
{"DELETE", true},
|
||||
{"TRACE", true},
|
||||
{"CONNECT", true},
|
||||
{"PRI", true},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
result := isHTTPMethod(testCase.method)
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("expected: %v, got: %v", testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewHTTPListener(t *testing.T) {
|
||||
errMsg := ": no such host"
|
||||
if runtime.GOOS == "windows" {
|
||||
errMsg = ": No such host is known."
|
||||
}
|
||||
|
||||
remoteAddrErrMsg := "listen tcp 93.184.216.34:9000: bind: cannot assign requested address"
|
||||
if runtime.GOOS == "windows" {
|
||||
remoteAddrErrMsg = "listen tcp 93.184.216.34:9000: bind: The requested address is not valid in its context."
|
||||
}
|
||||
|
||||
tlsConfig := getTLSConfig(t)
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
tcpKeepAliveTimeout time.Duration
|
||||
readTimeout time.Duration
|
||||
writeTimeout time.Duration
|
||||
updateBytesReadFunc func(int)
|
||||
updateBytesWrittenFunc func(int)
|
||||
errorLogFunc func(error, string, ...interface{})
|
||||
expectedErr error
|
||||
}{
|
||||
{[]string{"93.184.216.34:9000"}, nil, time.Duration(0), time.Duration(0), time.Duration(0), nil, nil, nil, errors.New(remoteAddrErrMsg)},
|
||||
{[]string{"example.org:9000"}, nil, time.Duration(0), time.Duration(0), time.Duration(0), nil, nil, nil, errors.New(remoteAddrErrMsg)},
|
||||
{[]string{"unknown-host"}, nil, time.Duration(0), time.Duration(0), time.Duration(0), nil, nil, nil, errors.New("listen tcp: missing port in address unknown-host")},
|
||||
{[]string{"unknown-host:9000"}, nil, time.Duration(0), time.Duration(0), time.Duration(0), nil, nil, nil, errors.New("listen tcp: lookup unknown-host" + errMsg)},
|
||||
{[]string{"localhost:9000", "93.184.216.34:9000"}, nil, time.Duration(0), time.Duration(0), time.Duration(0), nil, nil, nil, errors.New(remoteAddrErrMsg)},
|
||||
{[]string{"localhost:9000", "unknown-host:9000"}, nil, time.Duration(0), time.Duration(0), time.Duration(0), nil, nil, nil, errors.New("listen tcp: lookup unknown-host" + errMsg)},
|
||||
{[]string{"localhost:" + getNextPort()}, nil, time.Duration(0), time.Duration(0), time.Duration(0), nil, nil, nil, nil},
|
||||
{[]string{"localhost:" + getNextPort()}, tlsConfig, time.Duration(0), time.Duration(0), time.Duration(0), nil, nil, nil, nil},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
testCase.tcpKeepAliveTimeout,
|
||||
testCase.readTimeout,
|
||||
testCase.writeTimeout,
|
||||
testCase.updateBytesReadFunc,
|
||||
testCase.updateBytesWrittenFunc,
|
||||
testCase.errorLogFunc,
|
||||
)
|
||||
|
||||
if testCase.expectedErr == nil {
|
||||
if err != nil {
|
||||
t.Fatalf("error: expected = <nil>, got = %v", err)
|
||||
}
|
||||
} else if err == nil {
|
||||
t.Fatalf("error: expected = %v, got = <nil>", testCase.expectedErr)
|
||||
} else {
|
||||
var match bool
|
||||
if strings.HasSuffix(testCase.expectedErr.Error(), errMsg) {
|
||||
match = strings.HasSuffix(err.Error(), errMsg)
|
||||
} else {
|
||||
match = (testCase.expectedErr.Error() == err.Error())
|
||||
}
|
||||
if !match {
|
||||
t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerStartClose(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
var casePorts []string
|
||||
for i := 0; i < 6; i++ {
|
||||
casePorts = append(casePorts, getNextPort())
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
}{
|
||||
{[]string{"localhost:" + casePorts[0]}, nil},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[1]}, nil},
|
||||
{[]string{"127.0.0.1:" + casePorts[2], nonLoopBackIP + ":" + casePorts[2]}, nil},
|
||||
{[]string{"localhost:" + casePorts[3]}, tlsConfig},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[4]}, tlsConfig},
|
||||
{[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, tlsConfig},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
for _, serverAddr := range testCase.serverAddrs {
|
||||
conn, err := net.Dial("tcp", serverAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAddr(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
var casePorts []string
|
||||
for i := 0; i < 6; i++ {
|
||||
casePorts = append(casePorts, getNextPort())
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
expectedAddr string
|
||||
}{
|
||||
{[]string{"localhost:" + casePorts[0]}, nil, "127.0.0.1:" + casePorts[0]},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[1]}, nil, nonLoopBackIP + ":" + casePorts[1]},
|
||||
{[]string{"127.0.0.1:" + casePorts[2], nonLoopBackIP + ":" + casePorts[2]}, nil, "0.0.0.0:" + casePorts[2]},
|
||||
{[]string{"localhost:" + casePorts[3]}, tlsConfig, "127.0.0.1:" + casePorts[3]},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[4]}, tlsConfig, nonLoopBackIP + ":" + casePorts[4]},
|
||||
{[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, tlsConfig, "0.0.0.0:" + casePorts[5]},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
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) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
var casePorts []string
|
||||
for i := 0; i < 6; i++ {
|
||||
casePorts = append(casePorts, getNextPort())
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
expectedAddrs set.StringSet
|
||||
}{
|
||||
{[]string{"localhost:" + casePorts[0]}, nil, set.CreateStringSet("127.0.0.1:" + casePorts[0])},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[1]}, nil, set.CreateStringSet(nonLoopBackIP + ":" + casePorts[1])},
|
||||
{[]string{"127.0.0.1:" + casePorts[2], nonLoopBackIP + ":" + casePorts[2]}, nil, set.CreateStringSet("127.0.0.1:"+casePorts[2], nonLoopBackIP+":"+casePorts[2])},
|
||||
{[]string{"localhost:" + casePorts[3]}, tlsConfig, set.CreateStringSet("127.0.0.1:" + casePorts[3])},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[4]}, tlsConfig, set.CreateStringSet(nonLoopBackIP + ":" + casePorts[4])},
|
||||
{[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, tlsConfig, set.CreateStringSet("127.0.0.1:"+casePorts[5], nonLoopBackIP+":"+casePorts[5])},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAccept(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
var casePorts []string
|
||||
for i := 0; i < 6; i++ {
|
||||
casePorts = append(casePorts, getNextPort())
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
request string
|
||||
reply string
|
||||
}{
|
||||
{[]string{"localhost:" + casePorts[0]}, nil, "GET / HTTP/1.0\n", "200 OK\n"},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[1]}, nil, "POST / HTTP/1.0\n", "200 OK\n"},
|
||||
{[]string{"127.0.0.1:" + casePorts[2], nonLoopBackIP + ":" + casePorts[2]}, nil, "CONNECT \n", "200 OK\n"},
|
||||
{[]string{"localhost:" + casePorts[3]}, tlsConfig, "GET / HTTP/1.0\n", "200 OK\n"},
|
||||
{[]string{nonLoopBackIP + ":" + casePorts[4]}, tlsConfig, "POST / HTTP/1.0\n", "200 OK\n"},
|
||||
{[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, tlsConfig, "CONNECT \n", "200 OK\n"},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
for _, serverAddr := range testCase.serverAddrs {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
if testCase.tlsConfig == nil {
|
||||
conn, err = net.Dial("tcp", serverAddr)
|
||||
} else {
|
||||
conn, err = tls.Dial("tcp", serverAddr, &tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if _, err = io.WriteString(conn, testCase.request); err != nil {
|
||||
t.Fatalf("Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
serverConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: accept: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
request, err := bufio.NewReader(serverConn).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: request read: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if testCase.request != request {
|
||||
t.Fatalf("Test %d: request: expected = %v, got = %v", i+1, testCase.request, request)
|
||||
}
|
||||
|
||||
if _, err = io.WriteString(serverConn, testCase.reply); err != nil {
|
||||
t.Fatalf("Test %d: reply send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
reply, err := bufio.NewReader(conn).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: reply read: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if testCase.reply != reply {
|
||||
t.Fatalf("Test %d: reply: expected = %v, got = %v", i+1, testCase.reply, reply)
|
||||
}
|
||||
|
||||
serverConn.Close()
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAcceptPeekError(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
var casePorts []string
|
||||
for i := 0; i < 2; i++ {
|
||||
casePorts = append(casePorts, getNextPort())
|
||||
}
|
||||
errorFunc := func(err error, template string, args ...interface{}) {
|
||||
msg := fmt.Sprintf("error: %v. ", err)
|
||||
msg += fmt.Sprintf(template, args...)
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
request string
|
||||
}{
|
||||
{[]string{"127.0.0.1:" + casePorts[0], nonLoopBackIP + ":" + casePorts[0]}, nil, "CONN"},
|
||||
{[]string{"127.0.0.1:" + casePorts[1], nonLoopBackIP + ":" + casePorts[1]}, tlsConfig, "CONN"},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
nil,
|
||||
nil,
|
||||
errorFunc,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
serverConn, aerr := listener.Accept()
|
||||
if aerr == nil {
|
||||
t.Fatalf("Test %d: accept: expected = <error>, got = <nil>", i+1)
|
||||
}
|
||||
if serverConn != nil {
|
||||
t.Fatalf("Test %d: accept: server expected = <nil>, got = %v", i+1, serverConn)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, serverAddr := range testCase.serverAddrs {
|
||||
conn, err := net.Dial("tcp", serverAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if _, err = io.WriteString(conn, testCase.request); err != nil {
|
||||
t.Fatalf("Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAcceptTLSError(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
var casePorts []string
|
||||
for i := 0; i < 1; i++ {
|
||||
casePorts = append(casePorts, getNextPort())
|
||||
}
|
||||
errorFunc := func(err error, template string, args ...interface{}) {
|
||||
msg := fmt.Sprintf("error: %v. ", err)
|
||||
msg += fmt.Sprintf(template, args...)
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
request string
|
||||
}{
|
||||
{[]string{"127.0.0.1:" + casePorts[0], nonLoopBackIP + ":" + casePorts[0]}, tlsConfig, "GET / HTTP/1.0\n"},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
nil,
|
||||
nil,
|
||||
errorFunc,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
for _, serverAddr := range testCase.serverAddrs {
|
||||
conn, err := net.Dial("tcp", serverAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if _, err = io.WriteString(conn, testCase.request); err != nil {
|
||||
t.Fatalf("Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
serverConn, aerr := listener.Accept()
|
||||
if aerr == nil {
|
||||
t.Fatalf("Test %d: accept: expected = <error>, got = <nil>", i+1)
|
||||
}
|
||||
if serverConn != nil {
|
||||
t.Fatalf("Test %d: accept: server expected = <nil>, got = %v", i+1, serverConn)
|
||||
}
|
||||
}()
|
||||
|
||||
buf := make([]byte, len(sslRequiredErrMsg))
|
||||
n, err := io.ReadFull(conn, buf)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: reply read: expected = <nil> got = %v", i+1, err)
|
||||
} else if n != len(buf) {
|
||||
t.Fatalf("Test %d: reply length: expected = %v got = %v", i+1, len(buf), n)
|
||||
} else if !bytes.Equal(buf, sslRequiredErrMsg) {
|
||||
t.Fatalf("Test %d: reply: expected = %v got = %v", i+1, string(sslRequiredErrMsg), string(buf))
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAcceptError(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
var casePorts []string
|
||||
for i := 0; i < 3; i++ {
|
||||
casePorts = append(casePorts, getNextPort())
|
||||
}
|
||||
errorFunc := func(err error, template string, args ...interface{}) {
|
||||
msg := fmt.Sprintf("error: %v. ", err)
|
||||
msg += fmt.Sprintf(template, args...)
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
secureClient bool
|
||||
request string
|
||||
}{
|
||||
{[]string{"127.0.0.1:" + casePorts[0], nonLoopBackIP + ":" + casePorts[0]}, nil, false, "CONNECTION"},
|
||||
{[]string{"127.0.0.1:" + casePorts[1], nonLoopBackIP + ":" + casePorts[1]}, tlsConfig, false, "CONNECTION"},
|
||||
{[]string{"127.0.0.1:" + casePorts[2], nonLoopBackIP + ":" + casePorts[2]}, tlsConfig, true, "CONNECTION"},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
nil,
|
||||
nil,
|
||||
errorFunc,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
for _, serverAddr := range testCase.serverAddrs {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
if testCase.secureClient {
|
||||
conn, err = tls.Dial("tcp", serverAddr, &tls.Config{InsecureSkipVerify: true})
|
||||
} else {
|
||||
conn, err = net.Dial("tcp", serverAddr)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if _, err = io.WriteString(conn, testCase.request); err != nil {
|
||||
t.Fatalf("Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
serverConn, aerr := listener.Accept()
|
||||
if aerr == nil {
|
||||
t.Fatalf("Test %d: accept: expected = <error>, got = <nil>", i+1)
|
||||
}
|
||||
if serverConn != nil {
|
||||
t.Fatalf("Test %d: accept: server expected = <nil>, got = %v", i+1, serverConn)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = bufio.NewReader(conn).ReadString('\n')
|
||||
if err == nil {
|
||||
t.Fatalf("Test %d: reply read: expected = EOF got = <nil>", i+1)
|
||||
} else if err.Error() != "EOF" {
|
||||
t.Fatalf("Test %d: reply read: expected = EOF got = %v", i+1, err)
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAcceptParallel(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
case1Port := getNextPort()
|
||||
case2Port := getNextPort()
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
reply string
|
||||
}{
|
||||
{[]string{"127.0.0.1:" + case1Port, nonLoopBackIP + ":" + case1Port}, nil, "200 OK\n"},
|
||||
{[]string{"127.0.0.1:" + case2Port, nonLoopBackIP + ":" + case2Port}, tlsConfig, "200 OK\n"},
|
||||
}
|
||||
|
||||
// As t.Fatalf() is not goroutine safe, use this closure.
|
||||
fail := func(template string, args ...interface{}) {
|
||||
fmt.Printf(template, args...)
|
||||
fmt.Println()
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
connect := func(i int, serverAddr string, secure bool, delay bool, request, reply string) {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
if secure {
|
||||
conn, err = tls.Dial("tcp", serverAddr, &tls.Config{InsecureSkipVerify: true})
|
||||
} else {
|
||||
conn, err = net.Dial("tcp", serverAddr)
|
||||
}
|
||||
if err != nil {
|
||||
fail("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if delay {
|
||||
if _, err = io.WriteString(conn, request[:3]); err != nil {
|
||||
fail("Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
if _, err = io.WriteString(conn, request[3:]); err != nil {
|
||||
fail("Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
} else {
|
||||
if _, err = io.WriteString(conn, request); err != nil {
|
||||
fail("Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
}
|
||||
|
||||
received, err := bufio.NewReader(conn).ReadString('\n')
|
||||
if err != nil {
|
||||
fail("Test %d: reply read: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
if received != reply {
|
||||
fail("Test %d: reply: expected = %v, got = %v", i+1, reply, received)
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
handleConnection := func(i int, wg *sync.WaitGroup, serverConn net.Conn, request, reply string) {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
|
||||
received, err := bufio.NewReader(serverConn).ReadString('\n')
|
||||
if err != nil {
|
||||
fail("Test %d: request read: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if received != request {
|
||||
fail("Test %d: request: expected = %v, got = %v", i+1, request, received)
|
||||
}
|
||||
|
||||
if _, err := io.WriteString(serverConn, reply); err != nil {
|
||||
fail("Test %d: reply send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
serverConn.Close()
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
for _, serverAddr := range testCase.serverAddrs {
|
||||
go connect(i, serverAddr, testCase.tlsConfig != nil, true, "GET /1 HTTP/1.0\n", testCase.reply)
|
||||
go connect(i, serverAddr, testCase.tlsConfig != nil, false, "GET /2 HTTP/1.0\n", testCase.reply)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
serverConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: accept: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
go handleConnection(i, &wg, serverConn, "GET /2 HTTP/1.0\n", testCase.reply)
|
||||
|
||||
serverConn, err = listener.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: accept: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
go handleConnection(i, &wg, serverConn, "GET /1 HTTP/1.0\n", testCase.reply)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
173
pkg/http/server.go
Normal file
173
pkg/http/server.go
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 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 http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
const (
|
||||
serverShutdownPoll = 500 * time.Millisecond
|
||||
|
||||
// DefaultShutdownTimeout - default shutdown timeout used for graceful http server shutdown.
|
||||
DefaultShutdownTimeout = 5 * time.Second
|
||||
|
||||
// DefaultTCPKeepAliveTimeout - default TCP keep alive timeout for accepted connection.
|
||||
DefaultTCPKeepAliveTimeout = 10 * time.Second
|
||||
|
||||
// DefaultReadTimeout - default timout to read data from accepted connection.
|
||||
DefaultReadTimeout = 30 * time.Second
|
||||
|
||||
// DefaultWriteTimeout - default timout to write data to accepted connection.
|
||||
DefaultWriteTimeout = 30 * 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.
|
||||
TCPKeepAliveTimeout time.Duration // timeout used for underneath TCP connection.
|
||||
UpdateBytesReadFunc func(int) // function to be called to update bytes read in bufConn.
|
||||
UpdateBytesWrittenFunc func(int) // function to be called to update bytes written in bufConn.
|
||||
ErrorLogFunc func(error, string, ...interface{}) // function to be called on errors.
|
||||
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 process.
|
||||
}
|
||||
|
||||
// Start - start HTTP server
|
||||
func (srv *Server) Start() (err error) {
|
||||
// Take a copy of server fields.
|
||||
tlsConfig := srv.TLSConfig
|
||||
readTimeout := srv.ReadTimeout
|
||||
writeTimeout := srv.WriteTimeout
|
||||
handler := srv.Handler
|
||||
|
||||
addrs := srv.Addrs
|
||||
tcpKeepAliveTimeout := srv.TCPKeepAliveTimeout
|
||||
updateBytesReadFunc := srv.UpdateBytesReadFunc
|
||||
updateBytesWrittenFunc := srv.UpdateBytesWrittenFunc
|
||||
errorLogFunc := srv.ErrorLogFunc
|
||||
|
||||
// Create new HTTP listener.
|
||||
var listener *httpListener
|
||||
listener, err = newHTTPListener(
|
||||
addrs,
|
||||
tlsConfig,
|
||||
tcpKeepAliveTimeout,
|
||||
readTimeout,
|
||||
writeTimeout,
|
||||
updateBytesReadFunc,
|
||||
updateBytesWrittenFunc,
|
||||
errorLogFunc,
|
||||
)
|
||||
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) {
|
||||
atomic.AddInt32(&srv.requestCount, 1)
|
||||
defer atomic.AddInt32(&srv.requestCount, -1)
|
||||
|
||||
// If server is in shutdown, return 503 (service unavailable)
|
||||
if atomic.LoadUint32(&srv.inShutdown) != 0 {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
// 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.
|
||||
return srv.Server.Serve(listener)
|
||||
}
|
||||
|
||||
// Shutdown - shuts down HTTP server.
|
||||
func (srv *Server) Shutdown() error {
|
||||
if atomic.AddUint32(&srv.inShutdown, 1) > 1 {
|
||||
// shutdown in progress
|
||||
return errors.New("http server already in shutdown")
|
||||
}
|
||||
|
||||
// Close underneath HTTP listener.
|
||||
srv.listenerMutex.Lock()
|
||||
err := srv.listener.Close()
|
||||
srv.listenerMutex.Unlock()
|
||||
|
||||
// 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:
|
||||
return errors.New("timed out. some connections are still active. doing abnormal shutdown")
|
||||
case <-ticker.C:
|
||||
if atomic.LoadInt32(&srv.requestCount) <= 0 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewServer - creates new HTTP server using given arguments.
|
||||
func NewServer(addrs []string, handler http.Handler, certificate *tls.Certificate) *Server {
|
||||
var tlsConfig *tls.Config
|
||||
if certificate != nil {
|
||||
tlsConfig = &tls.Config{
|
||||
PreferServerCipherSuites: true,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"http/1.1", "h2"},
|
||||
}
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *certificate)
|
||||
}
|
||||
|
||||
httpServer := &Server{
|
||||
Addrs: addrs,
|
||||
ShutdownTimeout: DefaultShutdownTimeout,
|
||||
TCPKeepAliveTimeout: DefaultTCPKeepAliveTimeout,
|
||||
listenerMutex: &sync.Mutex{},
|
||||
}
|
||||
httpServer.Handler = handler
|
||||
httpServer.TLSConfig = tlsConfig
|
||||
httpServer.ReadTimeout = DefaultReadTimeout
|
||||
httpServer.WriteTimeout = DefaultWriteTimeout
|
||||
httpServer.MaxHeaderBytes = DefaultMaxHeaderBytes
|
||||
|
||||
return httpServer
|
||||
}
|
||||
99
pkg/http/server_test.go
Normal file
99
pkg/http/server_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 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 http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
certificate, err := getTLSCert()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to parse private/certificate data. %v\n", err)
|
||||
}
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, world")
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
addrs []string
|
||||
handler http.Handler
|
||||
certificate *tls.Certificate
|
||||
}{
|
||||
{[]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, &certificate},
|
||||
{[]string{nonLoopBackIP + ":9000"}, handler, &certificate},
|
||||
{[]string{"127.0.0.1:9000", nonLoopBackIP + ":9000"}, handler, &certificate},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
server := NewServer(testCase.addrs, testCase.handler, testCase.certificate)
|
||||
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.certificate == 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.TCPKeepAliveTimeout != DefaultTCPKeepAliveTimeout {
|
||||
t.Fatalf("Case %v: server.TCPKeepAliveTimeout: expected: %v, got: %v", (i + 1), DefaultTCPKeepAliveTimeout, server.TCPKeepAliveTimeout)
|
||||
}
|
||||
|
||||
if server.listenerMutex == nil {
|
||||
t.Fatalf("Case %v: server.listenerMutex: expected: <non-nil>, got: <nil>", (i + 1))
|
||||
}
|
||||
|
||||
if server.ReadTimeout != DefaultReadTimeout {
|
||||
t.Fatalf("Case %v: server.ReadTimeout: expected: %v, got: %v", (i + 1), DefaultReadTimeout, server.ReadTimeout)
|
||||
}
|
||||
|
||||
if server.WriteTimeout != DefaultWriteTimeout {
|
||||
t.Fatalf("Case %v: server.WriteTimeout: expected: %v, got: %v", (i + 1), DefaultWriteTimeout, server.WriteTimeout)
|
||||
}
|
||||
|
||||
if server.MaxHeaderBytes != DefaultMaxHeaderBytes {
|
||||
t.Fatalf("Case %v: server.MaxHeaderBytes: expected: %v, got: %v", (i + 1), DefaultMaxHeaderBytes, server.MaxHeaderBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user