Go API Documentation

github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy/fastcgi

No package summary is available.

Package

Files: 7. Third party imports: 2. Imports from organisation: 0. Tests: 0. Benchmarks: 0.

Constants

// FCGIHeaderLen describes header length.
const FCGIHeaderLen uint8 = 8
// FCGIKeepConn describes keep connection mode.
const FCGIKeepConn uint8 = 1
// FCGIListenSockFileno describes listen socket file number.
const FCGIListenSockFileno uint8 = 0
// FCGINullRequestID describes the null request ID.
const FCGINullRequestID uint8 = 0
// Version1 describes the version.
const Version1 uint8 = 1
const (
	// BeginRequest is the begin request flag.
	BeginRequest	uint8	= iota + 1
	// AbortRequest is the abort request flag.
	AbortRequest
	// EndRequest is the end request flag.
	EndRequest
	// Params is the parameters flag.
	Params
	// Stdin is the standard input flag.
	Stdin
	// Stdout is the standard output flag.
	Stdout
	// Stderr is the standard error flag.
	Stderr
	// Data is the data flag.
	Data
	// GetValues is the get values flag.
	GetValues
	// GetValuesResult is the get values result flag.
	GetValuesResult
	// UnknownType is the unknown type flag.
	UnknownType
	// MaxType is the maximum type flag.
	MaxType	= UnknownType
)
const (
	// Responder is the responder flag.
	Responder	uint8	= iota + 1
	// Authorizer is the authorizer flag.
	Authorizer
	// Filter is the filter flag.
	Filter
)
const (
	// RequestComplete is the completed request flag.
	RequestComplete	uint8	= iota
	// CantMultiplexConns is the multiplexed connections flag.
	CantMultiplexConns
	// Overloaded is the overloaded flag.
	Overloaded
	// UnknownRole is the unknown role flag.
	UnknownRole
)
const (
	// MaxConns is the maximum connections flag.
	MaxConns	string	= "MAX_CONNS"
	// MaxRequests is the maximum requests flag.
	MaxRequests	string	= "MAX_REQS"
	// MultiplexConns is the multiplex connections flag.
	MultiplexConns	string	= "MPXS_CONNS"
)
const (
	maxWrite	= 65500	// 65530 may work, but for compatibility
	maxPad		= 255
)

Vars

var (
	_	zapcore.ObjectMarshaler	= (*loggableEnv)(nil)

	_	caddy.Provisioner	= (*Transport)(nil)
	_	http.RoundTripper	= (*Transport)(nil)
)
var bufPool = sync.Pool{
	New: func() any {
		return new(bytes.Buffer)
	},
}
var headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
var noopLogger = zap.NewNop()

for padding so we don't have to allocate all the time not synchronized because we don't care what the contents are

var pad [maxPad]byte

Map of supported protocols to Apache ssl_mod format Note that these are slightly different from SupportedProtocols in caddytls/config.go

var tlsProtocolStrings = map[uint16]string{
	tls.VersionTLS10:	"TLSv1",
	tls.VersionTLS11:	"TLSv1.1",
	tls.VersionTLS12:	"TLSv1.2",
	tls.VersionTLS13:	"TLSv1.3",
}

Types

Transport

Transport facilitates FastCGI communication.

type Transport struct {
	// Use this directory as the fastcgi root directory. Defaults to the root
	// directory of the parent virtual host.
	Root	string	`json:"root,omitempty"`

	// The path in the URL will be split into two, with the first piece ending
	// with the value of SplitPath. The first piece will be assumed as the
	// actual resource (CGI script) name, and the second piece will be set to
	// PATH_INFO for the CGI script to use.
	//
	// Future enhancements should be careful to avoid CVE-2019-11043,
	// which can be mitigated with use of a try_files-like behavior
	// that 404s if the fastcgi path info is not found.
	SplitPath	[]string	`json:"split_path,omitempty"`

	// Path declared as root directory will be resolved to its absolute value
	// after the evaluation of any symbolic links.
	// Due to the nature of PHP opcache, root directory path is cached: when
	// using a symlinked directory as root this could generate errors when
	// symlink is changed without php-fpm being restarted; enabling this
	// directive will set $_SERVER['DOCUMENT_ROOT'] to the real directory path.
	ResolveRootSymlink	bool	`json:"resolve_root_symlink,omitempty"`

	// Extra environment variables.
	EnvVars	map[string]string	`json:"env,omitempty"`

	// The duration used to set a deadline when connecting to an upstream. Default: `3s`.
	DialTimeout	caddy.Duration	`json:"dial_timeout,omitempty"`

	// The duration used to set a deadline when reading from the FastCGI server.
	ReadTimeout	caddy.Duration	`json:"read_timeout,omitempty"`

	// The duration used to set a deadline when sending to the FastCGI server.
	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
	logger		*zap.Logger
}

client

client implements a FastCGI client, which is a standard for interfacing external applications with Web servers.

type client struct {
	rwc	net.Conn
	// keepAlive bool // TODO: implement
	reqID	uint16
	stderr	bool
	logger	*zap.Logger
}

clientCloser

clientCloser is a io.ReadCloser. It wraps a io.Reader with a Closer that closes the client connection.

type clientCloser struct {
	rwc	net.Conn
	r	*streamReader
	io.Reader

	status	int
	logger	*zap.Logger
}

envVars

This type doesn't have documentation.

type envVars map[string]string

This type doesn't have documentation.

type header struct {
	Version		uint8
	Type		uint8
	ID		uint16
	ContentLength	uint16
	PaddingLength	uint8
	Reserved	uint8
}

loggableEnv

loggableEnv is a simple type to allow for speeding up zap log encoding.

type loggableEnv struct {
	vars		envVars
	logCredentials	bool
}

record

This type doesn't have documentation.

type record struct {
	h	header
	lr	io.LimitedReader
	padding	int64
}

streamReader

This type doesn't have documentation.

type streamReader struct {
	c	*client
	rec	record
	stderr	bytes.Buffer
}

streamWriter

streamWriter abstracts out the separation of a stream into discrete records. It only writes maxWrite bytes at a time.

type streamWriter struct {
	c	*client
	h	header
	buf	*bytes.Buffer
	recType	uint8
}

Functions

func (*Transport) Provision

Provision sets up t.

func (t *Transport) Provision(ctx caddy.Context) error {
	t.logger = ctx.Logger()

	if t.Root == "" {
		t.Root = "{http.vars.root}"
	}

	version, _ := caddy.Version()
	t.serverSoftware = "Caddy/" + version

	// Set a relatively short default dial timeout.
	// This is helpful to make load-balancer retries more speedy.
	if t.DialTimeout == 0 {
		t.DialTimeout = caddy.Duration(3 * time.Second)
	}

	return nil
}

Cognitive complexity: 4, Cyclomatic complexity: 3

Uses: time.Second.

func (*Transport) UnmarshalCaddyfile

UnmarshalCaddyfile deserializes Caddyfile tokens into h.

transport fastcgi {
    root <path>
    split <at>
    env <key> <value>
    resolve_root_symlink
    dial_timeout <duration>
    read_timeout <duration>
    write_timeout <duration>
    capture_stderr
}

