diff --git a/middleware/fastcgi/fastcgi.go b/middleware/fastcgi/fastcgi.go index 844e5dc9..e38e9a26 100644 --- a/middleware/fastcgi/fastcgi.go +++ b/middleware/fastcgi/fastcgi.go @@ -1,6 +1,6 @@ // 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 layer is to serve PHP websites via php-fpm. +// The most common use for this package is to serve PHP websites via php-fpm. package fastcgi import ( @@ -17,95 +17,114 @@ import ( func New(c middleware.Controller) (middleware.Middleware, error) { root := c.Root() - var rules []fastCgi + rules, err := parse(c) + if err != nil { + return nil, err + } + + return func(next middleware.Handler) middleware.Handler { + return Handler{Next: next, Rules: rules, Root: root} + }, nil +} + +// Handler is a middleware type that can handle requests as a FastCGI client. +type Handler struct { + Next middleware.Handler + Root string + Rules []Rule +} + +// ServeHTTP satisfies the middleware.Handler interface. +func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { + servedFcgi := false + for _, rule := range h.Rules { + if middleware.Path(r.URL.Path).Matches(rule.Path) { + servedFcgi = true + + // Get absolute file paths + absPath, err := filepath.Abs(h.Root + r.URL.Path) + if err != nil { + return http.StatusInternalServerError, err + } + + // Get absolute file path to website root + absRootPath, err := filepath.Abs(h.Root) + if err != nil { + return http.StatusInternalServerError, err + } + + // Separate remote IP and port + var ip, port string + if idx := strings.Index(r.RemoteAddr, ":"); idx > -1 { + ip = r.RemoteAddr[idx:] + port = r.RemoteAddr[:idx] + } else { + ip = r.RemoteAddr + } + + // TODO: Do we really have to make this map from scratch for each request? + // TODO: We have quite a few more to map, too. + env := make(map[string]string) + env["SERVER_SOFTWARE"] = "caddy" // TODO: Obtain version info... + env["SERVER_PROTOCOL"] = r.Proto + env["SCRIPT_FILENAME"] = absPath + env["REMOTE_ADDR"] = ip + env["REMOTE_PORT"] = port + env["REQUEST_METHOD"] = r.Method + env["QUERY_STRING"] = r.URL.RawQuery + env["DOCUMENT_URI"] = r.URL.Path + env["DOCUMENT_ROOT"] = absRootPath + + fcgi, err := Dial("tcp", rule.Address) + if err != nil { + return http.StatusBadGateway, err + } + + resp, err := fcgi.Get(env) + if err != nil && err != io.EOF { + return http.StatusBadGateway, err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return http.StatusBadGateway, err + } + + for key, vals := range resp.Header { + for _, val := range vals { + w.Header().Add(key, val) + } + } + + w.WriteHeader(resp.StatusCode) + w.Write(body) + + break + } + } + + if !servedFcgi { + return h.Next.ServeHTTP(w, r) + } + + return 0, nil +} + +func parse(c middleware.Controller) ([]Rule, error) { + var rules []Rule + for c.Next() { - rule := fastCgi{} - if !c.Args(&rule.path, &rule.address) { - return nil, c.ArgErr() + var rule Rule + if !c.Args(&rule.Path, &rule.Address) { + return rules, c.ArgErr() } rules = append(rules, rule) } - return func(next middleware.Handler) middleware.Handler { - return middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - - servedFcgi := false - for _, rule := range rules { - if middleware.Path(r.URL.Path).Matches(rule.path) { - servedFcgi = true - - // Get absolute file paths - absPath, err := filepath.Abs(root + r.URL.Path) - if err != nil { - return http.StatusInternalServerError, err - } - - // Get absolute file path to website root - absRootPath, err := filepath.Abs(root) - if err != nil { - return http.StatusInternalServerError, err - } - - // Separate remote IP and port - var ip, port string - if idx := strings.Index(r.RemoteAddr, ":"); idx > -1 { - ip = r.RemoteAddr[idx:] - port = r.RemoteAddr[:idx] - } else { - ip = r.RemoteAddr - } - - // TODO: Do we really have to make this map from scratch for each request? - // TODO: We have quite a few more to map, too. - env := make(map[string]string) - env["SERVER_SOFTWARE"] = "caddy" // TODO: Obtain version info... - env["SERVER_PROTOCOL"] = r.Proto - env["SCRIPT_FILENAME"] = absPath - env["REMOTE_ADDR"] = ip - env["REMOTE_PORT"] = port - env["REQUEST_METHOD"] = r.Method - env["QUERY_STRING"] = r.URL.RawQuery - env["DOCUMENT_URI"] = r.URL.Path - env["DOCUMENT_ROOT"] = absRootPath - - fcgi, err := Dial("tcp", rule.address) - if err != nil { - return http.StatusBadGateway, err - } - - resp, err := fcgi.Get(env) - if err != nil && err != io.EOF { - return http.StatusBadGateway, err - } - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return http.StatusBadGateway, err - } - - for key, vals := range resp.Header { - for _, val := range vals { - w.Header().Add(key, val) - } - } - - w.WriteHeader(resp.StatusCode) - w.Write(body) - - break - } - } - - if !servedFcgi { - return next.ServeHTTP(w, r) - } - - return 0, nil - }) - }, nil + return rules, nil } -type fastCgi struct { - path string - address string +// Rule represents a FastCGI handling rule. +type Rule struct { + Path, Address string } diff --git a/middleware/redirect/redirect.go b/middleware/redirect/redirect.go index 96ff1d0e..a4db80b0 100644 --- a/middleware/redirect/redirect.go +++ b/middleware/redirect/redirect.go @@ -9,7 +9,7 @@ import ( "github.com/mholt/caddy/middleware" ) -// New instantiates a new Rewrites middleware. +// New instantiates a new Redirect middleware. func New(c middleware.Controller) (middleware.Middleware, error) { rules, err := parse(c) if err != nil {