mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-29 05:15:59 +03:00
Expanded index file support to other middlewares (fixes #27)
This commit is contained in:
parent
a9064a7871
commit
47717fee88
7 changed files with 117 additions and 52 deletions
|
@ -2,6 +2,7 @@ package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
|
@ -10,7 +11,7 @@ import (
|
||||||
|
|
||||||
// FastCGI configures a new FastCGI middleware instance.
|
// FastCGI configures a new FastCGI middleware instance.
|
||||||
func FastCGI(c *Controller) (middleware.Middleware, error) {
|
func FastCGI(c *Controller) (middleware.Middleware, error) {
|
||||||
root, err := filepath.Abs(c.Root)
|
absRoot, err := filepath.Abs(c.Root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -24,7 +25,9 @@ func FastCGI(c *Controller) (middleware.Middleware, error) {
|
||||||
return fastcgi.Handler{
|
return fastcgi.Handler{
|
||||||
Next: next,
|
Next: next,
|
||||||
Rules: rules,
|
Rules: rules,
|
||||||
Root: root,
|
Root: c.Root,
|
||||||
|
AbsRoot: absRoot,
|
||||||
|
FileSys: http.Dir(c.Root),
|
||||||
SoftwareName: c.AppName,
|
SoftwareName: c.AppName,
|
||||||
SoftwareVersion: c.AppVersion,
|
SoftwareVersion: c.AppVersion,
|
||||||
ServerName: c.Host,
|
ServerName: c.Host,
|
||||||
|
@ -72,10 +75,11 @@ func fastcgiParse(c *Controller) ([]fastcgi.Rule, error) {
|
||||||
}
|
}
|
||||||
rule.SplitPath = c.Val()
|
rule.SplitPath = c.Val()
|
||||||
case "index":
|
case "index":
|
||||||
if !c.NextArg() {
|
args := c.RemainingArgs()
|
||||||
|
if len(args) == 0 {
|
||||||
return rules, c.ArgErr()
|
return rules, c.ArgErr()
|
||||||
}
|
}
|
||||||
rule.IndexFile = c.Val()
|
rule.IndexFiles = args
|
||||||
case "env":
|
case "env":
|
||||||
envArgs := c.RemainingArgs()
|
envArgs := c.RemainingArgs()
|
||||||
if len(envArgs) < 2 {
|
if len(envArgs) < 2 {
|
||||||
|
@ -98,7 +102,7 @@ func fastcgiPreset(name string, rule *fastcgi.Rule) error {
|
||||||
case "php":
|
case "php":
|
||||||
rule.Ext = ".php"
|
rule.Ext = ".php"
|
||||||
rule.SplitPath = ".php"
|
rule.SplitPath = ".php"
|
||||||
rule.IndexFile = "index.php"
|
rule.IndexFiles = []string{"index.php"}
|
||||||
default:
|
default:
|
||||||
return errors.New(name + " is not a valid preset name")
|
return errors.New(name + " is not a valid preset name")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package setup
|
package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
"github.com/mholt/caddy/middleware/markdown"
|
"github.com/mholt/caddy/middleware/markdown"
|
||||||
"github.com/russross/blackfriday"
|
"github.com/russross/blackfriday"
|
||||||
|
@ -15,7 +17,9 @@ func Markdown(c *Controller) (middleware.Middleware, error) {
|
||||||
|
|
||||||
md := markdown.Markdown{
|
md := markdown.Markdown{
|
||||||
Root: c.Root,
|
Root: c.Root,
|
||||||
|
FileSys: http.Dir(c.Root),
|
||||||
Configs: mdconfigs,
|
Configs: mdconfigs,
|
||||||
|
IndexFiles: []string{"index.md"},
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package setup
|
package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
"github.com/mholt/caddy/middleware/templates"
|
"github.com/mholt/caddy/middleware/templates"
|
||||||
)
|
)
|
||||||
|
@ -13,8 +15,9 @@ func Templates(c *Controller) (middleware.Middleware, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpls := templates.Templates{
|
tmpls := templates.Templates{
|
||||||
Root: c.Root,
|
|
||||||
Rules: rules,
|
Rules: rules,
|
||||||
|
Root: c.Root,
|
||||||
|
FileSys: http.Dir(c.Root),
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
@ -43,6 +46,10 @@ func templatesParse(c *Controller) ([]templates.Rule, error) {
|
||||||
rule.Extensions = defaultExtensions
|
rule.Extensions = defaultExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ext := range rule.Extensions {
|
||||||
|
rule.IndexFiles = append(rule.IndexFiles, "index"+ext)
|
||||||
|
}
|
||||||
|
|
||||||
rules = append(rules, rule)
|
rules = append(rules, rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,4 +58,4 @@ func templatesParse(c *Controller) ([]templates.Rule, error) {
|
||||||
|
|
||||||
const defaultPath = "/"
|
const defaultPath = "/"
|
||||||
|
|
||||||
var defaultExtensions = []string{".html", ".htm", ".txt"}
|
var defaultExtensions = []string{".html", ".htm", ".tmpl", ".tpl", ".txt"}
|
||||||
|
|
|
@ -17,8 +17,10 @@ import (
|
||||||
// Handler is a middleware type that can handle requests as a FastCGI client.
|
// Handler is a middleware type that can handle requests as a FastCGI client.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
Root string // must be absolute path to site root
|
|
||||||
Rules []Rule
|
Rules []Rule
|
||||||
|
Root string
|
||||||
|
AbsRoot string // same as root, but absolute path
|
||||||
|
FileSys http.FileSystem
|
||||||
|
|
||||||
// These are sent to CGI scripts in env variables
|
// These are sent to CGI scripts in env variables
|
||||||
SoftwareName string
|
SoftwareName string
|
||||||
|
@ -30,26 +32,27 @@ type Handler struct {
|
||||||
// ServeHTTP satisfies the middleware.Handler interface.
|
// ServeHTTP satisfies the middleware.Handler interface.
|
||||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
for _, rule := range h.Rules {
|
for _, rule := range h.Rules {
|
||||||
|
|
||||||
|
// First requirement: Base path must match
|
||||||
|
if !middleware.Path(r.URL.Path).Matches(rule.Path) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// In addition to matching the path, a request must meet some
|
// In addition to matching the path, a request must meet some
|
||||||
// other criteria before being proxied as FastCGI. For example,
|
// other criteria before being proxied as FastCGI. For example,
|
||||||
// we probably want to exclude static assets (CSS, JS, images...)
|
// we probably want to exclude static assets (CSS, JS, images...)
|
||||||
// but we also want to be flexible for the script we proxy to.
|
// but we also want to be flexible for the script we proxy to.
|
||||||
|
|
||||||
path := r.URL.Path
|
fpath := r.URL.Path
|
||||||
|
if idx, ok := middleware.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok {
|
||||||
// These criteria work well in this order for PHP sites
|
fpath = idx
|
||||||
if middleware.Path(path).Matches(rule.Path) &&
|
|
||||||
(path[len(path)-1] == '/' ||
|
|
||||||
strings.HasSuffix(path, rule.Ext) ||
|
|
||||||
!h.exists(path)) {
|
|
||||||
|
|
||||||
if path[len(path)-1] == '/' && h.exists(path+rule.IndexFile) {
|
|
||||||
// If index file in specified folder exists, send request to it
|
|
||||||
path += rule.IndexFile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These criteria work well in this order for PHP sites
|
||||||
|
if fpath[len(fpath)-1] == '/' || strings.HasSuffix(fpath, rule.Ext) || !h.exists(fpath) {
|
||||||
|
|
||||||
// Create environment for CGI script
|
// Create environment for CGI script
|
||||||
env, err := h.buildEnv(r, rule, path)
|
env, err := h.buildEnv(r, rule, fpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
@ -115,14 +118,12 @@ func (h Handler) exists(path string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]string, error) {
|
// 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
|
var env map[string]string
|
||||||
|
|
||||||
// Get absolute path of requested resource
|
// Get absolute path of requested resource
|
||||||
absPath, err := filepath.Abs(h.Root + path)
|
absPath := filepath.Join(h.AbsRoot, fpath)
|
||||||
if err != nil {
|
|
||||||
return env, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Separate remote IP and port; more lenient than net.SplitHostPort
|
// Separate remote IP and port; more lenient than net.SplitHostPort
|
||||||
var ip, port string
|
var ip, port string
|
||||||
|
@ -134,19 +135,19 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split path in preparation for env variables
|
// Split path in preparation for env variables
|
||||||
splitPos := strings.Index(path, rule.SplitPath)
|
splitPos := strings.Index(fpath, rule.SplitPath)
|
||||||
var docURI, scriptName, scriptFilename, pathInfo string
|
var docURI, scriptName, scriptFilename, pathInfo string
|
||||||
if splitPos == -1 {
|
if splitPos == -1 {
|
||||||
// Request doesn't have the extension, so assume index file in root
|
// Request doesn't have the extension, so assume index file in root
|
||||||
docURI = "/" + rule.IndexFile
|
docURI = "/" + rule.IndexFiles[0]
|
||||||
scriptName = "/" + rule.IndexFile
|
scriptName = "/" + rule.IndexFiles[0]
|
||||||
scriptFilename = h.Root + "/" + rule.IndexFile
|
scriptFilename = filepath.Join(h.AbsRoot, rule.IndexFiles[0])
|
||||||
pathInfo = path
|
pathInfo = fpath
|
||||||
} else {
|
} else {
|
||||||
// Request has the extension; path was split successfully
|
// Request has the extension; path was split successfully
|
||||||
docURI = path[:splitPos+len(rule.SplitPath)]
|
docURI = fpath[:splitPos+len(rule.SplitPath)]
|
||||||
pathInfo = path[splitPos+len(rule.SplitPath):]
|
pathInfo = fpath[splitPos+len(rule.SplitPath):]
|
||||||
scriptName = path
|
scriptName = fpath
|
||||||
scriptFilename = absPath
|
scriptFilename = absPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +161,7 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]s
|
||||||
"CONTENT_TYPE": r.Header.Get("Content-Type"),
|
"CONTENT_TYPE": r.Header.Get("Content-Type"),
|
||||||
"GATEWAY_INTERFACE": "CGI/1.1",
|
"GATEWAY_INTERFACE": "CGI/1.1",
|
||||||
"PATH_INFO": pathInfo,
|
"PATH_INFO": pathInfo,
|
||||||
"PATH_TRANSLATED": h.Root + "/" + pathInfo, // Source for path_translated: http://www.oreilly.com/openbook/cgi/ch02_04.html
|
"PATH_TRANSLATED": filepath.Join(h.AbsRoot, pathInfo), // Info: http://www.oreilly.com/openbook/cgi/ch02_04.html
|
||||||
"QUERY_STRING": r.URL.RawQuery,
|
"QUERY_STRING": r.URL.RawQuery,
|
||||||
"REMOTE_ADDR": ip,
|
"REMOTE_ADDR": ip,
|
||||||
"REMOTE_HOST": ip, // For speed, remote host lookups disabled
|
"REMOTE_HOST": ip, // For speed, remote host lookups disabled
|
||||||
|
@ -174,7 +175,7 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]s
|
||||||
"SERVER_SOFTWARE": h.SoftwareName + "/" + h.SoftwareVersion,
|
"SERVER_SOFTWARE": h.SoftwareName + "/" + h.SoftwareVersion,
|
||||||
|
|
||||||
// Other variables
|
// Other variables
|
||||||
"DOCUMENT_ROOT": h.Root,
|
"DOCUMENT_ROOT": h.AbsRoot,
|
||||||
"DOCUMENT_URI": docURI,
|
"DOCUMENT_URI": docURI,
|
||||||
"HTTP_HOST": r.Host, // added here, since not always part of headers
|
"HTTP_HOST": r.Host, // added here, since not always part of headers
|
||||||
"REQUEST_URI": r.URL.RequestURI(),
|
"REQUEST_URI": r.URL.RequestURI(),
|
||||||
|
@ -214,8 +215,9 @@ type Rule struct {
|
||||||
// PATH_INFO for the CGI script to use.
|
// PATH_INFO for the CGI script to use.
|
||||||
SplitPath string
|
SplitPath string
|
||||||
|
|
||||||
// If the URL does not indicate a file, an index file with this name will be assumed.
|
// If the URL ends with '/' (which indicates a directory), these index
|
||||||
IndexFile string
|
// files will be tried instead.
|
||||||
|
IndexFiles []string
|
||||||
|
|
||||||
// Environment Variables
|
// Environment Variables
|
||||||
EnvVars [][2]string
|
EnvVars [][2]string
|
||||||
|
|
|
@ -20,11 +20,17 @@ type Markdown struct {
|
||||||
// Server root
|
// Server root
|
||||||
Root string
|
Root string
|
||||||
|
|
||||||
|
// Jail the requests to site root with a mock file system
|
||||||
|
FileSys http.FileSystem
|
||||||
|
|
||||||
// Next HTTP handler in the chain
|
// Next HTTP handler in the chain
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
|
|
||||||
// The list of markdown configurations
|
// The list of markdown configurations
|
||||||
Configs []Config
|
Configs []Config
|
||||||
|
|
||||||
|
// The list of index files to try
|
||||||
|
IndexFiles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config stores markdown middleware configurations.
|
// Config stores markdown middleware configurations.
|
||||||
|
@ -52,11 +58,14 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ext := range m.Extensions {
|
fpath := r.URL.Path
|
||||||
if strings.HasSuffix(r.URL.Path, ext) {
|
if idx, ok := middleware.IndexFile(md.FileSys, fpath, md.IndexFiles); ok {
|
||||||
fpath := md.Root + r.URL.Path
|
fpath = idx
|
||||||
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadFile(fpath)
|
for _, ext := range m.Extensions {
|
||||||
|
if strings.HasSuffix(fpath, ext) {
|
||||||
|
f, err := md.FileSys.Open(fpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsPermission(err) {
|
if os.IsPermission(err) {
|
||||||
return http.StatusForbidden, err
|
return http.StatusForbidden, err
|
||||||
|
@ -64,6 +73,11 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
||||||
return http.StatusNotFound, nil
|
return http.StatusNotFound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
content := blackfriday.Markdown(body, m.Renderer, 0)
|
content := blackfriday.Markdown(body, m.Renderer, 0)
|
||||||
|
|
||||||
var scripts, styles string
|
var scripts, styles string
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
// Package middleware provides some types and functions common among middleware.
|
// Package middleware provides some types and functions common among middleware.
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Middleware is the middle layer which represents the traditional
|
// Middleware is the middle layer which represents the traditional
|
||||||
|
@ -47,3 +50,24 @@ type (
|
||||||
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
return f(w, r)
|
return f(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IndexFile looks for a file in /root/fpath/indexFile for each string
|
||||||
|
// in indexFiles. If an index file is found, it returns the root-relative
|
||||||
|
// path to the file and true. If no index file is found, empty string
|
||||||
|
// and false is returned. fpath must end in a forward slash '/'
|
||||||
|
// otherwise no index files will be tried (directory paths must end
|
||||||
|
// in a forward slash according to HTTP).
|
||||||
|
func IndexFile(root http.FileSystem, fpath string, indexFiles []string) (string, bool) {
|
||||||
|
if fpath[len(fpath)-1] != '/' || root == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
for _, indexFile := range indexFiles {
|
||||||
|
fp := filepath.Join(fpath, indexFile)
|
||||||
|
f, err := root.Open(fp)
|
||||||
|
if err == nil {
|
||||||
|
f.Close()
|
||||||
|
return fp, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
|
@ -17,15 +18,22 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
reqExt := path.Ext(r.URL.Path)
|
// Check for index files
|
||||||
|
fpath := r.URL.Path
|
||||||
|
if idx, ok := middleware.IndexFile(t.FileSys, fpath, rule.IndexFiles); ok {
|
||||||
|
fpath = idx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the extension
|
||||||
|
reqExt := path.Ext(fpath)
|
||||||
|
|
||||||
for _, ext := range rule.Extensions {
|
for _, ext := range rule.Extensions {
|
||||||
if reqExt == ext {
|
if reqExt == ext {
|
||||||
// Create execution context
|
// Create execution context
|
||||||
ctx := context{root: http.Dir(t.Root), req: r, URL: r.URL}
|
ctx := context{root: t.FileSys, req: r, URL: r.URL}
|
||||||
|
|
||||||
// Build the template
|
// Build the template
|
||||||
tpl, err := template.ParseFiles(t.Root + r.URL.Path)
|
tpl, err := template.ParseFiles(filepath.Join(t.Root, fpath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
@ -49,8 +57,9 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
||||||
// Templates is middleware to render templated files as the HTTP response.
|
// Templates is middleware to render templated files as the HTTP response.
|
||||||
type Templates struct {
|
type Templates struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
Root string
|
|
||||||
Rules []Rule
|
Rules []Rule
|
||||||
|
Root string
|
||||||
|
FileSys http.FileSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule represents a template rule. A template will only execute
|
// Rule represents a template rule. A template will only execute
|
||||||
|
@ -59,4 +68,5 @@ type Templates struct {
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
Path string
|
Path string
|
||||||
Extensions []string
|
Extensions []string
|
||||||
|
IndexFiles []string
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue