Commit f7a70266 authored by Mohammed Al Sahaf's avatar Mohammed Al Sahaf Committed by Toby Allen

Implement per-site index (#1906)

parent fc75527e
...@@ -252,7 +252,7 @@ func directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, config ...@@ -252,7 +252,7 @@ func directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, config
for _, f := range files { for _, f := range files {
name := f.Name() name := f.Name()
for _, indexName := range staticfiles.IndexPages { for _, indexName := range config.Fs.IndexPages {
if name == indexName { if name == indexName {
hasIndexFile = true hasIndexFile = true
break break
......
...@@ -78,8 +78,9 @@ func browseParse(c *caddy.Controller) ([]Config, error) { ...@@ -78,8 +78,9 @@ func browseParse(c *caddy.Controller) ([]Config, error) {
} }
bc.Fs = staticfiles.FileServer{ bc.Fs = staticfiles.FileServer{
Root: http.Dir(cfg.Root), Root: http.Dir(cfg.Root),
Hide: cfg.HiddenFiles, Hide: cfg.HiddenFiles,
IndexPages: cfg.IndexPages,
} }
// Second argument would be the template file to use // Second argument would be the template file to use
......
...@@ -27,6 +27,7 @@ import ( ...@@ -27,6 +27,7 @@ import (
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/mholt/caddy/caddyfile" "github.com/mholt/caddy/caddyfile"
"github.com/mholt/caddy/caddyhttp/staticfiles"
"github.com/mholt/caddy/caddytls" "github.com/mholt/caddy/caddytls"
) )
...@@ -155,6 +156,7 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd ...@@ -155,6 +156,7 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd
AltTLSSNIPort: altTLSSNIPort, AltTLSSNIPort: altTLSSNIPort,
}, },
originCaddyfile: sourceFile, originCaddyfile: sourceFile,
IndexPages: staticfiles.DefaultIndexPages,
} }
h.saveConfig(key, cfg) h.saveConfig(key, cfg)
} }
...@@ -234,7 +236,7 @@ func GetConfig(c *caddy.Controller) *SiteConfig { ...@@ -234,7 +236,7 @@ func GetConfig(c *caddy.Controller) *SiteConfig {
// we should only get here during tests because directive // we should only get here during tests because directive
// actions typically skip the server blocks where we make // actions typically skip the server blocks where we make
// the configs // the configs
cfg := &SiteConfig{Root: Root, TLS: new(caddytls.Config)} cfg := &SiteConfig{Root: Root, TLS: new(caddytls.Config), IndexPages: staticfiles.DefaultIndexPages}
ctx.saveConfig(key, cfg) ctx.saveConfig(key, cfg)
return cfg return cfg
} }
......
...@@ -142,7 +142,7 @@ func NewServer(addr string, group []*SiteConfig) (*Server, error) { ...@@ -142,7 +142,7 @@ func NewServer(addr string, group []*SiteConfig) (*Server, error) {
// Compile custom middleware for every site (enables virtual hosting) // Compile custom middleware for every site (enables virtual hosting)
for _, site := range group { for _, site := range group {
stack := Handler(staticfiles.FileServer{Root: http.Dir(site.Root), Hide: site.HiddenFiles}) stack := Handler(staticfiles.FileServer{Root: http.Dir(site.Root), Hide: site.HiddenFiles, IndexPages: site.IndexPages})
for i := len(site.middleware) - 1; i >= 0; i-- { for i := len(site.middleware) - 1; i >= 0; i-- {
stack = site.middleware[i](stack) stack = site.middleware[i](stack)
} }
......
...@@ -26,6 +26,9 @@ type SiteConfig struct { ...@@ -26,6 +26,9 @@ type SiteConfig struct {
// The address of the site // The address of the site
Addr Address Addr Address
// The list of viable index page names of the site
IndexPages []string
// The hostname to bind listener to; // The hostname to bind listener to;
// defaults to Addr.Host // defaults to Addr.Host
ListenHost string ListenHost string
......
...@@ -16,7 +16,7 @@ package index ...@@ -16,7 +16,7 @@ package index
import ( import (
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/staticfiles" "github.com/mholt/caddy/caddyhttp/httpserver"
) )
func init() { func init() {
...@@ -29,6 +29,8 @@ func init() { ...@@ -29,6 +29,8 @@ func init() {
func setupIndex(c *caddy.Controller) error { func setupIndex(c *caddy.Controller) error {
var index []string var index []string
cfg := httpserver.GetConfig(c)
for c.Next() { for c.Next() {
args := c.RemainingArgs() args := c.RemainingArgs()
...@@ -40,7 +42,7 @@ func setupIndex(c *caddy.Controller) error { ...@@ -40,7 +42,7 @@ func setupIndex(c *caddy.Controller) error {
index = append(index, in) index = append(index, in)
} }
staticfiles.IndexPages = index cfg.IndexPages = index
} }
return nil return nil
......
...@@ -18,6 +18,7 @@ import ( ...@@ -18,6 +18,7 @@ import (
"testing" "testing"
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles" "github.com/mholt/caddy/caddyhttp/staticfiles"
) )
...@@ -31,7 +32,7 @@ func TestIndexIncompleteParams(t *testing.T) { ...@@ -31,7 +32,7 @@ func TestIndexIncompleteParams(t *testing.T) {
} }
func TestIndex(t *testing.T) { func TestIndex(t *testing.T) {
c := caddy.NewTestController("", "index a.html b.html c.html") c := caddy.NewTestController("http", "index a.html b.html c.html")
err := setupIndex(c) err := setupIndex(c)
if err != nil { if err != nil {
...@@ -40,14 +41,85 @@ func TestIndex(t *testing.T) { ...@@ -40,14 +41,85 @@ func TestIndex(t *testing.T) {
expectedIndex := []string{"a.html", "b.html", "c.html"} expectedIndex := []string{"a.html", "b.html", "c.html"}
if len(staticfiles.IndexPages) != 3 { siteConfig := httpserver.GetConfig(c)
t.Errorf("Expected 3 values, got %v", len(staticfiles.IndexPages))
if len(siteConfig.IndexPages) != len(expectedIndex) {
t.Errorf("Expected 3 values, got %v", len(siteConfig.IndexPages))
} }
// Ensure ordering is correct // Ensure ordering is correct
for i, actual := range staticfiles.IndexPages { for i, actual := range siteConfig.IndexPages {
if actual != expectedIndex[i] { if actual != expectedIndex[i] {
t.Errorf("Expected value in position %d to be %v, got %v", i, expectedIndex[i], actual) t.Errorf("Expected value in position %d to be %v, got %v", i, expectedIndex[i], actual)
} }
} }
} }
func TestMultiSiteIndexWithEitherHasDefault(t *testing.T) {
// TestIndex already covers the correctness of the directive
// when used on a single controller, so no need to verify test setupIndex again.
// This sets the stage for the actual verification.
customIndex := caddy.NewTestController("http", "index a.html b.html")
// setupIndex against customIdx should not pollute the
// index list for other controllers.
err := setupIndex(customIndex)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
// Represents a virtual host with no index directive.
defaultIndex := caddy.NewTestController("http", "")
// Not calling setupIndex because it guards against lack of arguments,
// and we need to ensure the site gets the default set of index pages.
siteConfig := httpserver.GetConfig(defaultIndex)
// In case the index directive is not used, the virtual host
// should receive staticfiles.DefaultIndexPages slice. The length, as checked here,
// and the values, as checked in the upcoming loop, should match.
if len(siteConfig.IndexPages) != len(staticfiles.DefaultIndexPages) {
t.Errorf("Expected %d values, got %d", len(staticfiles.DefaultIndexPages), len(siteConfig.IndexPages))
}
// Ensure values match the expected default index pages
for i, actual := range siteConfig.IndexPages {
if actual != staticfiles.DefaultIndexPages[i] {
t.Errorf("Expected value in position %d to be %v, got %v", i, staticfiles.DefaultIndexPages[i], actual)
}
}
}
func TestPerSiteIndexPageIsolation(t *testing.T) {
firstIndex := "first.html"
secondIndex := "second.html"
// Create two sites with different index page configurations
firstSite := caddy.NewTestController("http", "index first.html")
err := setupIndex(firstSite)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
secondSite := caddy.NewTestController("http", "index second.html")
err = setupIndex(secondSite)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
firstSiteConfig := httpserver.GetConfig(firstSite)
if firstSiteConfig.IndexPages[0] != firstIndex {
t.Errorf("Expected index for first site as %s, received %s", firstIndex, firstSiteConfig.IndexPages[0])
}
secondSiteConfig := httpserver.GetConfig(secondSite)
if secondSiteConfig.IndexPages[0] != secondIndex {
t.Errorf("Expected index for second site as %s, received %s", secondIndex, secondSiteConfig.IndexPages[0])
}
// They should have different index pages, as per the provided config.
if firstSiteConfig.IndexPages[0] == secondSiteConfig.IndexPages[0] {
t.Errorf("Expected different index pages for both sites, got %s for first and %s for second", firstSiteConfig.IndexPages[0], secondSiteConfig.IndexPages[0])
}
}
...@@ -19,7 +19,6 @@ import ( ...@@ -19,7 +19,6 @@ import (
"strings" "strings"
"github.com/mholt/caddy/caddyhttp/httpserver" "github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles"
) )
func (h Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { func (h Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
...@@ -44,7 +43,7 @@ outer: ...@@ -44,7 +43,7 @@ outer:
matches := httpserver.Path(urlPath).Matches(rule.Path) matches := httpserver.Path(urlPath).Matches(rule.Path)
// Also check IndexPages when requesting a directory // Also check IndexPages when requesting a directory
if !matches { if !matches {
indexFile, isIndexFile := httpserver.IndexFile(h.Root, urlPath, staticfiles.IndexPages) indexFile, isIndexFile := httpserver.IndexFile(h.Root, urlPath, h.indexPages)
if isIndexFile { if isIndexFile {
matches = httpserver.Path(indexFile).Matches(rule.Path) matches = httpserver.Path(indexFile).Matches(rule.Path)
} }
......
...@@ -393,7 +393,8 @@ func TestMiddlewareShouldPushIndexFile(t *testing.T) { ...@@ -393,7 +393,8 @@ func TestMiddlewareShouldPushIndexFile(t *testing.T) {
{Path: "/index.css", Method: http.MethodGet}, {Path: "/index.css", Method: http.MethodGet},
}}, }},
}, },
Root: http.Dir(root), Root: http.Dir(root),
indexPages: []string{indexFile},
} }
indexFilePath := filepath.Join(root, indexFile) indexFilePath := filepath.Join(root, indexFile)
......
...@@ -36,9 +36,10 @@ type ( ...@@ -36,9 +36,10 @@ type (
// Middleware supports pushing resources to clients // Middleware supports pushing resources to clients
Middleware struct { Middleware struct {
Next httpserver.Handler Next httpserver.Handler
Rules []Rule Rules []Rule
Root http.FileSystem Root http.FileSystem
indexPages []string // will be injected from SiteConfig on setup
} }
ruleOp func([]Resource) ruleOp func([]Resource)
......
...@@ -50,7 +50,7 @@ func setup(c *caddy.Controller) error { ...@@ -50,7 +50,7 @@ func setup(c *caddy.Controller) error {
cfg := httpserver.GetConfig(c) cfg := httpserver.GetConfig(c)
cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler { cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
return Middleware{Next: next, Rules: rules, Root: http.Dir(cfg.Root)} return Middleware{Next: next, Rules: rules, Root: http.Dir(cfg.Root), indexPages: cfg.IndexPages}
}) })
return nil return nil
......
...@@ -45,6 +45,10 @@ import ( ...@@ -45,6 +45,10 @@ import (
type FileServer struct { type FileServer struct {
Root http.FileSystem // jailed access to the file system Root http.FileSystem // jailed access to the file system
Hide []string // list of files for which to respond with "Not Found" Hide []string // list of files for which to respond with "Not Found"
// A list of pages that may be understood as the "index" files to directories.
// Injected from *SiteConfig.
IndexPages []string
} }
// ServeHTTP serves static files for r according to fs's configuration. // ServeHTTP serves static files for r according to fs's configuration.
...@@ -118,7 +122,7 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err ...@@ -118,7 +122,7 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err
// if an index file was explicitly requested, strip file name from the request // if an index file was explicitly requested, strip file name from the request
// ("/foo/index.html" -> "/foo/") // ("/foo/index.html" -> "/foo/")
var requestPage = path.Base(urlCopy.Path) var requestPage = path.Base(urlCopy.Path)
for _, indexPage := range IndexPages { for _, indexPage := range fs.IndexPages {
if requestPage == indexPage { if requestPage == indexPage {
urlCopy.Path = urlCopy.Path[:len(urlCopy.Path)-len(indexPage)] urlCopy.Path = urlCopy.Path[:len(urlCopy.Path)-len(indexPage)]
redir = true redir = true
...@@ -134,7 +138,7 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err ...@@ -134,7 +138,7 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err
// use contents of an index file, if present, for directory requests // use contents of an index file, if present, for directory requests
if d.IsDir() { if d.IsDir() {
for _, indexPage := range IndexPages { for _, indexPage := range fs.IndexPages {
indexPath := path.Join(reqPath, indexPage) indexPath := path.Join(reqPath, indexPage)
indexFile, err := fs.Root.Open(indexPath) indexFile, err := fs.Root.Open(indexPath)
if err != nil { if err != nil {
...@@ -253,9 +257,9 @@ func calculateEtag(d os.FileInfo) string { ...@@ -253,9 +257,9 @@ func calculateEtag(d os.FileInfo) string {
return `"` + t + s + `"` return `"` + t + s + `"`
} }
// IndexPages is a list of pages that may be understood as // DefaultIndexPages is a list of pages that may be understood as
// the "index" files to directories. // the "index" files to directories.
var IndexPages = []string{ var DefaultIndexPages = []string{
"index.html", "index.html",
"index.htm", "index.htm",
"index.txt", "index.txt",
......
...@@ -36,8 +36,9 @@ func TestServeHTTP(t *testing.T) { ...@@ -36,8 +36,9 @@ func TestServeHTTP(t *testing.T) {
defer afterServeHTTPTest(t, tmpWebRootDir) defer afterServeHTTPTest(t, tmpWebRootDir)
fileserver := FileServer{ fileserver := FileServer{
Root: http.Dir(filepath.Join(tmpWebRootDir, webrootName)), Root: http.Dir(filepath.Join(tmpWebRootDir, webrootName)),
Hide: []string{"dir/hidden.html"}, Hide: []string{"dir/hidden.html"},
IndexPages: DefaultIndexPages,
} }
movedPermanently := "Moved Permanently" movedPermanently := "Moved Permanently"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment