mirror of
https://github.com/caddyserver/caddy.git
synced 2025-05-24 02:02:26 -04:00
Merge branch 'master' into proxy/single-webconn
This commit is contained in:
commit
8898066455
@ -1,12 +1,9 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.6.2
|
- 1.6.3
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
# Decrypts a script that installs an authenticated cookie
|
# Decrypts a script that installs an authenticated cookie
|
||||||
# for git to use when cloning from googlesource.com.
|
# for git to use when cloning from googlesource.com.
|
||||||
@ -24,7 +21,7 @@ script:
|
|||||||
- diff <(echo -n) <(gofmt -s -d .)
|
- diff <(echo -n) <(gofmt -s -d .)
|
||||||
- ineffassign .
|
- ineffassign .
|
||||||
- go vet ./...
|
- go vet ./...
|
||||||
- go test ./...
|
- go test -race ./...
|
||||||
|
|
||||||
after_script:
|
after_script:
|
||||||
- golint ./...
|
- golint ./...
|
||||||
|
@ -6,12 +6,11 @@ clone_folder: c:\gopath\src\github.com\mholt\caddy
|
|||||||
|
|
||||||
environment:
|
environment:
|
||||||
GOPATH: c:\gopath
|
GOPATH: c:\gopath
|
||||||
CGO_ENABLED: 0
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- rmdir c:\go /s /q
|
- rmdir c:\go /s /q
|
||||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.6.2.windows-amd64.zip
|
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.6.3.windows-amd64.zip
|
||||||
- 7z x go1.6.2.windows-amd64.zip -y -oC:\ > NUL
|
- 7z x go1.6.3.windows-amd64.zip -y -oC:\ > NUL
|
||||||
- go version
|
- go version
|
||||||
- go env
|
- go env
|
||||||
- go get -t ./...
|
- go get -t ./...
|
||||||
@ -23,7 +22,7 @@ build: off
|
|||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- go vet ./...
|
- go vet ./...
|
||||||
- go test ./...
|
- go test -race ./...
|
||||||
- ineffassign .
|
- ineffassign .
|
||||||
|
|
||||||
after_test:
|
after_test:
|
||||||
|
20
caddy.go
20
caddy.go
@ -21,8 +21,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -725,24 +723,6 @@ func IsLoopback(addr string) bool {
|
|||||||
strings.HasPrefix(host, "127.")
|
strings.HasPrefix(host, "127.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkFdlimit issues a warning if the OS limit for
|
|
||||||
// max file descriptors is below a recommended minimum.
|
|
||||||
func checkFdlimit() {
|
|
||||||
const min = 8192
|
|
||||||
|
|
||||||
// Warn if ulimit is too low for production sites
|
|
||||||
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
|
||||||
out, err := exec.Command("sh", "-c", "ulimit -n").Output() // use sh because ulimit isn't in Linux $PATH
|
|
||||||
if err == nil {
|
|
||||||
lim, err := strconv.Atoi(string(bytes.TrimSpace(out)))
|
|
||||||
if err == nil && lim < min {
|
|
||||||
fmt.Printf("WARNING: File descriptor limit %d is too low for production servers. "+
|
|
||||||
"At least %d is recommended. Fix with \"ulimit -n %d\".\n", lim, min, min)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upgrade re-launches the process, preserving the listeners
|
// Upgrade re-launches the process, preserving the listeners
|
||||||
// for a graceful restart. It does NOT load new configuration;
|
// for a graceful restart. It does NOT load new configuration;
|
||||||
// it only starts the process anew with a fresh binary.
|
// it only starts the process anew with a fresh binary.
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"hash/fnv"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,20 +14,21 @@ type HostPool []*UpstreamHost
|
|||||||
|
|
||||||
// Policy decides how a host will be selected from a pool.
|
// Policy decides how a host will be selected from a pool.
|
||||||
type Policy interface {
|
type Policy interface {
|
||||||
Select(pool HostPool) *UpstreamHost
|
Select(pool HostPool, r *http.Request) *UpstreamHost
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterPolicy("random", func() Policy { return &Random{} })
|
RegisterPolicy("random", func() Policy { return &Random{} })
|
||||||
RegisterPolicy("least_conn", func() Policy { return &LeastConn{} })
|
RegisterPolicy("least_conn", func() Policy { return &LeastConn{} })
|
||||||
RegisterPolicy("round_robin", func() Policy { return &RoundRobin{} })
|
RegisterPolicy("round_robin", func() Policy { return &RoundRobin{} })
|
||||||
|
RegisterPolicy("ip_hash", func() Policy { return &IPHash{} })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Random is a policy that selects up hosts from a pool at random.
|
// Random is a policy that selects up hosts from a pool at random.
|
||||||
type Random struct{}
|
type Random struct{}
|
||||||
|
|
||||||
// Select selects an up host at random from the specified pool.
|
// Select selects an up host at random from the specified pool.
|
||||||
func (r *Random) Select(pool HostPool) *UpstreamHost {
|
func (r *Random) Select(pool HostPool, request *http.Request) *UpstreamHost {
|
||||||
|
|
||||||
// Because the number of available hosts isn't known
|
// Because the number of available hosts isn't known
|
||||||
// up front, the host is selected via reservoir sampling
|
// up front, the host is selected via reservoir sampling
|
||||||
@ -53,7 +57,7 @@ type LeastConn struct{}
|
|||||||
// Select selects the up host with the least number of connections in the
|
// Select selects the up host with the least number of connections in the
|
||||||
// pool. If more than one host has the same least number of connections,
|
// pool. If more than one host has the same least number of connections,
|
||||||
// one of the hosts is chosen at random.
|
// one of the hosts is chosen at random.
|
||||||
func (r *LeastConn) Select(pool HostPool) *UpstreamHost {
|
func (r *LeastConn) Select(pool HostPool, request *http.Request) *UpstreamHost {
|
||||||
var bestHost *UpstreamHost
|
var bestHost *UpstreamHost
|
||||||
count := 0
|
count := 0
|
||||||
leastConn := int64(math.MaxInt64)
|
leastConn := int64(math.MaxInt64)
|
||||||
@ -86,7 +90,7 @@ type RoundRobin struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Select selects an up host from the pool using a round robin ordering scheme.
|
// Select selects an up host from the pool using a round robin ordering scheme.
|
||||||
func (r *RoundRobin) Select(pool HostPool) *UpstreamHost {
|
func (r *RoundRobin) Select(pool HostPool, request *http.Request) *UpstreamHost {
|
||||||
poolLen := uint32(len(pool))
|
poolLen := uint32(len(pool))
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
@ -100,3 +104,35 @@ func (r *RoundRobin) Select(pool HostPool) *UpstreamHost {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IPHash is a policy that selects hosts based on hashing the request ip
|
||||||
|
type IPHash struct{}
|
||||||
|
|
||||||
|
func hash(s string) uint32 {
|
||||||
|
h := fnv.New32a()
|
||||||
|
h.Write([]byte(s))
|
||||||
|
return h.Sum32()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select selects an up host from the pool using a round robin ordering scheme.
|
||||||
|
func (r *IPHash) Select(pool HostPool, request *http.Request) *UpstreamHost {
|
||||||
|
poolLen := uint32(len(pool))
|
||||||
|
clientIP, _, err := net.SplitHostPort(request.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
clientIP = request.RemoteAddr
|
||||||
|
}
|
||||||
|
hash := hash(clientIP)
|
||||||
|
for {
|
||||||
|
if poolLen == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
index := hash % poolLen
|
||||||
|
host := pool[index]
|
||||||
|
if host.Available() {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
pool = append(pool[:index], pool[index+1:]...)
|
||||||
|
poolLen--
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -21,7 +21,7 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
type customPolicy struct{}
|
type customPolicy struct{}
|
||||||
|
|
||||||
func (r *customPolicy) Select(pool HostPool) *UpstreamHost {
|
func (r *customPolicy) Select(pool HostPool, request *http.Request) *UpstreamHost {
|
||||||
return pool[0]
|
return pool[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,37 +43,39 @@ func testPool() HostPool {
|
|||||||
func TestRoundRobinPolicy(t *testing.T) {
|
func TestRoundRobinPolicy(t *testing.T) {
|
||||||
pool := testPool()
|
pool := testPool()
|
||||||
rrPolicy := &RoundRobin{}
|
rrPolicy := &RoundRobin{}
|
||||||
h := rrPolicy.Select(pool)
|
request, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
|
||||||
|
h := rrPolicy.Select(pool, request)
|
||||||
// First selected host is 1, because counter starts at 0
|
// First selected host is 1, because counter starts at 0
|
||||||
// and increments before host is selected
|
// and increments before host is selected
|
||||||
if h != pool[1] {
|
if h != pool[1] {
|
||||||
t.Error("Expected first round robin host to be second host in the pool.")
|
t.Error("Expected first round robin host to be second host in the pool.")
|
||||||
}
|
}
|
||||||
h = rrPolicy.Select(pool)
|
h = rrPolicy.Select(pool, request)
|
||||||
if h != pool[2] {
|
if h != pool[2] {
|
||||||
t.Error("Expected second round robin host to be third host in the pool.")
|
t.Error("Expected second round robin host to be third host in the pool.")
|
||||||
}
|
}
|
||||||
h = rrPolicy.Select(pool)
|
h = rrPolicy.Select(pool, request)
|
||||||
if h != pool[0] {
|
if h != pool[0] {
|
||||||
t.Error("Expected third round robin host to be first host in the pool.")
|
t.Error("Expected third round robin host to be first host in the pool.")
|
||||||
}
|
}
|
||||||
// mark host as down
|
// mark host as down
|
||||||
pool[1].Unhealthy = true
|
pool[1].Unhealthy = true
|
||||||
h = rrPolicy.Select(pool)
|
h = rrPolicy.Select(pool, request)
|
||||||
if h != pool[2] {
|
if h != pool[2] {
|
||||||
t.Error("Expected to skip down host.")
|
t.Error("Expected to skip down host.")
|
||||||
}
|
}
|
||||||
// mark host as up
|
// mark host as up
|
||||||
pool[1].Unhealthy = false
|
pool[1].Unhealthy = false
|
||||||
|
|
||||||
h = rrPolicy.Select(pool)
|
h = rrPolicy.Select(pool, request)
|
||||||
if h == pool[2] {
|
if h == pool[2] {
|
||||||
t.Error("Expected to balance evenly among healthy hosts")
|
t.Error("Expected to balance evenly among healthy hosts")
|
||||||
}
|
}
|
||||||
// mark host as full
|
// mark host as full
|
||||||
pool[1].Conns = 1
|
pool[1].Conns = 1
|
||||||
pool[1].MaxConns = 1
|
pool[1].MaxConns = 1
|
||||||
h = rrPolicy.Select(pool)
|
h = rrPolicy.Select(pool, request)
|
||||||
if h != pool[2] {
|
if h != pool[2] {
|
||||||
t.Error("Expected to skip full host.")
|
t.Error("Expected to skip full host.")
|
||||||
}
|
}
|
||||||
@ -82,14 +84,16 @@ func TestRoundRobinPolicy(t *testing.T) {
|
|||||||
func TestLeastConnPolicy(t *testing.T) {
|
func TestLeastConnPolicy(t *testing.T) {
|
||||||
pool := testPool()
|
pool := testPool()
|
||||||
lcPolicy := &LeastConn{}
|
lcPolicy := &LeastConn{}
|
||||||
|
request, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
|
||||||
pool[0].Conns = 10
|
pool[0].Conns = 10
|
||||||
pool[1].Conns = 10
|
pool[1].Conns = 10
|
||||||
h := lcPolicy.Select(pool)
|
h := lcPolicy.Select(pool, request)
|
||||||
if h != pool[2] {
|
if h != pool[2] {
|
||||||
t.Error("Expected least connection host to be third host.")
|
t.Error("Expected least connection host to be third host.")
|
||||||
}
|
}
|
||||||
pool[2].Conns = 100
|
pool[2].Conns = 100
|
||||||
h = lcPolicy.Select(pool)
|
h = lcPolicy.Select(pool, request)
|
||||||
if h != pool[0] && h != pool[1] {
|
if h != pool[0] && h != pool[1] {
|
||||||
t.Error("Expected least connection host to be first or second host.")
|
t.Error("Expected least connection host to be first or second host.")
|
||||||
}
|
}
|
||||||
@ -98,8 +102,127 @@ func TestLeastConnPolicy(t *testing.T) {
|
|||||||
func TestCustomPolicy(t *testing.T) {
|
func TestCustomPolicy(t *testing.T) {
|
||||||
pool := testPool()
|
pool := testPool()
|
||||||
customPolicy := &customPolicy{}
|
customPolicy := &customPolicy{}
|
||||||
h := customPolicy.Select(pool)
|
request, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
|
||||||
|
h := customPolicy.Select(pool, request)
|
||||||
if h != pool[0] {
|
if h != pool[0] {
|
||||||
t.Error("Expected custom policy host to be the first host.")
|
t.Error("Expected custom policy host to be the first host.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIPHashPolicy(t *testing.T) {
|
||||||
|
pool := testPool()
|
||||||
|
ipHash := &IPHash{}
|
||||||
|
request, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
// We should be able to predict where every request is routed.
|
||||||
|
request.RemoteAddr = "172.0.0.1:80"
|
||||||
|
h := ipHash.Select(pool, request)
|
||||||
|
if h != pool[1] {
|
||||||
|
t.Error("Expected ip hash policy host to be the second host.")
|
||||||
|
}
|
||||||
|
request.RemoteAddr = "172.0.0.2:80"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[1] {
|
||||||
|
t.Error("Expected ip hash policy host to be the second host.")
|
||||||
|
}
|
||||||
|
request.RemoteAddr = "172.0.0.3:80"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[2] {
|
||||||
|
t.Error("Expected ip hash policy host to be the third host.")
|
||||||
|
}
|
||||||
|
request.RemoteAddr = "172.0.0.4:80"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[1] {
|
||||||
|
t.Error("Expected ip hash policy host to be the second host.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should get the same results without a port
|
||||||
|
request.RemoteAddr = "172.0.0.1"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[1] {
|
||||||
|
t.Error("Expected ip hash policy host to be the second host.")
|
||||||
|
}
|
||||||
|
request.RemoteAddr = "172.0.0.2"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[1] {
|
||||||
|
t.Error("Expected ip hash policy host to be the second host.")
|
||||||
|
}
|
||||||
|
request.RemoteAddr = "172.0.0.3"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[2] {
|
||||||
|
t.Error("Expected ip hash policy host to be the third host.")
|
||||||
|
}
|
||||||
|
request.RemoteAddr = "172.0.0.4"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[1] {
|
||||||
|
t.Error("Expected ip hash policy host to be the second host.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should get a healthy host if the original host is unhealthy and a
|
||||||
|
// healthy host is available
|
||||||
|
request.RemoteAddr = "172.0.0.1"
|
||||||
|
pool[1].Unhealthy = true
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[0] {
|
||||||
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
|
}
|
||||||
|
|
||||||
|
request.RemoteAddr = "172.0.0.2"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[1] {
|
||||||
|
t.Error("Expected ip hash policy host to be the second host.")
|
||||||
|
}
|
||||||
|
pool[1].Unhealthy = false
|
||||||
|
|
||||||
|
request.RemoteAddr = "172.0.0.3"
|
||||||
|
pool[2].Unhealthy = true
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[0] {
|
||||||
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
|
}
|
||||||
|
request.RemoteAddr = "172.0.0.4"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[0] {
|
||||||
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should be able to resize the host pool and still be able to predict
|
||||||
|
// where a request will be routed with the same IP's used above
|
||||||
|
pool = []*UpstreamHost{
|
||||||
|
{
|
||||||
|
Name: workableServer.URL, // this should resolve (healthcheck test)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "http://localhost:99998", // this shouldn't
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pool = HostPool(pool)
|
||||||
|
request.RemoteAddr = "172.0.0.1:80"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[0] {
|
||||||
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
|
}
|
||||||
|
request.RemoteAddr = "172.0.0.2:80"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[1] {
|
||||||
|
t.Error("Expected ip hash policy host to be the second host.")
|
||||||
|
}
|
||||||
|
request.RemoteAddr = "172.0.0.3:80"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[0] {
|
||||||
|
t.Error("Expected ip hash policy host to be the first host.")
|
||||||
|
}
|
||||||
|
request.RemoteAddr = "172.0.0.4:80"
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != pool[1] {
|
||||||
|
t.Error("Expected ip hash policy host to be the second host.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should get nil when there are no healthy hosts
|
||||||
|
pool[0].Unhealthy = true
|
||||||
|
pool[1].Unhealthy = true
|
||||||
|
h = ipHash.Select(pool, request)
|
||||||
|
if h != nil {
|
||||||
|
t.Error("Expected ip hash policy host to be nil.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -27,7 +27,7 @@ type Upstream interface {
|
|||||||
// The path this upstream host should be routed on
|
// The path this upstream host should be routed on
|
||||||
From() string
|
From() string
|
||||||
// Selects an upstream host to be routed to.
|
// Selects an upstream host to be routed to.
|
||||||
Select() *UpstreamHost
|
Select(*http.Request) *UpstreamHost
|
||||||
// Checks if subpath is not an ignored path
|
// Checks if subpath is not an ignored path
|
||||||
AllowedPath(string) bool
|
AllowedPath(string) bool
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
|||||||
// hosts until timeout (or until we get a nil host).
|
// hosts until timeout (or until we get a nil host).
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
for time.Now().Sub(start) < tryDuration {
|
for time.Now().Sub(start) < tryDuration {
|
||||||
host := upstream.Select()
|
host := upstream.Select(r)
|
||||||
if host == nil {
|
if host == nil {
|
||||||
return http.StatusBadGateway, errUnreachable
|
return http.StatusBadGateway, errUnreachable
|
||||||
}
|
}
|
||||||
@ -108,7 +108,7 @@ func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
|||||||
if nameURL, err := url.Parse(host.Name); err == nil {
|
if nameURL, err := url.Parse(host.Name); err == nil {
|
||||||
outreq.Host = nameURL.Host
|
outreq.Host = nameURL.Host
|
||||||
if proxy == nil {
|
if proxy == nil {
|
||||||
proxy = NewSingleHostReverseProxy(nameURL, host.WithoutPathPrefix, 0)
|
proxy = NewSingleHostReverseProxy(nameURL, host.WithoutPathPrefix, http.DefaultMaxIdleConnsPerHost)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use upstream credentials by default
|
// use upstream credentials by default
|
||||||
|
@ -362,9 +362,11 @@ func TestUpstreamHeadersUpdate(t *testing.T) {
|
|||||||
defer log.SetOutput(os.Stderr)
|
defer log.SetOutput(os.Stderr)
|
||||||
|
|
||||||
var actualHeaders http.Header
|
var actualHeaders http.Header
|
||||||
|
var actualHost string
|
||||||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write([]byte("Hello, client"))
|
w.Write([]byte("Hello, client"))
|
||||||
actualHeaders = r.Header
|
actualHeaders = r.Header
|
||||||
|
actualHost = r.Host
|
||||||
}))
|
}))
|
||||||
defer backend.Close()
|
defer backend.Close()
|
||||||
|
|
||||||
@ -376,6 +378,7 @@ func TestUpstreamHeadersUpdate(t *testing.T) {
|
|||||||
"+Add-Me": {"Add-Value"},
|
"+Add-Me": {"Add-Value"},
|
||||||
"-Remove-Me": {""},
|
"-Remove-Me": {""},
|
||||||
"Replace-Me": {"{hostname}"},
|
"Replace-Me": {"{hostname}"},
|
||||||
|
"Host": {"{>Host}"},
|
||||||
}
|
}
|
||||||
// set up proxy
|
// set up proxy
|
||||||
p := &Proxy{
|
p := &Proxy{
|
||||||
@ -390,10 +393,12 @@ func TestUpstreamHeadersUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
const expectHost = "example.com"
|
||||||
//add initial headers
|
//add initial headers
|
||||||
r.Header.Add("Merge-Me", "Initial")
|
r.Header.Add("Merge-Me", "Initial")
|
||||||
r.Header.Add("Remove-Me", "Remove-Value")
|
r.Header.Add("Remove-Me", "Remove-Value")
|
||||||
r.Header.Add("Replace-Me", "Replace-Value")
|
r.Header.Add("Replace-Me", "Replace-Value")
|
||||||
|
r.Header.Add("Host", expectHost)
|
||||||
|
|
||||||
p.ServeHTTP(w, r)
|
p.ServeHTTP(w, r)
|
||||||
|
|
||||||
@ -426,6 +431,10 @@ func TestUpstreamHeadersUpdate(t *testing.T) {
|
|||||||
t.Errorf("Request sent to upstream backend should replace value of %v header with %v. Instead value was %v", headerKey, headerValue, value)
|
t.Errorf("Request sent to upstream backend should replace value of %v header with %v. Instead value was %v", headerKey, headerValue, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if actualHost != expectHost {
|
||||||
|
t.Errorf("Request sent to upstream backend should have value of Host with %s, but got %s", expectHost, actualHost)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDownstreamHeadersUpdate(t *testing.T) {
|
func TestDownstreamHeadersUpdate(t *testing.T) {
|
||||||
@ -721,7 +730,7 @@ func newFakeUpstream(name string, insecure bool) *fakeUpstream {
|
|||||||
from: "/",
|
from: "/",
|
||||||
host: &UpstreamHost{
|
host: &UpstreamHost{
|
||||||
Name: name,
|
Name: name,
|
||||||
ReverseProxy: NewSingleHostReverseProxy(uri, "", 0),
|
ReverseProxy: NewSingleHostReverseProxy(uri, "", http.DefaultMaxIdleConnsPerHost),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if insecure {
|
if insecure {
|
||||||
@ -741,7 +750,7 @@ func (u *fakeUpstream) From() string {
|
|||||||
return u.from
|
return u.from
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *fakeUpstream) Select() *UpstreamHost {
|
func (u *fakeUpstream) Select(r *http.Request) *UpstreamHost {
|
||||||
if u.host == nil {
|
if u.host == nil {
|
||||||
uri, err := url.Parse(u.name)
|
uri, err := url.Parse(u.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -749,7 +758,7 @@ func (u *fakeUpstream) Select() *UpstreamHost {
|
|||||||
}
|
}
|
||||||
u.host = &UpstreamHost{
|
u.host = &UpstreamHost{
|
||||||
Name: u.name,
|
Name: u.name,
|
||||||
ReverseProxy: NewSingleHostReverseProxy(uri, u.without, 0),
|
ReverseProxy: NewSingleHostReverseProxy(uri, u.without, http.DefaultMaxIdleConnsPerHost),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return u.host
|
return u.host
|
||||||
@ -786,11 +795,11 @@ func (u *fakeWsUpstream) From() string {
|
|||||||
return "/"
|
return "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *fakeWsUpstream) Select() *UpstreamHost {
|
func (u *fakeWsUpstream) Select(r *http.Request) *UpstreamHost {
|
||||||
uri, _ := url.Parse(u.name)
|
uri, _ := url.Parse(u.name)
|
||||||
return &UpstreamHost{
|
return &UpstreamHost{
|
||||||
Name: u.name,
|
Name: u.name,
|
||||||
ReverseProxy: NewSingleHostReverseProxy(uri, u.without, 0),
|
ReverseProxy: NewSingleHostReverseProxy(uri, u.without, http.DefaultMaxIdleConnsPerHost),
|
||||||
UpstreamHeaders: http.Header{
|
UpstreamHeaders: http.Header{
|
||||||
"Connection": {"{>Connection}"},
|
"Connection": {"{>Connection}"},
|
||||||
"Upgrade": {"{>Upgrade}"}},
|
"Upgrade": {"{>Upgrade}"}},
|
||||||
|
@ -122,7 +122,10 @@ func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int) *
|
|||||||
rp.Transport = &http.Transport{
|
rp.Transport = &http.Transport{
|
||||||
Dial: socketDial(target.String()),
|
Dial: socketDial(target.String()),
|
||||||
}
|
}
|
||||||
} else if keepalive != 0 {
|
} else if keepalive != http.DefaultMaxIdleConnsPerHost {
|
||||||
|
// if keepalive is equal to the default,
|
||||||
|
// just use default transport, to avoid creating
|
||||||
|
// a brand new transport
|
||||||
rp.Transport = &http.Transport{
|
rp.Transport = &http.Transport{
|
||||||
Proxy: http.ProxyFromEnvironment,
|
Proxy: http.ProxyFromEnvironment,
|
||||||
Dial: (&net.Dialer{
|
Dial: (&net.Dialer{
|
||||||
@ -132,7 +135,7 @@ func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int) *
|
|||||||
TLSHandshakeTimeout: 10 * time.Second,
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
}
|
}
|
||||||
if keepalive < 0 {
|
if keepalive == 0 {
|
||||||
rp.Transport.(*http.Transport).DisableKeepAlives = true
|
rp.Transport.(*http.Transport).DisableKeepAlives = true
|
||||||
} else {
|
} else {
|
||||||
rp.Transport.(*http.Transport).MaxIdleConnsPerHost = keepalive
|
rp.Transport.(*http.Transport).MaxIdleConnsPerHost = keepalive
|
||||||
|
@ -55,6 +55,7 @@ func NewStaticUpstreams(c caddyfile.Dispenser) ([]Upstream, error) {
|
|||||||
FailTimeout: 10 * time.Second,
|
FailTimeout: 10 * time.Second,
|
||||||
MaxFails: 1,
|
MaxFails: 1,
|
||||||
MaxConns: 0,
|
MaxConns: 0,
|
||||||
|
KeepAlive: http.DefaultMaxIdleConnsPerHost,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.Args(&upstream.from) {
|
if !c.Args(&upstream.from) {
|
||||||
@ -321,6 +322,9 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if n < 0 {
|
||||||
|
return c.ArgErr()
|
||||||
|
}
|
||||||
u.KeepAlive = n
|
u.KeepAlive = n
|
||||||
default:
|
default:
|
||||||
return c.Errf("unknown property '%s'", c.Val())
|
return c.Errf("unknown property '%s'", c.Val())
|
||||||
@ -356,7 +360,7 @@ func (u *staticUpstream) HealthCheckWorker(stop chan struct{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *staticUpstream) Select() *UpstreamHost {
|
func (u *staticUpstream) Select(r *http.Request) *UpstreamHost {
|
||||||
pool := u.Hosts
|
pool := u.Hosts
|
||||||
if len(pool) == 1 {
|
if len(pool) == 1 {
|
||||||
if !pool[0].Available() {
|
if !pool[0].Available() {
|
||||||
@ -374,11 +378,10 @@ func (u *staticUpstream) Select() *UpstreamHost {
|
|||||||
if allUnavailable {
|
if allUnavailable {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.Policy == nil {
|
if u.Policy == nil {
|
||||||
return (&Random{}).Select(pool)
|
return (&Random{}).Select(pool, r)
|
||||||
}
|
}
|
||||||
return u.Policy.Select(pool)
|
return u.Policy.Select(pool, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *staticUpstream) AllowedPath(requestPath string) bool {
|
func (u *staticUpstream) AllowedPath(requestPath string) bool {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/mholt/caddy/caddyfile"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mholt/caddy/caddyfile"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewHost(t *testing.T) {
|
func TestNewHost(t *testing.T) {
|
||||||
@ -72,14 +72,15 @@ func TestSelect(t *testing.T) {
|
|||||||
FailTimeout: 10 * time.Second,
|
FailTimeout: 10 * time.Second,
|
||||||
MaxFails: 1,
|
MaxFails: 1,
|
||||||
}
|
}
|
||||||
|
r, _ := http.NewRequest("GET", "/", nil)
|
||||||
upstream.Hosts[0].Unhealthy = true
|
upstream.Hosts[0].Unhealthy = true
|
||||||
upstream.Hosts[1].Unhealthy = true
|
upstream.Hosts[1].Unhealthy = true
|
||||||
upstream.Hosts[2].Unhealthy = true
|
upstream.Hosts[2].Unhealthy = true
|
||||||
if h := upstream.Select(); h != nil {
|
if h := upstream.Select(r); h != nil {
|
||||||
t.Error("Expected select to return nil as all host are down")
|
t.Error("Expected select to return nil as all host are down")
|
||||||
}
|
}
|
||||||
upstream.Hosts[2].Unhealthy = false
|
upstream.Hosts[2].Unhealthy = false
|
||||||
if h := upstream.Select(); h == nil {
|
if h := upstream.Select(r); h == nil {
|
||||||
t.Error("Expected select to not return nil")
|
t.Error("Expected select to not return nil")
|
||||||
}
|
}
|
||||||
upstream.Hosts[0].Conns = 1
|
upstream.Hosts[0].Conns = 1
|
||||||
@ -88,11 +89,11 @@ func TestSelect(t *testing.T) {
|
|||||||
upstream.Hosts[1].MaxConns = 1
|
upstream.Hosts[1].MaxConns = 1
|
||||||
upstream.Hosts[2].Conns = 1
|
upstream.Hosts[2].Conns = 1
|
||||||
upstream.Hosts[2].MaxConns = 1
|
upstream.Hosts[2].MaxConns = 1
|
||||||
if h := upstream.Select(); h != nil {
|
if h := upstream.Select(r); h != nil {
|
||||||
t.Error("Expected select to return nil as all hosts are full")
|
t.Error("Expected select to return nil as all hosts are full")
|
||||||
}
|
}
|
||||||
upstream.Hosts[2].Conns = 0
|
upstream.Hosts[2].Conns = 0
|
||||||
if h := upstream.Select(); h == nil {
|
if h := upstream.Select(r); h == nil {
|
||||||
t.Error("Expected select to not return nil")
|
t.Error("Expected select to not return nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,6 +189,7 @@ func TestParseBlockHealthCheck(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseBlock(t *testing.T) {
|
func TestParseBlock(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", "/", nil)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
config string
|
config string
|
||||||
}{
|
}{
|
||||||
@ -207,7 +209,7 @@ func TestParseBlock(t *testing.T) {
|
|||||||
t.Error("Expected no error. Got:", err.Error())
|
t.Error("Expected no error. Got:", err.Error())
|
||||||
}
|
}
|
||||||
for _, upstream := range upstreams {
|
for _, upstream := range upstreams {
|
||||||
headers := upstream.Select().UpstreamHeaders
|
headers := upstream.Select(r).UpstreamHeaders
|
||||||
|
|
||||||
if _, ok := headers["Host"]; !ok {
|
if _, ok := headers["Host"]; !ok {
|
||||||
t.Errorf("Test %d: Could not find the Host header", i+1)
|
t.Errorf("Test %d: Could not find the Host header", i+1)
|
||||||
|
@ -79,19 +79,22 @@ func PrivateKeyBytes(key crypto.PrivateKey) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStandaloneTLSTicketKeyRotation(t *testing.T) {
|
func TestStandaloneTLSTicketKeyRotation(t *testing.T) {
|
||||||
|
type syncPkt struct {
|
||||||
|
ticketKey [32]byte
|
||||||
|
keysInUse int
|
||||||
|
}
|
||||||
|
|
||||||
tlsGovChan := make(chan struct{})
|
tlsGovChan := make(chan struct{})
|
||||||
defer close(tlsGovChan)
|
defer close(tlsGovChan)
|
||||||
callSync := make(chan bool, 1)
|
callSync := make(chan *syncPkt, 1)
|
||||||
defer close(callSync)
|
defer close(callSync)
|
||||||
|
|
||||||
oldHook := setSessionTicketKeysTestHook
|
oldHook := setSessionTicketKeysTestHook
|
||||||
defer func() {
|
defer func() {
|
||||||
setSessionTicketKeysTestHook = oldHook
|
setSessionTicketKeysTestHook = oldHook
|
||||||
}()
|
}()
|
||||||
var keysInUse [][32]byte
|
|
||||||
setSessionTicketKeysTestHook = func(keys [][32]byte) [][32]byte {
|
setSessionTicketKeysTestHook = func(keys [][32]byte) [][32]byte {
|
||||||
keysInUse = keys
|
callSync <- &syncPkt{keys[0], len(keys)}
|
||||||
callSync <- true
|
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,17 +107,17 @@ func TestStandaloneTLSTicketKeyRotation(t *testing.T) {
|
|||||||
var lastTicketKey [32]byte
|
var lastTicketKey [32]byte
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-callSync:
|
case pkt := <-callSync:
|
||||||
if lastTicketKey == keysInUse[0] {
|
if lastTicketKey == pkt.ticketKey {
|
||||||
close(tlsGovChan)
|
close(tlsGovChan)
|
||||||
t.Errorf("The same TLS ticket key has been used again (not rotated): %x.", lastTicketKey)
|
t.Errorf("The same TLS ticket key has been used again (not rotated): %x.", lastTicketKey)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lastTicketKey = keysInUse[0]
|
lastTicketKey = pkt.ticketKey
|
||||||
rounds++
|
rounds++
|
||||||
if rounds <= NumTickets && len(keysInUse) != rounds {
|
if rounds <= NumTickets && pkt.keysInUse != rounds {
|
||||||
close(tlsGovChan)
|
close(tlsGovChan)
|
||||||
t.Errorf("Expected TLS ticket keys in use: %d; Got instead: %d.", rounds, len(keysInUse))
|
t.Errorf("Expected TLS ticket keys in use: %d; Got instead: %d.", rounds, pkt.keysInUse)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.SessionTicketsDisabled == true {
|
if c.SessionTicketsDisabled == true {
|
||||||
|
11
dist/init/linux-sysvinit/caddy
vendored
11
dist/init/linux-sysvinit/caddy
vendored
@ -20,9 +20,9 @@ DAEMONUSER=www-data
|
|||||||
PIDFILE=/var/run/$NAME.pid
|
PIDFILE=/var/run/$NAME.pid
|
||||||
LOGFILE=/var/log/$NAME.log
|
LOGFILE=/var/log/$NAME.log
|
||||||
CONFIGFILE=/etc/caddy/Caddyfile
|
CONFIGFILE=/etc/caddy/Caddyfile
|
||||||
DAEMONOPTS="-agree=true --pidfile=$PIDFILE log=$LOGFILE -conf=$CONFIGFILE"
|
DAEMONOPTS="-agree=true -pidfile=$PIDFILE -log=$LOGFILE -conf=$CONFIGFILE"
|
||||||
|
|
||||||
USERBIND="$(which setcap) cap_net_bind_service=+ep"
|
USERBIND="setcap cap_net_bind_service=+ep"
|
||||||
STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}"
|
STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}"
|
||||||
|
|
||||||
test -x $DAEMON || exit 0
|
test -x $DAEMON || exit 0
|
||||||
@ -37,12 +37,13 @@ ulimit -n 8192
|
|||||||
start() {
|
start() {
|
||||||
$USERBIND $DAEMON
|
$USERBIND $DAEMON
|
||||||
start-stop-daemon --start --quiet --make-pidfile --pidfile $PIDFILE \
|
start-stop-daemon --start --quiet --make-pidfile --pidfile $PIDFILE \
|
||||||
--background --chuid $DAEMONUSER --exec $DAEMON -- $DAEMONOPTS
|
--background --chuid $DAEMONUSER --oknodo --exec $DAEMON -- $DAEMONOPTS
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
start-stop-daemon --stop --quiet --remove-pidfile --pidfile $PIDFILE \
|
start-stop-daemon --stop --quiet --pidfile $PIDFILE --retry=$STOP_SCHEDULE \
|
||||||
--retry=$STOP_SCHEDULE --name $NAME --oknodo
|
--name $NAME --oknodo
|
||||||
|
rm -f $PIDFILE
|
||||||
}
|
}
|
||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
|
23
rlimit_posix.go
Normal file
23
rlimit_posix.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package caddy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkFdlimit issues a warning if the OS limit for
|
||||||
|
// max file descriptors is below a recommended minimum.
|
||||||
|
func checkFdlimit() {
|
||||||
|
const min = 8192
|
||||||
|
|
||||||
|
// Warn if ulimit is too low for production sites
|
||||||
|
rlimit := &syscall.Rlimit{}
|
||||||
|
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimit)
|
||||||
|
if err == nil && rlimit.Cur < min {
|
||||||
|
fmt.Printf("WARNING: File descriptor limit %d is too low for production servers. "+
|
||||||
|
"At least %d is recommended. Fix with \"ulimit -n %d\".\n", rlimit.Cur, min, min)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
6
rlimit_windows.go
Normal file
6
rlimit_windows.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package caddy
|
||||||
|
|
||||||
|
// checkFdlimit issues a warning if the OS limit for
|
||||||
|
// max file descriptors is below a recommended minimum.
|
||||||
|
func checkFdlimit() {
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user