496
vendor/github.com/mholt/caddy/caddyhttp/fastcgi/fastcgi.go
generated
vendored
Normal file
496
vendor/github.com/mholt/caddy/caddyhttp/fastcgi/fastcgi.go
generated
vendored
Normal file
@@ -0,0 +1,496 @@
|
||||
// Copyright 2015 Light Code Labs, LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package fastcgi has middleware that acts as a FastCGI client. Requests
|
||||
// that get forwarded to FastCGI stop the middleware execution chain.
|
||||
// The most common use for this package is to serve PHP websites via php-fpm.
|
||||
package fastcgi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/mholt/caddy"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
"github.com/mholt/caddy/caddytls"
|
||||
)
|
||||
|
||||
// Handler is a middleware type that can handle requests as a FastCGI client.
|
||||
type Handler struct {
|
||||
Next httpserver.Handler
|
||||
Rules []Rule
|
||||
Root string
|
||||
FileSys http.FileSystem
|
||||
|
||||
// These are sent to CGI scripts in env variables
|
||||
SoftwareName string
|
||||
SoftwareVersion string
|
||||
ServerName string
|
||||
ServerPort string
|
||||
}
|
||||
|
||||
// ServeHTTP satisfies the httpserver.Handler interface.
|
||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
for _, rule := range h.Rules {
|
||||
// First requirement: Base path must match request path. If it doesn't,
|
||||
// we check to make sure the leading slash is not missing, and if so,
|
||||
// we check again with it prepended. This is in case people forget
|
||||
// a leading slash when performing rewrites, and we don't want to expose
|
||||
// the contents of the (likely PHP) script. See issue #1645.
|
||||
hpath := httpserver.Path(r.URL.Path)
|
||||
if !hpath.Matches(rule.Path) {
|
||||
if strings.HasPrefix(string(hpath), "/") {
|
||||
// this is a normal-looking path, and it doesn't match; try next rule
|
||||
continue
|
||||
}
|
||||
hpath = httpserver.Path("/" + string(hpath)) // prepend leading slash
|
||||
if !hpath.Matches(rule.Path) {
|
||||
// even after fixing the request path, it still doesn't match; try next rule
|
||||
continue
|
||||
}
|
||||
}
|
||||
// The path must also be allowed (not ignored).
|
||||
if !rule.AllowedPath(r.URL.Path) {
|
||||
continue
|
||||
}
|
||||
|
||||
// In addition to matching the path, a request must meet some
|
||||
// other criteria before being proxied as FastCGI. For example,
|
||||
// we probably want to exclude static assets (CSS, JS, images...)
|
||||
// but we also want to be flexible for the script we proxy to.
|
||||
|
||||
fpath := r.URL.Path
|
||||
|
||||
if idx, ok := httpserver.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok {
|
||||
fpath = idx
|
||||
// Index file present.
|
||||
// If request path cannot be split, return error.
|
||||
if !rule.canSplit(fpath) {
|
||||
return http.StatusInternalServerError, ErrIndexMissingSplit
|
||||
}
|
||||
} else {
|
||||
// No index file present.
|
||||
// If request path cannot be split, ignore request.
|
||||
if !rule.canSplit(fpath) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// These criteria work well in this order for PHP sites
|
||||
if !h.exists(fpath) || fpath[len(fpath)-1] == '/' || strings.HasSuffix(fpath, rule.Ext) {
|
||||
|
||||
// Create environment for CGI script
|
||||
env, err := h.buildEnv(r, rule, fpath)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// Connect to FastCGI gateway
|
||||
address, err := rule.Address()
|
||||
if err != nil {
|
||||
return http.StatusBadGateway, err
|
||||
}
|
||||
network, address := parseAddress(address)
|
||||
|
||||
ctx := context.Background()
|
||||
if rule.ConnectTimeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, rule.ConnectTimeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
fcgiBackend, err := DialContext(ctx, network, address)
|
||||
if err != nil {
|
||||
return http.StatusBadGateway, err
|
||||
}
|
||||
defer fcgiBackend.Close()
|
||||
|
||||
// read/write timeouts
|
||||
if err := fcgiBackend.SetReadTimeout(rule.ReadTimeout); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
if err := fcgiBackend.SetSendTimeout(rule.SendTimeout); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
|
||||
var contentLength int64
|
||||
// if ContentLength is already set
|
||||
if r.ContentLength > 0 {
|
||||
contentLength = r.ContentLength
|
||||
} else {
|
||||
contentLength, _ = strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64)
|
||||
}
|
||||
switch r.Method {
|
||||
case "HEAD":
|
||||
resp, err = fcgiBackend.Head(env)
|
||||
case "GET":
|
||||
resp, err = fcgiBackend.Get(env, r.Body, contentLength)
|
||||
case "OPTIONS":
|
||||
resp, err = fcgiBackend.Options(env)
|
||||
default:
|
||||
resp, err = fcgiBackend.Post(env, r.Method, r.Header.Get("Content-Type"), r.Body, contentLength)
|
||||
}
|
||||
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
return http.StatusGatewayTimeout, err
|
||||
} else if err != io.EOF {
|
||||
return http.StatusBadGateway, err
|
||||
}
|
||||
}
|
||||
|
||||
// Write response header
|
||||
writeHeader(w, resp)
|
||||
|
||||
// Write the response body
|
||||
_, err = io.Copy(w, resp.Body)
|
||||
if err != nil {
|
||||
return http.StatusBadGateway, err
|
||||
}
|
||||
|
||||
// Log any stderr output from upstream
|
||||
if fcgiBackend.stderr.Len() != 0 {
|
||||
// Remove trailing newline, error logger already does this.
|
||||
err = LogError(strings.TrimSuffix(fcgiBackend.stderr.String(), "\n"))
|
||||
}
|
||||
|
||||
// Normally we would return the status code if it is an error status (>= 400),
|
||||
// however, upstream FastCGI apps don't know about our contract and have
|
||||
// probably already written an error page. So we just return 0, indicating
|
||||
// that the response body is already written. However, we do return any
|
||||
// error value so it can be logged.
|
||||
// Note that the proxy middleware works the same way, returning status=0.
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return h.Next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// parseAddress returns the network and address of fcgiAddress.
|
||||
// The first string is the network, "tcp" or "unix", implied from the scheme and address.
|
||||
// The second string is fcgiAddress, with scheme prefixes removed.
|
||||
// The two returned strings can be used as parameters to the Dial() function.
|
||||
func parseAddress(fcgiAddress string) (string, string) {
|
||||
// check if address has tcp scheme explicitly set
|
||||
if strings.HasPrefix(fcgiAddress, "tcp://") {
|
||||
return "tcp", fcgiAddress[len("tcp://"):]
|
||||
}
|
||||
// check if address has fastcgi scheme explicitly set
|
||||
if strings.HasPrefix(fcgiAddress, "fastcgi://") {
|
||||
return "tcp", fcgiAddress[len("fastcgi://"):]
|
||||
}
|
||||
// check if unix socket
|
||||
if trim := strings.HasPrefix(fcgiAddress, "unix"); strings.HasPrefix(fcgiAddress, "/") || trim {
|
||||
if trim {
|
||||
return "unix", fcgiAddress[len("unix:"):]
|
||||
}
|
||||
return "unix", fcgiAddress
|
||||
}
|
||||
// default case, a plain tcp address with no scheme
|
||||
return "tcp", fcgiAddress
|
||||
}
|
||||
|
||||
func writeHeader(w http.ResponseWriter, r *http.Response) {
|
||||
for key, vals := range r.Header {
|
||||
for _, val := range vals {
|
||||
w.Header().Add(key, val)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(r.StatusCode)
|
||||
}
|
||||
|
||||
func (h Handler) exists(path string) bool {
|
||||
if _, err := os.Stat(h.Root + path); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// buildEnv returns a set of CGI environment variables for the request.
|
||||
func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string]string, error) {
|
||||
var env map[string]string
|
||||
|
||||
// Separate remote IP and port; more lenient than net.SplitHostPort
|
||||
var ip, port string
|
||||
if idx := strings.LastIndex(r.RemoteAddr, ":"); idx > -1 {
|
||||
ip = r.RemoteAddr[:idx]
|
||||
port = r.RemoteAddr[idx+1:]
|
||||
} else {
|
||||
ip = r.RemoteAddr
|
||||
}
|
||||
|
||||
// Remove [] from IPv6 addresses
|
||||
ip = strings.Replace(ip, "[", "", 1)
|
||||
ip = strings.Replace(ip, "]", "", 1)
|
||||
|
||||
// Split path in preparation for env variables.
|
||||
// Previous rule.canSplit checks ensure this can never be -1.
|
||||
splitPos := rule.splitPos(fpath)
|
||||
|
||||
// Request has the extension; path was split successfully
|
||||
docURI := fpath[:splitPos+len(rule.SplitPath)]
|
||||
pathInfo := fpath[splitPos+len(rule.SplitPath):]
|
||||
scriptName := fpath
|
||||
|
||||
// Strip PATH_INFO from SCRIPT_NAME
|
||||
scriptName = strings.TrimSuffix(scriptName, pathInfo)
|
||||
|
||||
// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
|
||||
scriptFilename := filepath.Join(rule.Root, scriptName)
|
||||
|
||||
// Add vhost path prefix to scriptName. Otherwise, some PHP software will
|
||||
// have difficulty discovering its URL.
|
||||
pathPrefix, _ := r.Context().Value(caddy.CtxKey("path_prefix")).(string)
|
||||
scriptName = path.Join(pathPrefix, scriptName)
|
||||
|
||||
// Get the request URI from context. The context stores the original URI in case
|
||||
// it was changed by a middleware such as rewrite. By default, we pass the
|
||||
// original URI in as the value of REQUEST_URI (the user can overwrite this
|
||||
// if desired). Most PHP apps seem to want the original URI. Besides, this is
|
||||
// how nginx defaults: http://stackoverflow.com/a/12485156/1048862
|
||||
reqURL, _ := r.Context().Value(httpserver.OriginalURLCtxKey).(url.URL)
|
||||
|
||||
// Retrieve name of remote user that was set by some downstream middleware such as basicauth.
|
||||
remoteUser, _ := r.Context().Value(httpserver.RemoteUserCtxKey).(string)
|
||||
|
||||
requestScheme := "http"
|
||||
if r.TLS != nil {
|
||||
requestScheme = "https"
|
||||
}
|
||||
|
||||
// Some variables are unused but cleared explicitly to prevent
|
||||
// the parent environment from interfering.
|
||||
env = map[string]string{
|
||||
// Variables defined in CGI 1.1 spec
|
||||
"AUTH_TYPE": "", // Not used
|
||||
"CONTENT_LENGTH": r.Header.Get("Content-Length"),
|
||||
"CONTENT_TYPE": r.Header.Get("Content-Type"),
|
||||
"GATEWAY_INTERFACE": "CGI/1.1",
|
||||
"PATH_INFO": pathInfo,
|
||||
"QUERY_STRING": r.URL.RawQuery,
|
||||
"REMOTE_ADDR": ip,
|
||||
"REMOTE_HOST": ip, // For speed, remote host lookups disabled
|
||||
"REMOTE_PORT": port,
|
||||
"REMOTE_IDENT": "", // Not used
|
||||
"REMOTE_USER": remoteUser,
|
||||
"REQUEST_METHOD": r.Method,
|
||||
"REQUEST_SCHEME": requestScheme,
|
||||
"SERVER_NAME": h.ServerName,
|
||||
"SERVER_PORT": h.ServerPort,
|
||||
"SERVER_PROTOCOL": r.Proto,
|
||||
"SERVER_SOFTWARE": h.SoftwareName + "/" + h.SoftwareVersion,
|
||||
|
||||
// Other variables
|
||||
"DOCUMENT_ROOT": rule.Root,
|
||||
"DOCUMENT_URI": docURI,
|
||||
"HTTP_HOST": r.Host, // added here, since not always part of headers
|
||||
"REQUEST_URI": reqURL.RequestURI(),
|
||||
"SCRIPT_FILENAME": scriptFilename,
|
||||
"SCRIPT_NAME": scriptName,
|
||||
}
|
||||
|
||||
// compliance with the CGI specification requires that
|
||||
// PATH_TRANSLATED should only exist if PATH_INFO is defined.
|
||||
// Info: https://www.ietf.org/rfc/rfc3875 Page 14
|
||||
if env["PATH_INFO"] != "" {
|
||||
env["PATH_TRANSLATED"] = filepath.Join(rule.Root, pathInfo) // Info: http://www.oreilly.com/openbook/cgi/ch02_04.html
|
||||
}
|
||||
|
||||
// Some web apps rely on knowing HTTPS or not
|
||||
if r.TLS != nil {
|
||||
env["HTTPS"] = "on"
|
||||
// and pass the protocol details in a manner compatible with apache's mod_ssl
|
||||
// (which is why they have a SSL_ prefix and not TLS_).
|
||||
v, ok := tlsProtocolStringToMap[r.TLS.Version]
|
||||
if ok {
|
||||
env["SSL_PROTOCOL"] = v
|
||||
}
|
||||
// and pass the cipher suite in a manner compatible with apache's mod_ssl
|
||||
for k, v := range caddytls.SupportedCiphersMap {
|
||||
if v == r.TLS.CipherSuite {
|
||||
env["SSL_CIPHER"] = k
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add env variables from config (with support for placeholders in values)
|
||||
replacer := httpserver.NewReplacer(r, nil, "")
|
||||
for _, envVar := range rule.EnvVars {
|
||||
env[envVar[0]] = replacer.Replace(envVar[1])
|
||||
}
|
||||
|
||||
// Add all HTTP headers to env variables
|
||||
for field, val := range r.Header {
|
||||
header := strings.ToUpper(field)
|
||||
header = headerNameReplacer.Replace(header)
|
||||
env["HTTP_"+header] = strings.Join(val, ", ")
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// Rule represents a FastCGI handling rule.
|
||||
// It is parsed from the fastcgi directive in the Caddyfile, see setup.go.
|
||||
type Rule struct {
|
||||
// The base path to match. Required.
|
||||
Path string
|
||||
|
||||
// upstream load balancer
|
||||
balancer
|
||||
|
||||
// Always process files with this extension with fastcgi.
|
||||
Ext string
|
||||
|
||||
// Use this directory as the fastcgi root directory. Defaults to the root
|
||||
// directory of the parent virtual host.
|
||||
Root string
|
||||
|
||||
// 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.
|
||||
SplitPath string
|
||||
|
||||
// If the URL ends with '/' (which indicates a directory), these index
|
||||
// files will be tried instead.
|
||||
IndexFiles []string
|
||||
|
||||
// Environment Variables
|
||||
EnvVars [][2]string
|
||||
|
||||
// Ignored paths
|
||||
IgnoredSubPaths []string
|
||||
|
||||
// The duration used to set a deadline when connecting to an upstream.
|
||||
ConnectTimeout time.Duration
|
||||
|
||||
// The duration used to set a deadline when reading from the FastCGI server.
|
||||
ReadTimeout time.Duration
|
||||
|
||||
// The duration used to set a deadline when sending to the FastCGI server.
|
||||
SendTimeout time.Duration
|
||||
}
|
||||
|
||||
// balancer is a fastcgi upstream load balancer.
|
||||
type balancer interface {
|
||||
// Address picks an upstream address from the
|
||||
// underlying load balancer.
|
||||
Address() (string, error)
|
||||
}
|
||||
|
||||
// roundRobin is a round robin balancer for fastcgi upstreams.
|
||||
type roundRobin struct {
|
||||
// Known Go bug: https://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||
// must be first field for 64 bit alignment
|
||||
// on x86 and arm.
|
||||
index int64
|
||||
addresses []string
|
||||
}
|
||||
|
||||
func (r *roundRobin) Address() (string, error) {
|
||||
index := atomic.AddInt64(&r.index, 1) % int64(len(r.addresses))
|
||||
return r.addresses[index], nil
|
||||
}
|
||||
|
||||
// srvResolver is a private interface used to abstract
|
||||
// the DNS resolver. It is mainly used to facilitate testing.
|
||||
type srvResolver interface {
|
||||
LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, error)
|
||||
}
|
||||
|
||||
// srv is a service locator for fastcgi upstreams
|
||||
type srv struct {
|
||||
resolver srvResolver
|
||||
service string
|
||||
}
|
||||
|
||||
// Address looks up the service and returns the address:port
|
||||
// from first result in resolved list.
|
||||
// No explicit balancing is required because net.LookupSRV
|
||||
// sorts the results by priority and randomizes within priority.
|
||||
func (s *srv) Address() (string, error) {
|
||||
_, addrs, err := s.resolver.LookupSRV(context.Background(), "", "", s.service)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", strings.TrimRight(addrs[0].Target, "."), addrs[0].Port), nil
|
||||
}
|
||||
|
||||
// canSplit checks if path can split into two based on rule.SplitPath.
|
||||
func (r Rule) canSplit(path string) bool {
|
||||
return r.splitPos(path) >= 0
|
||||
}
|
||||
|
||||
// splitPos returns the index where path should be split
|
||||
// based on rule.SplitPath.
|
||||
func (r Rule) splitPos(path string) int {
|
||||
if httpserver.CaseSensitivePath {
|
||||
return strings.Index(path, r.SplitPath)
|
||||
}
|
||||
return strings.Index(strings.ToLower(path), strings.ToLower(r.SplitPath))
|
||||
}
|
||||
|
||||
// AllowedPath checks if requestPath is not an ignored path.
|
||||
func (r Rule) AllowedPath(requestPath string) bool {
|
||||
for _, ignoredSubPath := range r.IgnoredSubPaths {
|
||||
if httpserver.Path(path.Clean(requestPath)).Matches(path.Join(r.Path, ignoredSubPath)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
|
||||
// ErrIndexMissingSplit describes an index configuration error.
|
||||
ErrIndexMissingSplit = errors.New("configured index file(s) must include split value")
|
||||
)
|
||||
|
||||
// LogError is a non fatal error that allows requests to go through.
|
||||
type LogError string
|
||||
|
||||
// Error satisfies error interface.
|
||||
func (l LogError) Error() string {
|
||||
return string(l)
|
||||
}
|
||||
|
||||
// Map of supported protocols to Apache ssl_mod format
|
||||
// Note that these are slightly different from SupportedProtocols in caddytls/config.go's
|
||||
var tlsProtocolStringToMap = map[uint16]string{
|
||||
tls.VersionTLS10: "TLSv1",
|
||||
tls.VersionTLS11: "TLSv1.1",
|
||||
tls.VersionTLS12: "TLSv1.2",
|
||||
}
|
||||
79
vendor/github.com/mholt/caddy/caddyhttp/fastcgi/fcgi_test.php
generated
vendored
Normal file
79
vendor/github.com/mholt/caddy/caddyhttp/fastcgi/fcgi_test.php
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
ini_set("display_errors",1);
|
||||
|
||||
echo "resp: start\n";//.print_r($GLOBALS,1)."\n".print_r($_SERVER,1)."\n";
|
||||
|
||||
//echo print_r($_SERVER,1)."\n";
|
||||
|
||||
$length = 0;
|
||||
$stat = "PASSED";
|
||||
|
||||
$ret = "[";
|
||||
|
||||
if (count($_POST) || count($_FILES)) {
|
||||
foreach($_POST as $key => $val) {
|
||||
$md5 = md5($val);
|
||||
|
||||
if ($key != $md5) {
|
||||
$stat = "FAILED";
|
||||
echo "server:err ".$md5." != ".$key."\n";
|
||||
}
|
||||
|
||||
$length += strlen($key) + strlen($val);
|
||||
|
||||
$ret .= $key."(".strlen($key).") ";
|
||||
}
|
||||
$ret .= "] [";
|
||||
foreach ($_FILES as $k0 => $val) {
|
||||
|
||||
$error = $val["error"];
|
||||
if ($error == UPLOAD_ERR_OK) {
|
||||
$tmp_name = $val["tmp_name"];
|
||||
$name = $val["name"];
|
||||
$datafile = "/tmp/test.go";
|
||||
move_uploaded_file($tmp_name, $datafile);
|
||||
$md5 = md5_file($datafile);
|
||||
|
||||
if ($k0 != $md5) {
|
||||
$stat = "FAILED";
|
||||
echo "server:err ".$md5." != ".$key."\n";
|
||||
}
|
||||
|
||||
$length += strlen($k0) + filesize($datafile);
|
||||
|
||||
unlink($datafile);
|
||||
$ret .= $k0."(".strlen($k0).") ";
|
||||
}
|
||||
else{
|
||||
$stat = "FAILED";
|
||||
echo "server:file err ".file_upload_error_message($error)."\n";
|
||||
}
|
||||
}
|
||||
$ret .= "]";
|
||||
echo "server:got data length " .$length."\n";
|
||||
}
|
||||
|
||||
|
||||
echo "-{$stat}-POST(".count($_POST).") FILE(".count($_FILES).")\n";
|
||||
|
||||
function file_upload_error_message($error_code) {
|
||||
switch ($error_code) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
return 'The uploaded file exceeds the upload_max_filesize directive in php.ini';
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form';
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
return 'The uploaded file was only partially uploaded';
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
return 'No file was uploaded';
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
return 'Missing a temporary folder';
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
return 'Failed to write file to disk';
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
return 'File upload stopped by extension';
|
||||
default:
|
||||
return 'Unknown upload error';
|
||||
}
|
||||
}
|
||||
580
vendor/github.com/mholt/caddy/caddyhttp/fastcgi/fcgiclient.go
generated
vendored
Normal file
580
vendor/github.com/mholt/caddy/caddyhttp/fastcgi/fcgiclient.go
generated
vendored
Normal file
@@ -0,0 +1,580 @@
|
||||
// Copyright 2015 Light Code Labs, LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Forked Jan. 2015 from http://bitbucket.org/PinIdea/fcgi_client
|
||||
// (which is forked from https://code.google.com/p/go-fastcgi-client/)
|
||||
|
||||
// This fork contains several fixes and improvements by Matt Holt and
|
||||
// other contributors to this project.
|
||||
|
||||
// Copyright 2012 Junqing Tan <ivan@mysqlab.net> and The Go Authors
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// Part of source code is from Go fcgi package
|
||||
|
||||
package fastcgi
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FCGIListenSockFileno describes listen socket file number.
|
||||
const FCGIListenSockFileno uint8 = 0
|
||||
|
||||
// FCGIHeaderLen describes header length.
|
||||
const FCGIHeaderLen uint8 = 8
|
||||
|
||||
// Version1 describes the version.
|
||||
const Version1 uint8 = 1
|
||||
|
||||
// FCGINullRequestID describes the null request ID.
|
||||
const FCGINullRequestID uint8 = 0
|
||||
|
||||
// FCGIKeepConn describes keep connection mode.
|
||||
const FCGIKeepConn 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
|
||||
)
|
||||
|
||||
type header struct {
|
||||
Version uint8
|
||||
Type uint8
|
||||
ID uint16
|
||||
ContentLength uint16
|
||||
PaddingLength uint8
|
||||
Reserved uint8
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
func (h *header) init(recType uint8, reqID uint16, contentLength int) {
|
||||
h.Version = 1
|
||||
h.Type = recType
|
||||
h.ID = reqID
|
||||
h.ContentLength = uint16(contentLength)
|
||||
h.PaddingLength = uint8(-contentLength & 7)
|
||||
}
|
||||
|
||||
type record struct {
|
||||
h header
|
||||
rbuf []byte
|
||||
}
|
||||
|
||||
func (rec *record) read(r io.Reader) (buf []byte, err error) {
|
||||
if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil {
|
||||
return
|
||||
}
|
||||
if rec.h.Version != 1 {
|
||||
err = errors.New("fcgi: invalid header version")
|
||||
return
|
||||
}
|
||||
if rec.h.Type == EndRequest {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
n := int(rec.h.ContentLength) + int(rec.h.PaddingLength)
|
||||
if len(rec.rbuf) < n {
|
||||
rec.rbuf = make([]byte, n)
|
||||
}
|
||||
if _, err = io.ReadFull(r, rec.rbuf[:n]); err != nil {
|
||||
return
|
||||
}
|
||||
buf = rec.rbuf[:int(rec.h.ContentLength)]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FCGIClient implements a FastCGI client, which is a standard for
|
||||
// interfacing external applications with Web servers.
|
||||
type FCGIClient struct {
|
||||
mutex sync.Mutex
|
||||
rwc io.ReadWriteCloser
|
||||
h header
|
||||
buf bytes.Buffer
|
||||
stderr bytes.Buffer
|
||||
keepAlive bool
|
||||
reqID uint16
|
||||
readTimeout time.Duration
|
||||
sendTimeout time.Duration
|
||||
}
|
||||
|
||||
// DialWithDialerContext connects to the fcgi responder at the specified network address, using custom net.Dialer
|
||||
// and a context.
|
||||
// See func net.Dial for a description of the network and address parameters.
|
||||
func DialWithDialerContext(ctx context.Context, network, address string, dialer net.Dialer) (fcgi *FCGIClient, err error) {
|
||||
var conn net.Conn
|
||||
conn, err = dialer.DialContext(ctx, network, address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fcgi = &FCGIClient{
|
||||
rwc: conn,
|
||||
keepAlive: false,
|
||||
reqID: 1,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DialContext is like Dial but passes ctx to dialer.Dial.
|
||||
func DialContext(ctx context.Context, network, address string) (fcgi *FCGIClient, err error) {
|
||||
return DialWithDialerContext(ctx, network, address, net.Dialer{})
|
||||
}
|
||||
|
||||
// Dial connects to the fcgi responder at the specified network address, using default net.Dialer.
|
||||
// See func net.Dial for a description of the network and address parameters.
|
||||
func Dial(network, address string) (fcgi *FCGIClient, err error) {
|
||||
return DialContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
// Close closes fcgi connection
|
||||
func (c *FCGIClient) Close() {
|
||||
c.rwc.Close()
|
||||
}
|
||||
|
||||
func (c *FCGIClient) writeRecord(recType uint8, content []byte) (err error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.buf.Reset()
|
||||
c.h.init(recType, c.reqID, len(content))
|
||||
if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.buf.Write(content); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.rwc.Write(c.buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *FCGIClient) writeBeginRequest(role uint16, flags uint8) error {
|
||||
b := [8]byte{byte(role >> 8), byte(role), flags}
|
||||
return c.writeRecord(BeginRequest, b[:])
|
||||
}
|
||||
|
||||
func (c *FCGIClient) writeEndRequest(appStatus int, protocolStatus uint8) error {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint32(b, uint32(appStatus))
|
||||
b[4] = protocolStatus
|
||||
return c.writeRecord(EndRequest, b)
|
||||
}
|
||||
|
||||
func (c *FCGIClient) writePairs(recType uint8, pairs map[string]string) error {
|
||||
w := newWriter(c, recType)
|
||||
b := make([]byte, 8)
|
||||
nn := 0
|
||||
for k, v := range pairs {
|
||||
m := 8 + len(k) + len(v)
|
||||
if m > maxWrite {
|
||||
// param data size exceed 65535 bytes"
|
||||
vl := maxWrite - 8 - len(k)
|
||||
v = v[:vl]
|
||||
}
|
||||
n := encodeSize(b, uint32(len(k)))
|
||||
n += encodeSize(b[n:], uint32(len(v)))
|
||||
m = n + len(k) + len(v)
|
||||
if (nn + m) > maxWrite {
|
||||
w.Flush()
|
||||
nn = 0
|
||||
}
|
||||
nn += m
|
||||
if _, err := w.Write(b[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.WriteString(k); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.WriteString(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeSize(b []byte, size uint32) int {
|
||||
if size > 127 {
|
||||
size |= 1 << 31
|
||||
binary.BigEndian.PutUint32(b, size)
|
||||
return 4
|
||||
}
|
||||
b[0] = byte(size)
|
||||
return 1
|
||||
}
|
||||
|
||||
// bufWriter encapsulates bufio.Writer but also closes the underlying stream when
|
||||
// Closed.
|
||||
type bufWriter struct {
|
||||
closer io.Closer
|
||||
*bufio.Writer
|
||||
}
|
||||
|
||||
func (w *bufWriter) Close() error {
|
||||
if err := w.Writer.Flush(); err != nil {
|
||||
w.closer.Close()
|
||||
return err
|
||||
}
|
||||
return w.closer.Close()
|
||||
}
|
||||
|
||||
func newWriter(c *FCGIClient, recType uint8) *bufWriter {
|
||||
s := &streamWriter{c: c, recType: recType}
|
||||
w := bufio.NewWriterSize(s, maxWrite)
|
||||
return &bufWriter{s, w}
|
||||
}
|
||||
|
||||
// streamWriter abstracts out the separation of a stream into discrete records.
|
||||
// It only writes maxWrite bytes at a time.
|
||||
type streamWriter struct {
|
||||
c *FCGIClient
|
||||
recType uint8
|
||||
}
|
||||
|
||||
func (w *streamWriter) Write(p []byte) (int, error) {
|
||||
nn := 0
|
||||
for len(p) > 0 {
|
||||
n := len(p)
|
||||
if n > maxWrite {
|
||||
n = maxWrite
|
||||
}
|
||||
if err := w.c.writeRecord(w.recType, p[:n]); err != nil {
|
||||
return nn, err
|
||||
}
|
||||
nn += n
|
||||
p = p[n:]
|
||||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
func (w *streamWriter) Close() error {
|
||||
// send empty record to close the stream
|
||||
return w.c.writeRecord(w.recType, nil)
|
||||
}
|
||||
|
||||
type streamReader struct {
|
||||
c *FCGIClient
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (w *streamReader) Read(p []byte) (n int, err error) {
|
||||
|
||||
if len(p) > 0 {
|
||||
if len(w.buf) == 0 {
|
||||
|
||||
// filter outputs for error log
|
||||
for {
|
||||
rec := &record{}
|
||||
var buf []byte
|
||||
buf, err = rec.read(w.c.rwc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// standard error output
|
||||
if rec.h.Type == Stderr {
|
||||
w.c.stderr.Write(buf)
|
||||
continue
|
||||
}
|
||||
w.buf = buf
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
n = len(p)
|
||||
if n > len(w.buf) {
|
||||
n = len(w.buf)
|
||||
}
|
||||
copy(p, w.buf[:n])
|
||||
w.buf = w.buf[n:]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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 *FCGIClient) Do(p map[string]string, req io.Reader) (r io.Reader, err error) {
|
||||
err = c.writeBeginRequest(uint16(Responder), 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = c.writePairs(Params, p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body := newWriter(c, Stdin)
|
||||
if req != nil {
|
||||
io.Copy(body, req)
|
||||
}
|
||||
body.Close()
|
||||
|
||||
r = &streamReader{c: c}
|
||||
return
|
||||
}
|
||||
|
||||
// clientCloser is a io.ReadCloser. It wraps a io.Reader with a Closer
|
||||
// that closes FCGIClient connection.
|
||||
type clientCloser struct {
|
||||
*FCGIClient
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func (f clientCloser) Close() error { return f.rwc.Close() }
|
||||
|
||||
// Request returns a HTTP Response with Header and Body
|
||||
// from fcgi responder
|
||||
func (c *FCGIClient) 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") != "" {
|
||||
statusParts := strings.SplitN(resp.Header.Get("Status"), " ", 2)
|
||||
resp.StatusCode, err = strconv.Atoi(statusParts[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(statusParts) > 1 {
|
||||
resp.Status = statusParts[1]
|
||||
}
|
||||
|
||||
} else {
|
||||
resp.StatusCode = http.StatusOK
|
||||
}
|
||||
|
||||
// TODO: fixTransferEncoding ?
|
||||
resp.TransferEncoding = resp.Header["Transfer-Encoding"]
|
||||
resp.ContentLength, _ = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||
|
||||
if chunked(resp.TransferEncoding) {
|
||||
resp.Body = clientCloser{c, httputil.NewChunkedReader(rb)}
|
||||
} else {
|
||||
resp.Body = clientCloser{c, ioutil.NopCloser(rb)}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get issues a GET request to the fcgi responder.
|
||||
func (c *FCGIClient) 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)
|
||||
}
|
||||
|
||||
// Head issues a HEAD request to the fcgi responder.
|
||||
func (c *FCGIClient) Head(p map[string]string) (resp *http.Response, err error) {
|
||||
|
||||
p["REQUEST_METHOD"] = "HEAD"
|
||||
p["CONTENT_LENGTH"] = "0"
|
||||
|
||||
return c.Request(p, nil)
|
||||
}
|
||||
|
||||
// Options issues an OPTIONS request to the fcgi responder.
|
||||
func (c *FCGIClient) Options(p map[string]string) (resp *http.Response, err error) {
|
||||
|
||||
p["REQUEST_METHOD"] = "OPTIONS"
|
||||
p["CONTENT_LENGTH"] = "0"
|
||||
|
||||
return c.Request(p, nil)
|
||||
}
|
||||
|
||||
// Post issues a POST request to the fcgi responder. with request body
|
||||
// in the format that bodyType specified
|
||||
func (c *FCGIClient) 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)
|
||||
}
|
||||
|
||||
// PostForm issues a POST to the fcgi responder, with form
|
||||
// as a string key to a list values (url.Values)
|
||||
func (c *FCGIClient) 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()))
|
||||
}
|
||||
|
||||
// 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 *FCGIClient) 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()))
|
||||
}
|
||||
|
||||
// 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 *FCGIClient) SetReadTimeout(t time.Duration) error {
|
||||
if conn, ok := c.rwc.(net.Conn); ok && t != 0 {
|
||||
return conn.SetReadDeadline(time.Now().Add(t))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSendTimeout sets the read timeout for future calls that send data to
|
||||
// the fcgi responder. A zero value for t means no timeout will be set.
|
||||
func (c *FCGIClient) SetSendTimeout(t time.Duration) error {
|
||||
if conn, ok := c.rwc.(net.Conn); ok && t != 0 {
|
||||
return conn.SetWriteDeadline(time.Now().Add(t))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks whether chunked is part of the encodings stack
|
||||
func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }
|
||||
220
vendor/github.com/mholt/caddy/caddyhttp/fastcgi/setup.go
generated
vendored
Normal file
220
vendor/github.com/mholt/caddy/caddyhttp/fastcgi/setup.go
generated
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
// Copyright 2015 Light Code Labs, LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fastcgi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/caddy"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
)
|
||||
|
||||
var defaultTimeout = 60 * time.Second
|
||||
|
||||
func init() {
|
||||
caddy.RegisterPlugin("fastcgi", caddy.Plugin{
|
||||
ServerType: "http",
|
||||
Action: setup,
|
||||
})
|
||||
}
|
||||
|
||||
// setup configures a new FastCGI middleware instance.
|
||||
func setup(c *caddy.Controller) error {
|
||||
cfg := httpserver.GetConfig(c)
|
||||
|
||||
rules, err := fastcgiParse(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
||||
return Handler{
|
||||
Next: next,
|
||||
Rules: rules,
|
||||
Root: cfg.Root,
|
||||
FileSys: http.Dir(cfg.Root),
|
||||
SoftwareName: caddy.AppName,
|
||||
SoftwareVersion: caddy.AppVersion,
|
||||
ServerName: cfg.Addr.Host,
|
||||
ServerPort: cfg.Addr.Port,
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
|
||||
var rules []Rule
|
||||
|
||||
cfg := httpserver.GetConfig(c)
|
||||
absRoot, err := filepath.Abs(cfg.Root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for c.Next() {
|
||||
args := c.RemainingArgs()
|
||||
|
||||
if len(args) < 2 || len(args) > 3 {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
|
||||
rule := Rule{
|
||||
Root: absRoot,
|
||||
Path: args[0],
|
||||
ConnectTimeout: defaultTimeout,
|
||||
ReadTimeout: defaultTimeout,
|
||||
SendTimeout: defaultTimeout,
|
||||
}
|
||||
|
||||
upstreams := []string{args[1]}
|
||||
|
||||
srvUpstream := false
|
||||
if strings.HasPrefix(upstreams[0], "srv://") {
|
||||
srvUpstream = true
|
||||
}
|
||||
|
||||
if len(args) == 3 {
|
||||
if err := fastcgiPreset(args[2], &rule); err != nil {
|
||||
return rules, err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
for c.NextBlock() {
|
||||
switch c.Val() {
|
||||
case "root":
|
||||
if !c.NextArg() {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
rule.Root = c.Val()
|
||||
|
||||
case "ext":
|
||||
if !c.NextArg() {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
rule.Ext = c.Val()
|
||||
case "split":
|
||||
if !c.NextArg() {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
rule.SplitPath = c.Val()
|
||||
case "index":
|
||||
args := c.RemainingArgs()
|
||||
if len(args) == 0 {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
rule.IndexFiles = args
|
||||
|
||||
case "upstream":
|
||||
if srvUpstream {
|
||||
return rules, c.Err("additional upstreams are not supported with SRV upstream")
|
||||
}
|
||||
|
||||
args := c.RemainingArgs()
|
||||
|
||||
if len(args) != 1 {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
|
||||
upstreams = append(upstreams, args[0])
|
||||
case "env":
|
||||
envArgs := c.RemainingArgs()
|
||||
if len(envArgs) < 2 {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
rule.EnvVars = append(rule.EnvVars, [2]string{envArgs[0], envArgs[1]})
|
||||
case "except":
|
||||
ignoredPaths := c.RemainingArgs()
|
||||
if len(ignoredPaths) == 0 {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
rule.IgnoredSubPaths = ignoredPaths
|
||||
|
||||
case "connect_timeout":
|
||||
if !c.NextArg() {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
rule.ConnectTimeout, err = time.ParseDuration(c.Val())
|
||||
if err != nil {
|
||||
return rules, err
|
||||
}
|
||||
case "read_timeout":
|
||||
if !c.NextArg() {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
readTimeout, err := time.ParseDuration(c.Val())
|
||||
if err != nil {
|
||||
return rules, err
|
||||
}
|
||||
rule.ReadTimeout = readTimeout
|
||||
case "send_timeout":
|
||||
if !c.NextArg() {
|
||||
return rules, c.ArgErr()
|
||||
}
|
||||
sendTimeout, err := time.ParseDuration(c.Val())
|
||||
if err != nil {
|
||||
return rules, err
|
||||
}
|
||||
rule.SendTimeout = sendTimeout
|
||||
}
|
||||
}
|
||||
|
||||
if srvUpstream {
|
||||
balancer, err := parseSRV(upstreams[0])
|
||||
if err != nil {
|
||||
return rules, c.Err("malformed service locator string: " + err.Error())
|
||||
}
|
||||
rule.balancer = balancer
|
||||
} else {
|
||||
rule.balancer = &roundRobin{addresses: upstreams, index: -1}
|
||||
}
|
||||
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func parseSRV(locator string) (*srv, error) {
|
||||
if locator[6:] == "" {
|
||||
return nil, fmt.Errorf("%s does not include the host", locator)
|
||||
}
|
||||
|
||||
return &srv{
|
||||
service: locator[6:],
|
||||
resolver: &net.Resolver{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// fastcgiPreset configures rule according to name. It returns an error if
|
||||
// name is not a recognized preset name.
|
||||
func fastcgiPreset(name string, rule *Rule) error {
|
||||
switch name {
|
||||
case "php":
|
||||
rule.Ext = ".php"
|
||||
rule.SplitPath = ".php"
|
||||
rule.IndexFiles = []string{"index.php"}
|
||||
default:
|
||||
return errors.New(name + " is not a valid preset name")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user