235 строки
5.6 KiB
Go
235 строки
5.6 KiB
Go
package manners
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/tls"
|
|
"errors"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func newServer() *GracefulServer {
|
|
return NewWithServer(new(http.Server))
|
|
}
|
|
|
|
// a simple step-controllable http client
|
|
type client struct {
|
|
tls bool
|
|
addr net.Addr
|
|
connected chan error
|
|
sendrequest chan bool
|
|
idle chan error
|
|
idlerelease chan bool
|
|
closed chan bool
|
|
}
|
|
|
|
func (c *client) Run() {
|
|
go func() {
|
|
var err error
|
|
conn, err := net.Dial(c.addr.Network(), c.addr.String())
|
|
if err != nil {
|
|
c.connected <- err
|
|
return
|
|
}
|
|
if c.tls {
|
|
conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true})
|
|
}
|
|
c.connected <- nil
|
|
for <-c.sendrequest {
|
|
_, err = conn.Write([]byte("GET / HTTP/1.1\nHost: localhost:8000\n\n"))
|
|
if err != nil {
|
|
c.idle <- err
|
|
}
|
|
// Read response; no content
|
|
scanner := bufio.NewScanner(conn)
|
|
for scanner.Scan() {
|
|
// our null handler doesn't send a body, so we know the request is
|
|
// done when we reach the blank line after the headers
|
|
if scanner.Text() == "" {
|
|
break
|
|
}
|
|
}
|
|
c.idle <- scanner.Err()
|
|
<-c.idlerelease
|
|
}
|
|
conn.Close()
|
|
ioutil.ReadAll(conn)
|
|
c.closed <- true
|
|
}()
|
|
}
|
|
|
|
func newClient(addr net.Addr, tls bool) *client {
|
|
return &client{
|
|
addr: addr,
|
|
tls: tls,
|
|
connected: make(chan error),
|
|
sendrequest: make(chan bool),
|
|
idle: make(chan error),
|
|
idlerelease: make(chan bool),
|
|
closed: make(chan bool),
|
|
}
|
|
}
|
|
|
|
// a handler that returns 200 ok with no body
|
|
var nullHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
|
|
|
func startGenericServer(t *testing.T, server *GracefulServer, statechanged chan http.ConnState, runner func() error) (l net.Listener, errc chan error) {
|
|
server.Addr = "localhost:0"
|
|
server.Handler = nullHandler
|
|
if statechanged != nil {
|
|
// Wrap the ConnState handler with something that will notify
|
|
// the statechanged channel when a state change happens
|
|
server.ConnState = func(conn net.Conn, newState http.ConnState) {
|
|
statechanged <- newState
|
|
}
|
|
}
|
|
|
|
//server.up = make(chan chan bool))
|
|
server.up = make(chan net.Listener)
|
|
exitchan := make(chan error)
|
|
|
|
go func() {
|
|
exitchan <- runner()
|
|
}()
|
|
|
|
// wait for server socket to be bound
|
|
select {
|
|
case l = <-server.up:
|
|
// all good
|
|
|
|
case err := <-exitchan:
|
|
// all bad
|
|
t.Fatal("Server failed to start", err)
|
|
}
|
|
return l, exitchan
|
|
}
|
|
|
|
func startServer(t *testing.T, server *GracefulServer, statechanged chan http.ConnState) (
|
|
l net.Listener, errc chan error) {
|
|
return startGenericServer(t, server, statechanged, server.ListenAndServe)
|
|
}
|
|
|
|
func startTLSServer(t *testing.T, server *GracefulServer, certFile, keyFile string, statechanged chan http.ConnState) (l net.Listener, errc chan error) {
|
|
runner := func() error {
|
|
return server.ListenAndServeTLS(certFile, keyFile)
|
|
}
|
|
|
|
return startGenericServer(t, server, statechanged, runner)
|
|
}
|
|
|
|
type tempFile struct {
|
|
*os.File
|
|
}
|
|
|
|
func newTempFile(content []byte) (*tempFile, error) {
|
|
f, err := ioutil.TempFile("", "graceful-test")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f.Write(content)
|
|
return &tempFile{f}, nil
|
|
}
|
|
|
|
func (tf *tempFile) Unlink() {
|
|
if tf.File != nil {
|
|
os.Remove(tf.Name())
|
|
tf.File = nil
|
|
}
|
|
}
|
|
|
|
type testWg struct {
|
|
sync.Mutex
|
|
count int
|
|
waitCalled chan int
|
|
}
|
|
|
|
func newTestWg() *testWg {
|
|
return &testWg{
|
|
waitCalled: make(chan int, 1),
|
|
}
|
|
}
|
|
|
|
func (wg *testWg) Add(delta int) {
|
|
wg.Lock()
|
|
wg.count++
|
|
wg.Unlock()
|
|
}
|
|
|
|
func (wg *testWg) Done() {
|
|
wg.Lock()
|
|
wg.count--
|
|
wg.Unlock()
|
|
}
|
|
|
|
func (wg *testWg) Wait() {
|
|
wg.Lock()
|
|
wg.waitCalled <- wg.count
|
|
wg.Unlock()
|
|
}
|
|
|
|
type fakeConn struct {
|
|
net.Conn
|
|
closeCalled bool
|
|
}
|
|
|
|
func (c *fakeConn) Close() error {
|
|
c.closeCalled = true
|
|
return nil
|
|
}
|
|
|
|
type fakeListener struct {
|
|
acceptRelease chan bool
|
|
closeCalled chan bool
|
|
}
|
|
|
|
func newFakeListener() *fakeListener { return &fakeListener{make(chan bool, 1), make(chan bool, 1)} }
|
|
|
|
func (l *fakeListener) Addr() net.Addr {
|
|
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
|
|
return addr
|
|
}
|
|
|
|
func (l *fakeListener) Close() error {
|
|
l.closeCalled <- true
|
|
l.acceptRelease <- true
|
|
return nil
|
|
}
|
|
|
|
func (l *fakeListener) Accept() (net.Conn, error) {
|
|
<-l.acceptRelease
|
|
return nil, errors.New("connection closed")
|
|
}
|
|
|
|
// localhostCert is a PEM-encoded TLS cert with SAN IPs
|
|
// "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
|
|
// of ASN.1 time).
|
|
// generated from src/pkg/crypto/tls:
|
|
// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
|
var (
|
|
localhostCert = []byte(`-----BEGIN CERTIFICATE-----
|
|
MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
|
|
bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj
|
|
bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa
|
|
IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA
|
|
AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
|
|
EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA
|
|
AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk
|
|
Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA==
|
|
-----END CERTIFICATE-----`)
|
|
|
|
localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
|
MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0
|
|
0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV
|
|
NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d
|
|
AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW
|
|
MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD
|
|
EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA
|
|
1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE=
|
|
-----END RSA PRIVATE KEY-----`)
|
|
)
|