func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
	d.Next()	// consume transport name
	for d.NextBlock(0) {
		switch d.Val() {
		case "root":
			if !d.NextArg() {
				return d.ArgErr()
			}
			t.Root = d.Val()

		case "split":
			t.SplitPath = d.RemainingArgs()
			if len(t.SplitPath) == 0 {
				return d.ArgErr()
			}

		case "env":
			args := d.RemainingArgs()
			if len(args) != 2 {
				return d.ArgErr()
			}
			if t.EnvVars == nil {
				t.EnvVars = make(map[string]string)
			}
			t.EnvVars[args[0]] = args[1]

		case "resolve_root_symlink":
			if d.NextArg() {
				return d.ArgErr()
			}
			t.ResolveRootSymlink = true

		case "dial_timeout":
			if !d.NextArg() {
				return d.ArgErr()
			}
			dur, err := caddy.ParseDuration(d.Val())
			if err != nil {
				return d.Errf("bad timeout value %s: %v", d.Val(), err)
			}
			t.DialTimeout = caddy.Duration(dur)

		case "read_timeout":
			if !d.NextArg() {
				return d.ArgErr()
			}
			dur, err := caddy.ParseDuration(d.Val())
			if err != nil {
				return d.Errf("bad timeout value %s: %v", d.Val(), err)
			}
			t.ReadTimeout = caddy.Duration(dur)

		case "write_timeout":
			if !d.NextArg() {
				return d.ArgErr()
			}
			dur, err := caddy.ParseDuration(d.Val())
			if err != nil {
				return d.Errf("bad timeout value %s: %v", d.Val(), err)
			}
			t.WriteTimeout = caddy.Duration(dur)

		case "capture_stderr":
			if d.NextArg() {
				return d.ArgErr()
			}
			t.CaptureStderr = true

		default:
			return d.Errf("unrecognized subdirective %s", d.Val())
		}
	}
	return nil
}

Cognitive complexity: 36, Cyclomatic complexity: 23

func (*client) Do

Do made the request and returns a io.Reader that translates the data read from fcgi responder out of fcgi packet before returning it.

func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error) {
	// check for CONTENT_LENGTH, since the lack of it or wrong value will cause the backend to hang
	if clStr, ok := p["CONTENT_LENGTH"]; !ok {
		return nil, caddyhttp.Error(http.StatusLengthRequired, nil)
	} else if _, err := strconv.ParseUint(clStr, 10, 64); err != nil {
		// stdlib won't return a negative Content-Length, but we check just in case,
		// the most likely cause is from a missing content length, which is -1
		return nil, caddyhttp.Error(http.StatusLengthRequired, err)
	}

	writer := &streamWriter{c: c}
	writer.buf = bufPool.Get().(*bytes.Buffer)
	writer.buf.Reset()
	defer bufPool.Put(writer.buf)

	err = writer.writeBeginRequest(uint16(Responder), 0)
	if err != nil {
		return
	}

	writer.recType = Params
	err = writer.writePairs(p)
	if err != nil {
		return
	}

	writer.recType = Stdin
	if req != nil {
		_, err = io.Copy(writer, req)
		if err != nil {
			return nil, err
		}
	}
	err = writer.FlushStream()
	if err != nil {
		return nil, err
	}

	r = &streamReader{c: c}
	return
}

Cognitive complexity: 16, Cyclomatic complexity: 8

Uses: bytes.Buffer, caddyhttp.Error, http.StatusLengthRequired, io.Copy, strconv.ParseUint.

func (*client) Get

Get issues a GET request to the fcgi responder.

