From 0daffa37ced6dd4a2da055d8078acc35bfa6a300 Mon Sep 17 00:00:00 2001 From: Vishal Rana Date: Fri, 17 Feb 2017 11:09:23 -0800 Subject: [PATCH] Initial static middleware - #838 Signed-off-by: Vishal Rana --- middleware/static.go | 118 ++++++++++++++++++++++++++++++++++++++ middleware/static_test.go | 63 ++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 middleware/static.go create mode 100644 middleware/static_test.go diff --git a/middleware/static.go b/middleware/static.go new file mode 100644 index 00000000..793c1445 --- /dev/null +++ b/middleware/static.go @@ -0,0 +1,118 @@ +package middleware + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/labstack/echo" +) + +type ( + // StaticConfig defines the config for Static middleware. + StaticConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Root directory from where the static content is served. + // Required. + Root string `json:"root"` + + // Index file for serving a directory. + // Optional. Default value "index.html". + Index string `json:"index"` + + // Enable HTML5 mode by forwarding all not-found requests to root so that + // SPA (single-page application) can handle the routing. + // Optional. Default value false. + HTML5 bool `json:"html5"` + + // Enable directory browsing. + // Optional. Default value false. + Browse bool `json:"browse"` + } +) + +var ( + // DefaultStaticConfig is the default Static middleware config. + DefaultStaticConfig = StaticConfig{ + Skipper: DefaultSkipper, + Index: "index.html", + } +) + +// Static returns a Static middleware to serves static content from the provided +// root directory. +func Static(root string) echo.MiddlewareFunc { + c := DefaultStaticConfig + c.Root = root + return StaticWithConfig(c) +} + +// StaticWithConfig returns a Static middleware with config. +// See `Static()`. +func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultStaticConfig.Skipper + } + if config.Index == "" { + config.Index = DefaultStaticConfig.Index + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + p := c.Param("*") + name := filepath.Join(config.Root, p) + fi, err := os.Stat(name) + + if err != nil { + if os.IsNotExist(err) { + if config.HTML5 { + return c.File(filepath.Join(config.Root, config.Index)) + } + return echo.ErrNotFound + } + return err + } + + if fi.IsDir() { + if config.Browse { + return listDir(name, c.Response()) + } + return c.File(filepath.Join(name, config.Index)) + } + return c.File(name) + } + } +} + +func listDir(name string, res *echo.Response) error { + dir, err := os.Open(name) + if err != nil { + return err + } + dirs, err := dir.Readdir(-1) + if err != nil { + return err + } + + // Create a directory index + res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) + if _, err = fmt.Fprintf(res, "
\n"); err != nil {
+		return err
+	}
+	for _, d := range dirs {
+		name := d.Name()
+		color := "#212121"
+		if d.IsDir() {
+			color = "#e91e63"
+			name += "/"
+		}
+		if _, err = fmt.Fprintf(res, "%s\n", name, color, name); err != nil {
+			return err
+		}
+	}
+	_, err = fmt.Fprintf(res, "
\n") + return err +} diff --git a/middleware/static_test.go b/middleware/static_test.go new file mode 100644 index 00000000..b8ce8a08 --- /dev/null +++ b/middleware/static_test.go @@ -0,0 +1,63 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/labstack/echo" + "github.com/stretchr/testify/assert" +) + +func TestStatic(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(echo.GET, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + c.SetParamNames("*") + config := StaticConfig{ + Root: "../_fixture", + } + + // Directory + h := StaticWithConfig(config)(echo.NotFoundHandler) + if assert.NoError(t, h(c)) { + assert.Contains(t, rec.Body.String(), "Echo") + } + + // File found + h = StaticWithConfig(config)(echo.NotFoundHandler) + c.SetParamValues("/images/walle.png") + if assert.NoError(t, h(c)) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Equal(t, rec.Header().Get(echo.HeaderContentLength), "219885") + } + + // File not found + c.SetParamValues("/none") + rec.Body.Reset() + he := h(c).(*echo.HTTPError) + assert.Equal(t, http.StatusNotFound, he.Code) + + // HTML5 + c.SetParamValues("/random") + rec.Body.Reset() + config.HTML5 = true + static := StaticWithConfig(config) + h = static(echo.NotFoundHandler) + if assert.NoError(t, h(c)) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Contains(t, rec.Body.String(), "Echo") + } + + // Browse + c.SetParamValues("/") + rec.Body.Reset() + config.Browse = true + static = StaticWithConfig(config) + h = static(echo.NotFoundHandler) + if assert.NoError(t, h(c)) { + assert.Equal(t, http.StatusOK, rec.Code) + assert.Contains(t, rec.Body.String(), "images") + } +}