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
header
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
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
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
func (*client) Head
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
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
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
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
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
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
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
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
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
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
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{}