func (c *client) Get(p map[string]string, body io.Reader, l int64) (resp *http.Response, err error) {
	p["REQUEST_METHOD"] = "GET"
	p["CONTENT_LENGTH"] = strconv.FormatInt(l, 10)

	return c.Request(p, body)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

Uses: strconv.FormatInt.

Head issues a HEAD request to the fcgi responder.

func (c *client) Head(p map[string]string) (resp *http.Response, err error) {
	p["REQUEST_METHOD"] = "HEAD"
	p["CONTENT_LENGTH"] = "0"

	return c.Request(p, nil)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*client) Options

Options issues an OPTIONS request to the fcgi responder.

func (c *client) Options(p map[string]string) (resp *http.Response, err error) {
	p["REQUEST_METHOD"] = "OPTIONS"
	p["CONTENT_LENGTH"] = "0"

	return c.Request(p, nil)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*client) Post

Post issues a POST request to the fcgi responder. with request body in the format that bodyType specified

func (c *client) Post(p map[string]string, method string, bodyType string, body io.Reader, l int64) (resp *http.Response, err error) {
	if p == nil {
		p = make(map[string]string)
	}

	p["REQUEST_METHOD"] = strings.ToUpper(method)

	if len(p["REQUEST_METHOD"]) == 0 || p["REQUEST_METHOD"] == "GET" {
		p["REQUEST_METHOD"] = "POST"
	}

	p["CONTENT_LENGTH"] = strconv.FormatInt(l, 10)
	if len(bodyType) > 0 {
		p["CONTENT_TYPE"] = bodyType
	} else {
		p["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
	}

	return c.Request(p, body)
}

Cognitive complexity: 8, Cyclomatic complexity: 5

Uses: strconv.FormatInt, strings.ToUpper.

func (*client) PostFile

PostFile issues a POST to the fcgi responder in multipart(RFC 2046) standard, with form as a string key to a list values (url.Values), and/or with file as a string key to a list file path.

func (c *client) PostFile(p map[string]string, data url.Values, file map[string]string) (resp *http.Response, err error) {
	buf := &bytes.Buffer{}
	writer := multipart.NewWriter(buf)
	bodyType := writer.FormDataContentType()

	for key, val := range data {
		for _, v0 := range val {
			err = writer.WriteField(key, v0)
			if err != nil {
				return
			}
		}
	}

	for key, val := range file {
		fd, e := os.Open(val)
		if e != nil {
			return nil, e
		}
		defer fd.Close()

		part, e := writer.CreateFormFile(key, filepath.Base(val))
		if e != nil {
			return nil, e
		}
		_, err = io.Copy(part, fd)
		if err != nil {
			return
		}
	}

	err = writer.Close()
	if err != nil {
		return
	}

	return c.Post(p, "POST", bodyType, buf, int64(buf.Len()))
}

Cognitive complexity: 20, Cyclomatic complexity: 9

Uses: bytes.Buffer, filepath.Base, io.Copy, multipart.NewWriter, os.Open.

func (*client) PostForm

PostForm issues a POST to the fcgi responder, with form as a string key to a list values (url.Values)

func (c *client) PostForm(p map[string]string, data url.Values) (resp *http.Response, err error) {
	body := bytes.NewReader([]byte(data.Encode()))
	return c.Post(p, "POST", "application/x-www-form-urlencoded", body, int64(body.Len()))
}

Cognitive complexity: 0, Cyclomatic complexity: 1

Uses: bytes.NewReader.

func (*client) Request

Request returns a HTTP Response with Header and Body from fcgi responder

func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) {
	r, err := c.Do(p, req)
	if err != nil {
		return
	}

	rb := bufio.NewReader(r)
	tp := textproto.NewReader(rb)
	resp = new(http.Response)

	// Parse the response headers.
	mimeHeader, err := tp.ReadMIMEHeader()
	if err != nil && err != io.EOF {
		return
	}
	resp.Header = http.Header(mimeHeader)

	if resp.Header.Get("Status") != "" {
		statusNumber, statusInfo, statusIsCut := strings.Cut(resp.Header.Get("Status"), " ")
		resp.StatusCode, err = strconv.Atoi(statusNumber)
		if err != nil {
			return
		}
		if statusIsCut {
			resp.Status = statusInfo
		}
	} else {
		resp.StatusCode = http.StatusOK
	}

	// TODO: fixTransferEncoding ?
	resp.TransferEncoding = resp.Header["Transfer-Encoding"]
	resp.ContentLength, _ = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)

	// wrap the response body in our closer
	closer := clientCloser{
		rwc:	c.rwc,
		r:	r.(*streamReader),
		Reader:	rb,
		status:	resp.StatusCode,
		logger:	noopLogger,
	}
	if chunked(resp.TransferEncoding) {
		closer.Reader = httputil.NewChunkedReader(rb)
	}
	if c.stderr {
		closer.logger = c.logger
	}
	resp.Body = closer

	return
}

