mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 21:53:48 +03:00
Co-authored-by: flga <flga@users.noreply.github.com>
This commit is contained in:
parent
ec2a5762b0
commit
f2a7e7c966
3 changed files with 116 additions and 41 deletions
|
@ -35,16 +35,16 @@ func init() {
|
||||||
|
|
||||||
// UnmarshalCaddyfile deserializes Caddyfile tokens into h.
|
// UnmarshalCaddyfile deserializes Caddyfile tokens into h.
|
||||||
//
|
//
|
||||||
// transport fastcgi {
|
// transport fastcgi {
|
||||||
// root <path>
|
// root <path>
|
||||||
// split <at>
|
// split <at>
|
||||||
// env <key> <value>
|
// env <key> <value>
|
||||||
// resolve_root_symlink
|
// resolve_root_symlink
|
||||||
// dial_timeout <duration>
|
// dial_timeout <duration>
|
||||||
// read_timeout <duration>
|
// read_timeout <duration>
|
||||||
// write_timeout <duration>
|
// write_timeout <duration>
|
||||||
// }
|
// capture_stderr
|
||||||
//
|
// }
|
||||||
func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
for d.NextBlock(0) {
|
for d.NextBlock(0) {
|
||||||
|
@ -107,6 +107,12 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
}
|
}
|
||||||
t.WriteTimeout = caddy.Duration(dur)
|
t.WriteTimeout = caddy.Duration(dur)
|
||||||
|
|
||||||
|
case "capture_stderr":
|
||||||
|
if d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
t.CaptureStderr = true
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return d.Errf("unrecognized subdirective %s", d.Val())
|
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||||
}
|
}
|
||||||
|
@ -120,31 +126,31 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
// Unmarshaler is invoked by this function) but the resulting proxy is specially
|
// Unmarshaler is invoked by this function) but the resulting proxy is specially
|
||||||
// configured for most™️ PHP apps over FastCGI. A line such as this:
|
// configured for most™️ PHP apps over FastCGI. A line such as this:
|
||||||
//
|
//
|
||||||
// php_fastcgi localhost:7777
|
// php_fastcgi localhost:7777
|
||||||
//
|
//
|
||||||
// is equivalent to a route consisting of:
|
// is equivalent to a route consisting of:
|
||||||
//
|
//
|
||||||
// # Add trailing slash for directory requests
|
// # Add trailing slash for directory requests
|
||||||
// @canonicalPath {
|
// @canonicalPath {
|
||||||
// file {path}/index.php
|
// file {path}/index.php
|
||||||
// not path */
|
// not path */
|
||||||
// }
|
// }
|
||||||
// redir @canonicalPath {path}/ 308
|
// redir @canonicalPath {path}/ 308
|
||||||
//
|
//
|
||||||
// # If the requested file does not exist, try index files
|
// # If the requested file does not exist, try index files
|
||||||
// @indexFiles file {
|
// @indexFiles file {
|
||||||
// try_files {path} {path}/index.php index.php
|
// try_files {path} {path}/index.php index.php
|
||||||
// split_path .php
|
// split_path .php
|
||||||
// }
|
// }
|
||||||
// rewrite @indexFiles {http.matchers.file.relative}
|
// rewrite @indexFiles {http.matchers.file.relative}
|
||||||
//
|
//
|
||||||
// # Proxy PHP files to the FastCGI responder
|
// # Proxy PHP files to the FastCGI responder
|
||||||
// @phpFiles path *.php
|
// @phpFiles path *.php
|
||||||
// reverse_proxy @phpFiles localhost:7777 {
|
// reverse_proxy @phpFiles localhost:7777 {
|
||||||
// transport fastcgi {
|
// transport fastcgi {
|
||||||
// split .php
|
// split .php
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Thus, this directive produces multiple handlers, each with a different
|
// Thus, this directive produces multiple handlers, each with a different
|
||||||
// matcher because multiple consecutive handlers are necessary to support
|
// matcher because multiple consecutive handlers are necessary to support
|
||||||
|
@ -154,7 +160,7 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
//
|
//
|
||||||
// If a matcher is specified by the user, for example:
|
// If a matcher is specified by the user, for example:
|
||||||
//
|
//
|
||||||
// php_fastcgi /subpath localhost:7777
|
// php_fastcgi /subpath localhost:7777
|
||||||
//
|
//
|
||||||
// then the resulting handlers are wrapped in a subroute that uses the
|
// then the resulting handlers are wrapped in a subroute that uses the
|
||||||
// user's matcher as a prerequisite to enter the subroute. In other
|
// user's matcher as a prerequisite to enter the subroute. In other
|
||||||
|
@ -303,6 +309,14 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
|
||||||
fcgiTransport.WriteTimeout = caddy.Duration(dur)
|
fcgiTransport.WriteTimeout = caddy.Duration(dur)
|
||||||
dispenser.Delete()
|
dispenser.Delete()
|
||||||
dispenser.Delete()
|
dispenser.Delete()
|
||||||
|
|
||||||
|
case "capture_stderr":
|
||||||
|
args := dispenser.RemainingArgs()
|
||||||
|
dispenser.Delete()
|
||||||
|
for range args {
|
||||||
|
dispenser.Delete()
|
||||||
|
}
|
||||||
|
fcgiTransport.CaptureStderr = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FCGIListenSockFileno describes listen socket file number.
|
// FCGIListenSockFileno describes listen socket file number.
|
||||||
|
@ -180,6 +182,7 @@ type FCGIClient struct {
|
||||||
stderr bytes.Buffer
|
stderr bytes.Buffer
|
||||||
keepAlive bool
|
keepAlive bool
|
||||||
reqID uint16
|
reqID uint16
|
||||||
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialWithDialerContext connects to the fcgi responder at the specified network address, using custom net.Dialer
|
// DialWithDialerContext connects to the fcgi responder at the specified network address, using custom net.Dialer
|
||||||
|
@ -339,7 +342,6 @@ type streamReader struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *streamReader) Read(p []byte) (n int, err error) {
|
func (w *streamReader) Read(p []byte) (n int, err error) {
|
||||||
|
|
||||||
if len(p) > 0 {
|
if len(p) > 0 {
|
||||||
if len(w.buf) == 0 {
|
if len(w.buf) == 0 {
|
||||||
|
|
||||||
|
@ -400,9 +402,24 @@ func (c *FCGIClient) Do(p map[string]string, req io.Reader) (r io.Reader, err er
|
||||||
type clientCloser struct {
|
type clientCloser struct {
|
||||||
*FCGIClient
|
*FCGIClient
|
||||||
io.Reader
|
io.Reader
|
||||||
|
|
||||||
|
status int
|
||||||
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f clientCloser) Close() error { return f.rwc.Close() }
|
func (f clientCloser) Close() error {
|
||||||
|
stderr := f.FCGIClient.stderr.Bytes()
|
||||||
|
if len(stderr) == 0 {
|
||||||
|
return f.FCGIClient.rwc.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.status >= 400 {
|
||||||
|
f.logger.Error("stderr", zap.ByteString("body", stderr))
|
||||||
|
} else {
|
||||||
|
f.logger.Warn("stderr", zap.ByteString("body", stderr))
|
||||||
|
}
|
||||||
|
return f.FCGIClient.rwc.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// Request returns a HTTP Response with Header and Body
|
// Request returns a HTTP Response with Header and Body
|
||||||
// from fcgi responder
|
// from fcgi responder
|
||||||
|
@ -442,9 +459,19 @@ func (c *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Res
|
||||||
resp.ContentLength, _ = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
resp.ContentLength, _ = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||||
|
|
||||||
if chunked(resp.TransferEncoding) {
|
if chunked(resp.TransferEncoding) {
|
||||||
resp.Body = clientCloser{c, httputil.NewChunkedReader(rb)}
|
resp.Body = clientCloser{
|
||||||
|
FCGIClient: c,
|
||||||
|
Reader: httputil.NewChunkedReader(rb),
|
||||||
|
status: resp.StatusCode,
|
||||||
|
logger: c.logger,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
resp.Body = clientCloser{c, io.NopCloser(rb)}
|
resp.Body = clientCloser{
|
||||||
|
FCGIClient: c,
|
||||||
|
Reader: rb,
|
||||||
|
status: resp.StatusCode,
|
||||||
|
logger: c.logger,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,8 @@ import (
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var noopLogger = zap.NewNop()
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(Transport{})
|
caddy.RegisterModule(Transport{})
|
||||||
}
|
}
|
||||||
|
@ -74,6 +76,11 @@ type Transport struct {
|
||||||
// The duration used to set a deadline when sending to the FastCGI server.
|
// The duration used to set a deadline when sending to the FastCGI server.
|
||||||
WriteTimeout caddy.Duration `json:"write_timeout,omitempty"`
|
WriteTimeout caddy.Duration `json:"write_timeout,omitempty"`
|
||||||
|
|
||||||
|
// Capture and log any messages sent by the upstream on stderr. Logs at WARN
|
||||||
|
// level by default. If the response has a 4xx or 5xx status ERROR level will
|
||||||
|
// be used instead.
|
||||||
|
CaptureStderr bool `json:"capture_stderr,omitempty"`
|
||||||
|
|
||||||
serverSoftware string
|
serverSoftware string
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
@ -108,6 +115,8 @@ func (t *Transport) Provision(ctx caddy.Context) error {
|
||||||
|
|
||||||
// RoundTrip implements http.RoundTripper.
|
// RoundTrip implements http.RoundTripper.
|
||||||
func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
|
server := r.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server)
|
||||||
|
|
||||||
// Disallow null bytes in the request path, because
|
// Disallow null bytes in the request path, because
|
||||||
// PHP upstreams may do bad things, like execute a
|
// PHP upstreams may do bad things, like execute a
|
||||||
// non-PHP file as PHP code. See #4574
|
// non-PHP file as PHP code. See #4574
|
||||||
|
@ -135,10 +144,16 @@ func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
address = dialInfo.Address
|
address = dialInfo.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logCreds := server.Logs != nil && server.Logs.ShouldLogCredentials
|
||||||
|
loggableReq := caddyhttp.LoggableHTTPRequest{
|
||||||
|
Request: r,
|
||||||
|
ShouldLogCredentials: logCreds,
|
||||||
|
}
|
||||||
|
loggableEnv := loggableEnv{vars: env, logCredentials: logCreds}
|
||||||
t.logger.Debug("roundtrip",
|
t.logger.Debug("roundtrip",
|
||||||
zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: r}),
|
zap.Object("request", loggableReq),
|
||||||
zap.String("dial", address),
|
zap.String("dial", address),
|
||||||
zap.Object("env", env),
|
zap.Object("env", loggableEnv),
|
||||||
)
|
)
|
||||||
|
|
||||||
fcgiBackend, err := DialContext(ctx, network, address)
|
fcgiBackend, err := DialContext(ctx, network, address)
|
||||||
|
@ -146,6 +161,14 @@ func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
// TODO: wrap in a special error type if the dial failed, so retries can happen if enabled
|
// TODO: wrap in a special error type if the dial failed, so retries can happen if enabled
|
||||||
return nil, fmt.Errorf("dialing backend: %v", err)
|
return nil, fmt.Errorf("dialing backend: %v", err)
|
||||||
}
|
}
|
||||||
|
if t.CaptureStderr {
|
||||||
|
fcgiBackend.logger = t.logger.With(
|
||||||
|
zap.Object("request", loggableReq),
|
||||||
|
zap.Object("env", loggableEnv),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fcgiBackend.logger = noopLogger
|
||||||
|
}
|
||||||
// fcgiBackend gets closed when response body is closed (see clientCloser)
|
// fcgiBackend gets closed when response body is closed (see clientCloser)
|
||||||
|
|
||||||
// read/write timeouts
|
// read/write timeouts
|
||||||
|
@ -364,11 +387,22 @@ func (t Transport) splitPos(path string) int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// envVars is a simple type to allow for speeding up zap log encoding.
|
|
||||||
type envVars map[string]string
|
type envVars map[string]string
|
||||||
|
|
||||||
func (env envVars) MarshalLogObject(enc zapcore.ObjectEncoder) error {
|
// loggableEnv is a simple type to allow for speeding up zap log encoding.
|
||||||
for k, v := range env {
|
type loggableEnv struct {
|
||||||
|
vars envVars
|
||||||
|
logCredentials bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (env loggableEnv) MarshalLogObject(enc zapcore.ObjectEncoder) error {
|
||||||
|
for k, v := range env.vars {
|
||||||
|
if !env.logCredentials {
|
||||||
|
switch strings.ToLower(k) {
|
||||||
|
case "http_cookie", "http_set_cookie", "http_authorization", "http_proxy_authorization":
|
||||||
|
v = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
enc.AddString(k, v)
|
enc.AddString(k, v)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -387,7 +421,7 @@ var headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ zapcore.ObjectMarshaler = (*envVars)(nil)
|
_ zapcore.ObjectMarshaler = (*loggableEnv)(nil)
|
||||||
|
|
||||||
_ caddy.Provisioner = (*Transport)(nil)
|
_ caddy.Provisioner = (*Transport)(nil)
|
||||||
_ http.RoundTripper = (*Transport)(nil)
|
_ http.RoundTripper = (*Transport)(nil)
|
||||||
|
|
Loading…
Reference in a new issue