mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-26 18:30:54 +08:00
da8b7fe58f
* caddyhttp: Honor grace period in background This avoids blocking during config reloads. * Don't quit process until servers shut down * Make tests more likely to pass on fast CI (#5045) * caddyhttp: Even faster shutdowns Simultaneously shut down all HTTP servers, rather than one at a time. In practice there usually won't be more than 1 that lingers. But this code ensures that they all Shutdown() in their own goroutine and then we wait for them at the end (if exiting). We also wait for them to start up so we can be fairly confident the shutdowns have begun; i.e. old servers no longer accepting new connections. * Fix comment typo * Pull functions out of loop, for readability
498 lines
8.9 KiB
Go
498 lines
8.9 KiB
Go
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/caddyserver/caddy/v2/caddytest"
|
|
)
|
|
|
|
func TestSRVReverseProxy(t *testing.T) {
|
|
tester := caddytest.NewTester(t)
|
|
tester.InitServer(`
|
|
{
|
|
"apps": {
|
|
"http": {
|
|
"grace_period": 1,
|
|
"servers": {
|
|
"srv0": {
|
|
"listen": [
|
|
":8080"
|
|
],
|
|
"routes": [
|
|
{
|
|
"handle": [
|
|
{
|
|
"handler": "reverse_proxy",
|
|
"upstreams": [
|
|
{
|
|
"lookup_srv": "srv.host.service.consul"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`, "json")
|
|
}
|
|
|
|
func TestSRVWithDial(t *testing.T) {
|
|
caddytest.AssertLoadError(t, `
|
|
{
|
|
"apps": {
|
|
"http": {
|
|
"grace_period": 1,
|
|
"servers": {
|
|
"srv0": {
|
|
"listen": [
|
|
":8080"
|
|
],
|
|
"routes": [
|
|
{
|
|
"handle": [
|
|
{
|
|
"handler": "reverse_proxy",
|
|
"upstreams": [
|
|
{
|
|
"dial": "tcp/address.to.upstream:80",
|
|
"lookup_srv": "srv.host.service.consul"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`, "json", `upstream: specifying dial address is incompatible with lookup_srv: 0: {\"dial\": \"tcp/address.to.upstream:80\", \"lookup_srv\": \"srv.host.service.consul\"}`)
|
|
}
|
|
|
|
func TestDialWithPlaceholderUnix(t *testing.T) {
|
|
|
|
if runtime.GOOS == "windows" {
|
|
t.SkipNow()
|
|
}
|
|
|
|
f, err := os.CreateTemp("", "*.sock")
|
|
if err != nil {
|
|
t.Errorf("failed to create TempFile: %s", err)
|
|
return
|
|
}
|
|
// a hack to get a file name within a valid path to use as socket
|
|
socketName := f.Name()
|
|
os.Remove(f.Name())
|
|
|
|
server := http.Server{
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
w.Write([]byte("Hello, World!"))
|
|
}),
|
|
}
|
|
|
|
unixListener, err := net.Listen("unix", socketName)
|
|
if err != nil {
|
|
t.Errorf("failed to listen on the socket: %s", err)
|
|
return
|
|
}
|
|
go server.Serve(unixListener)
|
|
t.Cleanup(func() {
|
|
server.Close()
|
|
})
|
|
runtime.Gosched() // Allow other goroutines to run
|
|
|
|
tester := caddytest.NewTester(t)
|
|
tester.InitServer(`
|
|
{
|
|
"apps": {
|
|
"http": {
|
|
"grace_period": 1,
|
|
"servers": {
|
|
"srv0": {
|
|
"listen": [
|
|
":8080"
|
|
],
|
|
"routes": [
|
|
{
|
|
"handle": [
|
|
{
|
|
"handler": "reverse_proxy",
|
|
"upstreams": [
|
|
{
|
|
"dial": "unix/{http.request.header.X-Caddy-Upstream-Dial}"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`, "json")
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", nil)
|
|
if err != nil {
|
|
t.Fail()
|
|
return
|
|
}
|
|
req.Header.Set("X-Caddy-Upstream-Dial", socketName)
|
|
tester.AssertResponse(req, 200, "Hello, World!")
|
|
}
|
|
|
|
func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
|
|
tester := caddytest.NewTester(t)
|
|
tester.InitServer(`
|
|
{
|
|
"apps": {
|
|
"http": {
|
|
"grace_period": 1,
|
|
"servers": {
|
|
"srv0": {
|
|
"listen": [
|
|
":8080"
|
|
],
|
|
"routes": [
|
|
{
|
|
"match": [
|
|
{
|
|
"host": [
|
|
"localhost"
|
|
]
|
|
}
|
|
],
|
|
"handle": [
|
|
{
|
|
"handler": "static_response",
|
|
"body": "Hello, World!"
|
|
}
|
|
],
|
|
"terminal": true
|
|
}
|
|
],
|
|
"automatic_https": {
|
|
"skip": [
|
|
"localhost"
|
|
]
|
|
}
|
|
},
|
|
"srv1": {
|
|
"listen": [
|
|
":9080"
|
|
],
|
|
"routes": [
|
|
{
|
|
"match": [
|
|
{
|
|
"host": [
|
|
"localhost"
|
|
]
|
|
}
|
|
],
|
|
"handle": [
|
|
{
|
|
|
|
"handler": "reverse_proxy",
|
|
"upstreams": [
|
|
{
|
|
"dial": "{http.request.header.X-Caddy-Upstream-Dial}"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"terminal": true
|
|
}
|
|
],
|
|
"automatic_https": {
|
|
"skip": [
|
|
"localhost"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`, "json")
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
|
|
if err != nil {
|
|
t.Fail()
|
|
return
|
|
}
|
|
req.Header.Set("X-Caddy-Upstream-Dial", "localhost:8080")
|
|
tester.AssertResponse(req, 200, "Hello, World!")
|
|
}
|
|
|
|
func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
|
|
tester := caddytest.NewTester(t)
|
|
tester.InitServer(`
|
|
{
|
|
"apps": {
|
|
"http": {
|
|
"grace_period": 1,
|
|
"servers": {
|
|
"srv0": {
|
|
"listen": [
|
|
":8080"
|
|
],
|
|
"routes": [
|
|
{
|
|
"match": [
|
|
{
|
|
"host": [
|
|
"localhost"
|
|
]
|
|
}
|
|
],
|
|
"handle": [
|
|
{
|
|
"handler": "static_response",
|
|
"body": "Hello, World!"
|
|
}
|
|
],
|
|
"terminal": true
|
|
}
|
|
],
|
|
"automatic_https": {
|
|
"skip": [
|
|
"localhost"
|
|
]
|
|
}
|
|
},
|
|
"srv1": {
|
|
"listen": [
|
|
":9080"
|
|
],
|
|
"routes": [
|
|
{
|
|
"match": [
|
|
{
|
|
"host": [
|
|
"localhost"
|
|
]
|
|
}
|
|
],
|
|
"handle": [
|
|
{
|
|
|
|
"handler": "reverse_proxy",
|
|
"upstreams": [
|
|
{
|
|
"dial": "tcp/{http.request.header.X-Caddy-Upstream-Dial}:8080"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"terminal": true
|
|
}
|
|
],
|
|
"automatic_https": {
|
|
"skip": [
|
|
"localhost"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`, "json")
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
|
|
if err != nil {
|
|
t.Fail()
|
|
return
|
|
}
|
|
req.Header.Set("X-Caddy-Upstream-Dial", "localhost")
|
|
tester.AssertResponse(req, 200, "Hello, World!")
|
|
}
|
|
|
|
func TestSRVWithActiveHealthcheck(t *testing.T) {
|
|
caddytest.AssertLoadError(t, `
|
|
{
|
|
"apps": {
|
|
"http": {
|
|
"grace_period": 1,
|
|
"servers": {
|
|
"srv0": {
|
|
"listen": [
|
|
":8080"
|
|
],
|
|
"routes": [
|
|
{
|
|
"handle": [
|
|
{
|
|
"handler": "reverse_proxy",
|
|
"health_checks": {
|
|
"active": {
|
|
"path": "/ok"
|
|
}
|
|
},
|
|
"upstreams": [
|
|
{
|
|
"lookup_srv": "srv.host.service.consul"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`, "json", `upstream: lookup_srv is incompatible with active health checks: 0: {\"dial\": \"\", \"lookup_srv\": \"srv.host.service.consul\"}`)
|
|
}
|
|
|
|
func TestReverseProxyHealthCheck(t *testing.T) {
|
|
tester := caddytest.NewTester(t)
|
|
tester.InitServer(`
|
|
{
|
|
http_port 9080
|
|
https_port 9443
|
|
}
|
|
http://localhost:2020 {
|
|
respond "Hello, World!"
|
|
}
|
|
http://localhost:2021 {
|
|
respond "ok"
|
|
}
|
|
http://localhost:9080 {
|
|
reverse_proxy {
|
|
to localhost:2020
|
|
|
|
health_uri /health
|
|
health_port 2021
|
|
health_interval 2s
|
|
health_timeout 5s
|
|
}
|
|
}
|
|
`, "caddyfile")
|
|
|
|
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
|
|
}
|
|
|
|
func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.SkipNow()
|
|
}
|
|
tester := caddytest.NewTester(t)
|
|
f, err := os.CreateTemp("", "*.sock")
|
|
if err != nil {
|
|
t.Errorf("failed to create TempFile: %s", err)
|
|
return
|
|
}
|
|
// a hack to get a file name within a valid path to use as socket
|
|
socketName := f.Name()
|
|
os.Remove(f.Name())
|
|
|
|
server := http.Server{
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if strings.HasPrefix(req.URL.Path, "/health") {
|
|
w.Write([]byte("ok"))
|
|
return
|
|
}
|
|
w.Write([]byte("Hello, World!"))
|
|
}),
|
|
}
|
|
|
|
unixListener, err := net.Listen("unix", socketName)
|
|
if err != nil {
|
|
t.Errorf("failed to listen on the socket: %s", err)
|
|
return
|
|
}
|
|
go server.Serve(unixListener)
|
|
t.Cleanup(func() {
|
|
server.Close()
|
|
})
|
|
runtime.Gosched() // Allow other goroutines to run
|
|
|
|
tester.InitServer(fmt.Sprintf(`
|
|
{
|
|
http_port 9080
|
|
https_port 9443
|
|
}
|
|
http://localhost:9080 {
|
|
reverse_proxy {
|
|
to unix/%s
|
|
|
|
health_uri /health
|
|
health_port 2021
|
|
health_interval 2s
|
|
health_timeout 5s
|
|
}
|
|
}
|
|
`, socketName), "caddyfile")
|
|
|
|
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
|
|
}
|
|
|
|
func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.SkipNow()
|
|
}
|
|
tester := caddytest.NewTester(t)
|
|
f, err := os.CreateTemp("", "*.sock")
|
|
if err != nil {
|
|
t.Errorf("failed to create TempFile: %s", err)
|
|
return
|
|
}
|
|
// a hack to get a file name within a valid path to use as socket
|
|
socketName := f.Name()
|
|
os.Remove(f.Name())
|
|
|
|
server := http.Server{
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if strings.HasPrefix(req.URL.Path, "/health") {
|
|
w.Write([]byte("ok"))
|
|
return
|
|
}
|
|
w.Write([]byte("Hello, World!"))
|
|
}),
|
|
}
|
|
|
|
unixListener, err := net.Listen("unix", socketName)
|
|
if err != nil {
|
|
t.Errorf("failed to listen on the socket: %s", err)
|
|
return
|
|
}
|
|
go server.Serve(unixListener)
|
|
t.Cleanup(func() {
|
|
server.Close()
|
|
})
|
|
runtime.Gosched() // Allow other goroutines to run
|
|
|
|
tester.InitServer(fmt.Sprintf(`
|
|
{
|
|
http_port 9080
|
|
https_port 9443
|
|
}
|
|
http://localhost:9080 {
|
|
reverse_proxy {
|
|
to unix/%s
|
|
|
|
health_uri /health
|
|
health_interval 2s
|
|
health_timeout 5s
|
|
}
|
|
}
|
|
`, socketName), "caddyfile")
|
|
|
|
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
|
|
}
|