Cognitive complexity: 17, Cyclomatic complexity: 9

Uses: bufio.NewReader, http.Header, http.Response, http.StatusOK, httputil.NewChunkedReader, io.EOF, strconv.Atoi, strconv.ParseInt, strings.Cut, textproto.NewReader.

func (*client) SetReadTimeout

SetReadTimeout sets the read timeout for future calls that read from the fcgi responder. A zero value for t means no timeout will be set.

func (c *client) SetReadTimeout(t time.Duration) error {
	if t != 0 {
		return c.rwc.SetReadDeadline(time.Now().Add(t))
	}
	return nil
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: time.Now.

func (*client) SetWriteTimeout

SetWriteTimeout sets the write timeout for future calls that send data to the fcgi responder. A zero value for t means no timeout will be set.

func (c *client) SetWriteTimeout(t time.Duration) error {
	if t != 0 {
		return c.rwc.SetWriteDeadline(time.Now().Add(t))
	}
	return nil
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: time.Now.

func (*streamReader) Read

func (w *streamReader) Read(p []byte) (n int, err error) {
	for !w.rec.hasMore() {
		err = w.rec.fill(w.c.rwc)
		if err != nil {
			return 0, err
		}

		// standard error output
		if w.rec.h.Type == Stderr {
			if _, err = io.Copy(&w.stderr, &w.rec); err != nil {
				return 0, err
			}
		}
	}

	return w.rec.Read(p)
}

Cognitive complexity: 8, Cyclomatic complexity: 5

Uses: io.Copy.

func (*streamWriter) Flush

Flush write buffer data to the underlying connection, it assumes header data is the first 8 bytes of buf

func (w *streamWriter) Flush() error {
	w.h.init(w.recType, w.c.reqID, w.buf.Len()-8)
	w.writeHeader()
	w.buf.Write(pad[:w.h.PaddingLength])
	_, err := w.buf.WriteTo(w.c.rwc)
	return err
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*streamWriter) FlushStream

FlushStream flush data then end current stream

func (w *streamWriter) FlushStream() error {
	if err := w.Flush(); err != nil {
		return err
	}
	return w.endStream()
}

Cognitive complexity: 2, Cyclomatic complexity: 2

func (*streamWriter) Write

func (w *streamWriter) Write(p []byte) (int, error) {
	// init header
	if w.buf.Len() < 8 {
		w.buf.Write(pad[:8])
	}

	nn := 0
	for len(p) > 0 {
		n := len(p)
		nl := maxWrite + 8 - w.buf.Len()
		if n > nl {
			n = nl
			w.buf.Write(p[:n])
			if err := w.Flush(); err != nil {
				return nn, err
			}
			// reset headers
			w.buf.Write(pad[:8])
		} else {
			w.buf.Write(p[:n])
		}
		nn += n
		p = p[n:]
	}
	return nn, nil
}

Cognitive complexity: 10, Cyclomatic complexity: 5

func (Transport) CaddyModule

CaddyModule returns the Caddy module information.

func (Transport) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:	"http.reverse_proxy.transport.fastcgi",
		New:	func() caddy.Module { return new(Transport) },
	}
}

Cognitive complexity: 2, Cyclomatic complexity: 1

func (Transport) RoundTrip

