Compare commits

...

8 Commits

Author SHA1 Message Date
Francis Lavoie 5968ebd0f4 reverseproxy: Add support for specifying IDs in Caddyfile 2021-09-13 00:21:54 -04:00
Francis Lavoie a5f4fae145 reverseproxy: Add ID field for upstreams 2021-09-12 23:50:04 -04:00
Francis Lavoie a779e1b383 fastcgi: Fix Caddyfile parsing when handle_response is used (#4342) 2021-09-11 14:12:21 -06:00
Matthew Holt 46ab93be51 go.mod: Update CertMagic
Adds one more debug log
2021-09-03 11:42:13 -06:00
Mohammed Al Sahaf e0fc46a911 ci: revert workaround implemented in #4306 (#4328) 2021-09-03 10:05:04 -04:00
peymaneh 9f6393c64c cmd: export CaddyVersion(), Commands() (#4316)
* cmd: Export CaddyVersion()

* cmd: Add getter Commands()
2021-09-01 18:08:02 -06:00
Francis Lavoie 105dac8c2a ci: Only test cross-build on latest Go version (#4319)
This generated way too many test jobs, which weren't really that useful. Cross-build is just to keep us posted on which architectures are building okay, so it's not necessary to do it twice. Only plan9 is not working at this point (see https://github.com/caddyserver/caddy/issues/3615)
2021-08-31 13:44:07 -06:00
Steffen Brüheim 4ebf100f09 encode: ignore flushing until after first write (#4318)
* encode: ignore flushing until after first write (fix #4314)

The first write will determine if encoding has to be done and will add an Content-Encoding. Until then Flushing has to be delayed so the Content-Encoding header can be added before headers and status code is written. (A passthrough flush would write header and status code)

* Update modules/caddyhttp/encode/encode.go

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2021-08-31 13:36:36 -06:00
15 changed files with 244 additions and 21 deletions
-3
View File
@@ -156,9 +156,6 @@ jobs:
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Create 'caddy-build'
run: mkdir -p caddy-build
- uses: goreleaser/goreleaser-action@v2 - uses: goreleaser/goreleaser-action@v2
with: with:
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
goos: ['android', 'linux', 'solaris', 'illumos', 'dragonfly', 'freebsd', 'openbsd', 'plan9', 'windows', 'darwin', 'netbsd'] goos: ['android', 'linux', 'solaris', 'illumos', 'dragonfly', 'freebsd', 'openbsd', 'plan9', 'windows', 'darwin', 'netbsd']
go: [ '1.16', '1.17' ] go: [ '1.17' ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: true continue-on-error: true
steps: steps:
-4
View File
@@ -88,10 +88,6 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-go${{ matrix.go }}-release ${{ runner.os }}-go${{ matrix.go }}-release
- name: Create the 'caddy-build' dir for GoReleaser
run: |
mkdir -p caddy-build
# GoReleaser will take care of publishing those artifacts into the release # GoReleaser will take care of publishing those artifacts into the release
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2 uses: goreleaser/goreleaser-action@v2
@@ -0,0 +1,145 @@
:8881 {
php_fastcgi app:9000 {
env FOO bar
@error status 4xx
handle_response @error {
root * /errors
rewrite * /{http.reverse_proxy.status_code}.html
file_server
}
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8881"
],
"routes": [
{
"match": [
{
"file": {
"try_files": [
"{http.request.uri.path}/index.php"
]
},
"not": [
{
"path": [
"*/"
]
}
]
}
],
"handle": [
{
"handler": "static_response",
"headers": {
"Location": [
"{http.request.uri.path}/"
]
},
"status_code": 308
}
]
},
{
"match": [
{
"file": {
"try_files": [
"{http.request.uri.path}",
"{http.request.uri.path}/index.php",
"index.php"
],
"split_path": [
".php"
]
}
}
],
"handle": [
{
"handler": "rewrite",
"uri": "{http.matchers.file.relative}"
}
]
},
{
"match": [
{
"path": [
"*.php"
]
}
],
"handle": [
{
"handle_response": [
{
"match": {
"status_code": [
4
]
},
"routes": [
{
"handle": [
{
"handler": "vars",
"root": "/errors"
}
]
},
{
"group": "group0",
"handle": [
{
"handler": "rewrite",
"uri": "/{http.reverse_proxy.status_code}.html"
}
]
},
{
"handle": [
{
"handler": "file_server",
"hide": [
"./Caddyfile"
]
}
]
}
]
}
],
"handler": "reverse_proxy",
"transport": {
"env": {
"FOO": "bar"
},
"protocol": "fastcgi",
"split_path": [
".php"
]
},
"upstreams": [
{
"dial": "app:9000"
}
]
}
]
}
]
}
}
}
}
}
@@ -0,0 +1,46 @@
:8884
reverse_proxy one|http://localhost two|http://localhost {
to three|srv+http://localhost four|srv+http://localhost
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"upstreams": [
{
"dial": "localhost:80",
"id": "one"
},
{
"dial": "localhost:80",
"id": "two"
},
{
"id": "three",
"lookup_srv": "localhost"
},
{
"id": "four",
"lookup_srv": "localhost"
}
]
}
]
}
]
}
}
}
}
}
+1 -1
View File
@@ -331,7 +331,7 @@ func cmdReload(fl Flags) (int, error) {
} }
func cmdVersion(_ Flags) (int, error) { func cmdVersion(_ Flags) (int, error) {
fmt.Println(caddyVersion()) fmt.Println(CaddyVersion())
return caddy.ExitCodeSuccess, nil return caddy.ExitCodeSuccess, nil
} }
+6
View File
@@ -61,6 +61,12 @@ type Command struct {
// any error that occurred. // any error that occurred.
type CommandFunc func(Flags) (int, error) type CommandFunc func(Flags) (int, error)
// Commands returns a list of commands initialised by
// RegisterCommand
func Commands() map[string]Command {
return commands
}
var commands = make(map[string]Command) var commands = make(map[string]Command)
func init() { func init() {
+3 -3
View File
@@ -420,7 +420,7 @@ func printEnvironment() {
fmt.Printf("caddy.AppDataDir=%s\n", caddy.AppDataDir()) fmt.Printf("caddy.AppDataDir=%s\n", caddy.AppDataDir())
fmt.Printf("caddy.AppConfigDir=%s\n", caddy.AppConfigDir()) fmt.Printf("caddy.AppConfigDir=%s\n", caddy.AppConfigDir())
fmt.Printf("caddy.ConfigAutosavePath=%s\n", caddy.ConfigAutosavePath) fmt.Printf("caddy.ConfigAutosavePath=%s\n", caddy.ConfigAutosavePath)
fmt.Printf("caddy.Version=%s\n", caddyVersion()) fmt.Printf("caddy.Version=%s\n", CaddyVersion())
fmt.Printf("runtime.GOOS=%s\n", runtime.GOOS) fmt.Printf("runtime.GOOS=%s\n", runtime.GOOS)
fmt.Printf("runtime.GOARCH=%s\n", runtime.GOARCH) fmt.Printf("runtime.GOARCH=%s\n", runtime.GOARCH)
fmt.Printf("runtime.Compiler=%s\n", runtime.Compiler) fmt.Printf("runtime.Compiler=%s\n", runtime.Compiler)
@@ -437,8 +437,8 @@ func printEnvironment() {
} }
} }
// caddyVersion returns a detailed version string, if available. // CaddyVersion returns a detailed version string, if available.
func caddyVersion() string { func CaddyVersion() string {
goModule := caddy.GoModule() goModule := caddy.GoModule()
ver := goModule.Version ver := goModule.Version
if goModule.Sum != "" { if goModule.Sum != "" {
+1 -1
View File
@@ -6,7 +6,7 @@ require (
github.com/Masterminds/sprig/v3 v3.2.2 github.com/Masterminds/sprig/v3 v3.2.2
github.com/alecthomas/chroma v0.9.2 github.com/alecthomas/chroma v0.9.2
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
github.com/caddyserver/certmagic v0.14.4 github.com/caddyserver/certmagic v0.14.5
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/chi v4.1.2+incompatible
github.com/google/cel-go v0.7.3 github.com/google/cel-go v0.7.3
+2 -2
View File
@@ -173,8 +173,8 @@ github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw=
github.com/caddyserver/certmagic v0.14.4 h1:jlGiMUHOd7p3sgT8zHA/4wckZjIp/6ME2JWGAN5zh5E= github.com/caddyserver/certmagic v0.14.5 h1:y4HcFzLLBMsTv8sSlAPj5K55mvntX8e8ExcmB/lhO6w=
github.com/caddyserver/certmagic v0.14.4/go.mod h1:/0VQ5og2Jxa5yBQ8eT80wWS7fi/DgNy1uXeXRUJ1Wj0= github.com/caddyserver/certmagic v0.14.5/go.mod h1:/0VQ5og2Jxa5yBQ8eT80wWS7fi/DgNy1uXeXRUJ1Wj0=
github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
+16
View File
@@ -182,6 +182,7 @@ type responseWriter struct {
buf *bytes.Buffer buf *bytes.Buffer
config *Encode config *Encode
statusCode int statusCode int
wroteHeader bool
} }
// WriteHeader stores the status to write when the time comes // WriteHeader stores the status to write when the time comes
@@ -195,6 +196,19 @@ func (enc *Encode) Match(rw *responseWriter) bool {
return enc.Matcher.Match(rw.statusCode, rw.Header()) return enc.Matcher.Match(rw.statusCode, rw.Header())
} }
// Flush implements http.Flusher. It delays the actual Flush of the underlying ResponseWriterWrapper
// until headers were written.
func (rw *responseWriter) Flush() {
if !rw.wroteHeader {
// flushing the underlying ResponseWriter will write header and status code,
// but we need to delay that until we can determine if we must encode and
// therefore add the Content-Encoding header; this happens in the first call
// to rw.Write (see bug in #4314)
return
}
rw.ResponseWriterWrapper.Flush()
}
// Write writes to the response. If the response qualifies, // Write writes to the response. If the response qualifies,
// it is encoded using the encoder, which is initialized // it is encoded using the encoder, which is initialized
// if not done so already. // if not done so already.
@@ -225,6 +239,7 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
if rw.statusCode > 0 { if rw.statusCode > 0 {
rw.ResponseWriter.WriteHeader(rw.statusCode) rw.ResponseWriter.WriteHeader(rw.statusCode)
rw.statusCode = 0 rw.statusCode = 0
rw.wroteHeader = true
} }
switch { switch {
@@ -271,6 +286,7 @@ func (rw *responseWriter) Close() error {
// that rely on If-None-Match, for example // that rely on If-None-Match, for example
rw.ResponseWriter.WriteHeader(rw.statusCode) rw.ResponseWriter.WriteHeader(rw.statusCode)
rw.statusCode = 0 rw.statusCode = 0
rw.wroteHeader = true
} }
if rw.w != nil { if rw.w != nil {
err2 := rw.w.Close() err2 := rw.w.Close()
+5 -3
View File
@@ -34,7 +34,8 @@ type adminUpstreams struct{}
// upstreamResults holds the status of a particular upstream // upstreamResults holds the status of a particular upstream
type upstreamStatus struct { type upstreamStatus struct {
Address string `json:"address"` ID string `json:"id"`
Address string `json:"address"` // Address is deprecated, should be removed in a future release.
Healthy bool `json:"healthy"` Healthy bool `json:"healthy"`
NumRequests int `json:"num_requests"` NumRequests int `json:"num_requests"`
Fails int `json:"fails"` Fails int `json:"fails"`
@@ -78,7 +79,7 @@ func (adminUpstreams) handleUpstreams(w http.ResponseWriter, r *http.Request) er
// Iterate over the upstream pool (needs to be fast) // Iterate over the upstream pool (needs to be fast)
var rangeErr error var rangeErr error
hosts.Range(func(key, val interface{}) bool { hosts.Range(func(key, val interface{}) bool {
address, ok := key.(string) id, ok := key.(string)
if !ok { if !ok {
rangeErr = caddy.APIError{ rangeErr = caddy.APIError{
HTTPStatus: http.StatusInternalServerError, HTTPStatus: http.StatusInternalServerError,
@@ -97,7 +98,8 @@ func (adminUpstreams) handleUpstreams(w http.ResponseWriter, r *http.Request) er
} }
results = append(results, upstreamStatus{ results = append(results, upstreamStatus{
Address: address, ID: id,
Address: id,
Healthy: !upstream.Unhealthy(), Healthy: !upstream.Unhealthy(),
NumRequests: upstream.NumRequests(), NumRequests: upstream.NumRequests(),
Fails: upstream.Fails(), Fails: upstream.Fails(),
+8 -2
View File
@@ -219,6 +219,12 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// treated as a SRV-based upstream, and any port will be // treated as a SRV-based upstream, and any port will be
// dropped. // dropped.
appendUpstream := func(address string) error { appendUpstream := func(address string) error {
var id string
if strings.Contains(address, "|") {
parts := strings.SplitN(address, "|", 2)
id = parts[0]
address = parts[1]
}
isSRV := strings.HasPrefix(address, "srv+") isSRV := strings.HasPrefix(address, "srv+")
if isSRV { if isSRV {
address = strings.TrimPrefix(address, "srv+") address = strings.TrimPrefix(address, "srv+")
@@ -231,9 +237,9 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if host, _, err := net.SplitHostPort(dialAddr); err == nil { if host, _, err := net.SplitHostPort(dialAddr); err == nil {
dialAddr = host dialAddr = host
} }
h.Upstreams = append(h.Upstreams, &Upstream{LookupSRV: dialAddr}) h.Upstreams = append(h.Upstreams, &Upstream{ID: id, LookupSRV: dialAddr})
} else { } else {
h.Upstreams = append(h.Upstreams, &Upstream{Dial: dialAddr}) h.Upstreams = append(h.Upstreams, &Upstream{ID: id, Dial: dialAddr})
} }
return nil return nil
} }
@@ -192,7 +192,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
// NOTE: we delete the tokens as we go so that the reverse_proxy // NOTE: we delete the tokens as we go so that the reverse_proxy
// unmarshal doesn't see these subdirectives which it cannot handle // unmarshal doesn't see these subdirectives which it cannot handle
for dispenser.Next() { for dispenser.Next() {
for dispenser.NextBlock(0) { for dispenser.NextBlock(0) && dispenser.Nesting() == 1 {
switch dispenser.Val() { switch dispenser.Val() {
case "root": case "root":
if !dispenser.NextArg() { if !dispenser.NextArg() {
+9
View File
@@ -65,6 +65,12 @@ type UpstreamPool []*Upstream
type Upstream struct { type Upstream struct {
Host `json:"-"` Host `json:"-"`
// The unique ID for this upstream, to disambiguate multiple
// upstreams with the same Dial address. This is optional,
// and only necessary if the upstream states need to be
// separate, such as having different health checking policies.
ID string `json:"id,omitempty"`
// The [network address](/docs/conventions#network-addresses) // The [network address](/docs/conventions#network-addresses)
// to dial to connect to the upstream. Must represent precisely // to dial to connect to the upstream. Must represent precisely
// one socket (i.e. no port ranges). A valid network address // one socket (i.e. no port ranges). A valid network address
@@ -98,6 +104,9 @@ type Upstream struct {
} }
func (u Upstream) String() string { func (u Upstream) String() string {
if u.ID != "" {
return u.ID
}
if u.LookupSRV != "" { if u.LookupSRV != "" {
return u.LookupSRV return u.LookupSRV
} }