RoundTrip implements http.RoundTripper.

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
	// PHP upstreams may do bad things, like execute a
	// non-PHP file as PHP code. See #4574
	if strings.Contains(r.URL.Path, "\x00") {
		return nil, caddyhttp.Error(http.StatusBadRequest, fmt.Errorf("invalid request path"))
	}

	env, err := t.buildEnv(r)
	if err != nil {
		return nil, fmt.Errorf("building environment: %v", err)
	}

	ctx := r.Context()

	// extract dial information from request (should have been embedded by the reverse proxy)
	network, address := "tcp", r.URL.Host
	if dialInfo, ok := reverseproxy.GetDialInfo(ctx); ok {
		network = dialInfo.Network
		address = dialInfo.Address
	}

	logCreds := server.Logs != nil && server.Logs.ShouldLogCredentials
	loggableReq := caddyhttp.LoggableHTTPRequest{
		Request:		r,
		ShouldLogCredentials:	logCreds,
	}
	loggableEnv := loggableEnv{vars: env, logCredentials: logCreds}

	logger := t.logger.With(
		zap.Object("request", loggableReq),
		zap.Object("env", loggableEnv),
	)
	if c := t.logger.Check(zapcore.DebugLevel, "roundtrip"); c != nil {
		c.Write(
			zap.String("dial", address),
			zap.Object("env", loggableEnv),
			zap.Object("request", loggableReq),
		)
	}

	// connect to the backend
	dialer := net.Dialer{Timeout: time.Duration(t.DialTimeout)}
	conn, err := dialer.DialContext(ctx, network, address)
	if err != nil {
		return nil, fmt.Errorf("dialing backend: %v", err)
	}
	defer func() {
		// conn will be closed with the response body unless there's an error
		if err != nil {
			conn.Close()
		}
	}()

	// create the client that will facilitate the protocol
	client := client{
		rwc:	conn,
		reqID:	1,
		logger:	logger,
		stderr:	t.CaptureStderr,
	}

	// read/write timeouts
	if err = client.SetReadTimeout(time.Duration(t.ReadTimeout)); err != nil {
		return nil, fmt.Errorf("setting read timeout: %v", err)
	}
	if err = client.SetWriteTimeout(time.Duration(t.WriteTimeout)); err != nil {
		return nil, fmt.Errorf("setting write timeout: %v", err)
	}

	contentLength := r.ContentLength
	if contentLength == 0 {
		contentLength, _ = strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64)
	}

	var resp *http.Response
	switch r.Method {
	case http.MethodHead:
		resp, err = client.Head(env)
	case http.MethodGet:
		resp, err = client.Get(env, r.Body, contentLength)
	case http.MethodOptions:
		resp, err = client.Options(env)
	default:
		resp, err = client.Post(env, r.Method, r.Header.Get("Content-Type"), r.Body, contentLength)
	}
	if err != nil {
		return nil, err
	}

	return resp, nil
}

Cognitive complexity: 30, Cyclomatic complexity: 16

Uses: caddyhttp.Error, caddyhttp.LoggableHTTPRequest, caddyhttp.Server, caddyhttp.ServerCtxKey, fmt.Errorf, http.MethodGet, http.MethodHead, http.MethodOptions, http.Response, http.StatusBadRequest, net.Dialer, reverseproxy.GetDialInfo, strconv.ParseInt, strings.Contains, time.Duration, zap.Object, zap.String, zapcore.DebugLevel.

func (clientCloser) Close

func (f clientCloser) Close() error {
	stderr := f.r.stderr.Bytes()
	if len(stderr) == 0 {
		return f.rwc.Close()
	}

	logLevel := zapcore.WarnLevel
	if f.status >= 400 {
		logLevel = zapcore.ErrorLevel
	}

	if c := f.logger.Check(logLevel, "stderr"); c != nil {
		c.Write(zap.ByteString("body", stderr))
	}

	return f.rwc.Close()
}

Cognitive complexity: 6, Cyclomatic complexity: 4

Uses: zap.ByteString, zapcore.ErrorLevel, zapcore.WarnLevel.

func (loggableEnv) MarshalLogObject

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)
	}
	return nil
}

Cognitive complexity: 8, Cyclomatic complexity: 5

Uses: strings.ToLower.

Private functions

func chunked

Checks whether chunked is part of the encodings stack

chunked (te []string) bool

func encodeSize

encodeSize (b []byte, size uint32) int
References: binary.BigEndian.

func init

init ()
References: httpcaddyfile.RegisterDirective.

func parsePHPFastCGI

parsePHPFastCGI parses the php_fastcgi directive, which has the same syntax as the reverse_proxy directive (in fact, the reverse_proxy's directive Unmarshaler is invoked by this function) but the resulting proxy is specially configured for most™️ PHP apps over FastCGI. A line such as this:

php_fastcgi localhost:7777

is equivalent to a route consisting of:

# Add trailing slash for directory requests
@canonicalPath {
    file {path}/index.php
    not path */
}
redir @canonicalPath {path}/ 308

# If the requested file does not exist, try index files
@indexFiles file {
    try_files {path} {path}/index.php index.php
    split_path .php
}
rewrite @indexFiles {http.matchers.file.relative}

# Proxy PHP files to the FastCGI responder
@phpFiles path *.php
reverse_proxy @phpFiles localhost:7777 {
    transport fastcgi {
        split .php
    }
}

Thus, this directive produces multiple handlers, each with a different matcher because multiple consecutive handlers are necessary to support the common PHP use case. If this "common" config is not compatible with a user's PHP requirements, they can use a manual approach based on the example above to configure it precisely as they need.

If a matcher is specified by the user, for example:

php_fastcgi /subpath localhost:7777

then the resulting handlers are wrapped in a subroute that uses the user's matcher as a prerequisite to enter the subroute. In other words, the directive's matcher is necessary, but not sufficient.

parsePHPFastCGI (h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
References: caddyconfig.JSONModuleObject, caddyhttp.MatchNot, caddyhttp.MatchPath, caddyhttp.Route, caddyhttp.RouteList, caddyhttp.StaticResponse, caddyhttp.Subroute, caddyhttp.WeakString, fileserver.MatchFile, http.Header, http.StatusPermanentRedirect, httpcaddyfile.ConfigValue, json.RawMessage, reverseproxy.Handler, rewrite.Rewrite, strconv.Itoa, strings.HasSuffix.

func fill

fill (r io.Reader) error
References: binary.BigEndian, binary.Read, errors.New, io.Copy, io.Discard, io.EOF.

func hasMore

hasMore () bool

func endStream

endStream () error

func writeBeginRequest

writeBeginRequest (role uint16, flags uint8) error

func writeHeader

writeHeader populate header wire data in buf, it abuses buffer.Bytes() modification

writeHeader ()
References: binary.BigEndian.

func writePairs

writePairs (pairs map[string]string) error

func writeRecord

writeRecord (recType uint8, content []byte) error

func buildEnv

buildEnv returns a set of CGI environment variables for the request.

buildEnv (r *http.Request) (envVars, error)
References: caddyhttp.OriginalRequestCtxKey, caddyhttp.SanitizedPathJoin, caddytls.SupportedCipherSuites, filepath.EvalSymlinks, http.Request, net.SplitHostPort, strings.HasPrefix, strings.Join, strings.LastIndex, strings.Replace, strings.ToUpper, strings.TrimSuffix.

func splitPos

splitPos returns the index where path should be split based on t.SplitPath.

splitPos (path string) int
References: strings.Index, strings.ToLower.


Tests

Files: 1. Third party imports: 0. Imports from organisation: 0. Tests: 0. Benchmarks: 0.

Constants

// test fcgi protocol includes:
// Get, Post, Post in multipart/form-data, and Post with files
// each key should be the md5 of the value or the file uploaded
// specify remote fcgi responder ip:port to test with php
// test failed if the remote fcgi(script) failed md5 verification
// and output "FAILED" in response
const (
	scriptFile	= "/tank/www/fcgic_test.php"
	// ipPort = "remote-php-serv:59000"
	ipPort	= "127.0.0.1:59000"
)

Vars

var globalt *testing.T

Types

FastCGIServer

This type doesn't have documentation.

type FastCGIServer struct{}