file-server: fix ShowList on root dir

Former-commit-id: 6795382235d76942bcfd31ecc0b4ab02ecb85a8a
This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-07-05 05:39:48 +03:00
parent 0a1b500c8b
commit 57dc64625d
12 changed files with 142 additions and 131 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ go.sum
node_modules node_modules
/_examples/issue-*/ /_examples/issue-*/
/_examples/feature-*/ /_examples/feature-*/
_examples/**/uploads/*

View File

@ -80,6 +80,7 @@
* [Recovery](recover/main.go) * [Recovery](recover/main.go)
* [Profiling](pprof/main.go) * [Profiling](pprof/main.go)
* File Server * File Server
* [File Server](file-server/file-server/main.go)
* [Favicon](file-server/favicon/main.go) * [Favicon](file-server/favicon/main.go)
* [Basic](file-server/basic/main.go) * [Basic](file-server/basic/main.go)
* [Embedding Files Into App Executable File](file-server/embedding-files-into-app/main.go) * [Embedding Files Into App Executable File](file-server/embedding-files-into-app/main.go)

View File

@ -78,10 +78,6 @@ func TestEmbeddingFilesIntoApp(t *testing.T) {
t.Fatalf("expected a route to serve embedded files") t.Fatalf("expected a route to serve embedded files")
} }
if len(route.StaticSites()) > 0 {
t.Fatalf("not expected a static site, the ./assets directory or its subdirectories do not contain any index.html")
}
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
// remove the embedded static favicon for !windows, // remove the embedded static favicon for !windows,
// it should be built for unix-specific in order to be work // it should be built for unix-specific in order to be work

View File

@ -0,0 +1,66 @@
package main
import (
"crypto/md5"
"fmt"
"io"
"mime/multipart"
"os"
"strconv"
"strings"
"time"
"github.com/kataras/iris/v12"
)
func init() {
os.Mkdir("./uploads", 0700)
}
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
app.Get("/", index)
app.Get("/upload", uploadView)
app.Post("/upload", upload)
app.HandleDir("/files", "./uploads", iris.DirOptions{
Gzip: true,
ShowList: true,
})
app.Listen(":8080")
}
func index(ctx iris.Context) {
ctx.Redirect("/upload")
}
func uploadView(ctx iris.Context) {
now := time.Now().Unix()
h := md5.New()
io.WriteString(h, strconv.FormatInt(now, 10))
token := fmt.Sprintf("%x", h.Sum(nil))
ctx.View("upload.html", token)
}
func upload(ctx iris.Context) {
_, err := ctx.UploadFormFiles("./uploads", beforeSave)
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.Redirect("/files")
}
func beforeSave(ctx iris.Context, file *multipart.FileHeader) {
ip := ctx.RemoteAddr()
ip = strings.ReplaceAll(ip, ".", "_")
ip = strings.ReplaceAll(ip, ":", "_")
file.Filename = ip + "-" + file.Filename
}

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upload Files</title>
</head>
<body>
<form enctype="multipart/form-data"
action="/upload" method="POST">
<input type="file" name="uploadfile" multiple/> <input type="hidden"
name="token" value="{{.}}" />
<input type="submit" value="upload" />
</form>
</body>
</html>

View File

@ -444,16 +444,18 @@ type Context interface {
// Header adds a header to the response writer. // Header adds a header to the response writer.
Header(name string, value string) Header(name string, value string)
// ContentType sets the response writer's header key "Content-Type" to the 'cType'. // ContentType sets the response writer's
// header "Content-Type" to the 'cType'.
ContentType(cType string) ContentType(cType string)
// GetContentType returns the response writer's header value of "Content-Type" // GetContentType returns the response writer's
// which may, set before with the 'ContentType'. // header value of "Content-Type".
GetContentType() string GetContentType() string
// GetContentType returns the request's header value of "Content-Type". // GetContentType returns the request's
// trim-ed(without the charset and priority values)
// header value of "Content-Type".
GetContentTypeRequested() string GetContentTypeRequested() string
// GetContentLength returns the request's
// GetContentLength returns the request's header value of "Content-Length". // header value of "Content-Length".
// Returns 0 if header was unable to be found or its value was not a valid number.
GetContentLength() int64 GetContentLength() int64
// StatusCode sets the status code header to the response. // StatusCode sets the status code header to the response.
@ -2256,7 +2258,8 @@ func (ctx *context) contentTypeOnce(cType string, charset string) {
ctx.writer.Header().Set(ContentTypeHeaderKey, cType) ctx.writer.Header().Set(ContentTypeHeaderKey, cType)
} }
// ContentType sets the response writer's header key "Content-Type" to the 'cType'. // ContentType sets the response writer's
// header "Content-Type" to the 'cType'.
func (ctx *context) ContentType(cType string) { func (ctx *context) ContentType(cType string) {
if cType == "" { if cType == "" {
return return
@ -2282,19 +2285,21 @@ func (ctx *context) ContentType(cType string) {
ctx.writer.Header().Set(ContentTypeHeaderKey, cType) ctx.writer.Header().Set(ContentTypeHeaderKey, cType)
} }
// GetContentType returns the response writer's header value of "Content-Type" // GetContentType returns the response writer's
// which may, set before with the 'ContentType'. // header value of "Content-Type".
func (ctx *context) GetContentType() string { func (ctx *context) GetContentType() string {
return ctx.writer.Header().Get(ContentTypeHeaderKey) return ctx.writer.Header().Get(ContentTypeHeaderKey)
} }
// GetContentType returns the request's header value of "Content-Type". // GetContentType returns the request's
// trim-ed(without the charset and priority values)
// header value of "Content-Type".
func (ctx *context) GetContentTypeRequested() string { func (ctx *context) GetContentTypeRequested() string {
return TrimHeaderValue(ctx.GetHeader(ContentTypeHeaderKey)) return TrimHeaderValue(ctx.GetHeader(ContentTypeHeaderKey))
} }
// GetContentLength returns the request's header value of "Content-Length". // GetContentLength returns the request's
// Returns 0 if header was unable to be found or its value was not a valid number. // header value of "Content-Length".
func (ctx *context) GetContentLength() int64 { func (ctx *context) GetContentLength() int64 {
if v := ctx.GetHeader(ContentLengthHeaderKey); v != "" { if v := ctx.GetHeader(ContentLengthHeaderKey); v != "" {
n, _ := strconv.ParseInt(v, 10, 64) n, _ := strconv.ParseInt(v, 10, 64)
@ -3427,7 +3432,7 @@ func (ctx *context) TryWriteGzip(b []byte) (int, error) {
n, err := ctx.WriteGzip(b) n, err := ctx.WriteGzip(b)
if err != nil { if err != nil {
// check if the error came from gzip not allowed and not the writer itself // check if the error came from gzip not allowed and not the writer itself
if errors.Is(err, ErrGzipNotSupported) { if err == ErrGzipNotSupported {
// client didn't supported gzip, write them uncompressed: // client didn't supported gzip, write them uncompressed:
return ctx.writer.Write(b) return ctx.writer.Write(b)
} }
@ -4239,6 +4244,8 @@ type N struct {
Other []byte // custom content types. Other []byte // custom content types.
} }
var _ ContentSelector = N{}
// SelectContent returns a content based on the matched negotiated "mime". // SelectContent returns a content based on the matched negotiated "mime".
func (n N) SelectContent(mime string) interface{} { func (n N) SelectContent(mime string) interface{} {
switch mime { switch mime {
@ -4419,7 +4426,6 @@ func (ctx *context) Negotiate(v interface{}) (int, error) {
ctx.StatusCode(http.StatusNotAcceptable) ctx.StatusCode(http.StatusNotAcceptable)
return -1, ErrContentNotSupported return -1, ErrContentNotSupported
} }
} }
} }

View File

@ -2,10 +2,6 @@ package context
import ( import (
"io" "io"
"os"
"path"
"path/filepath"
"strings"
"time" "time"
"github.com/kataras/iris/v12/macro" "github.com/kataras/iris/v12/macro"
@ -66,13 +62,6 @@ type RouteReadOnly interface {
// MainHandlerIndex returns the first registered handler's index for the route. // MainHandlerIndex returns the first registered handler's index for the route.
MainHandlerIndex() int MainHandlerIndex() int
// StaticSites if not empty, refers to the system (or virtual if embedded) directory
// and sub directories that this "GET" route was registered to serve files and folders
// that contain index.html (a site). The index handler may registered by other
// route, manually or automatic by the framework,
// get the route by `Application#GetRouteByPath(staticSite.RequestPath)`.
StaticSites() []StaticSite
// Sitemap properties: https://www.sitemaps.org/protocol.html // Sitemap properties: https://www.sitemaps.org/protocol.html
// GetLastMod returns the date of last modification of the file served by this route. // GetLastMod returns the date of last modification of the file served by this route.
@ -82,51 +71,3 @@ type RouteReadOnly interface {
// GetPriority returns the priority of this route's URL relative to other URLs on your site. // GetPriority returns the priority of this route's URL relative to other URLs on your site.
GetPriority() float32 GetPriority() float32
} }
// StaticSite is a structure which is used as field on the `Route`
// and route registration on the `APIBuilder#HandleDir`.
// See `GetStaticSites` and `APIBuilder#HandleDir`.
type StaticSite struct {
Dir string `json:"dir"`
RequestPath string `json:"requestPath"`
}
// GetStaticSites search for a relative filename of "indexName" in "rootDir" and all its subdirectories
// and returns a list of structures which contains the directory found an "indexName" and the request path
// that a route should be registered to handle this "indexName".
// The request path is given by the directory which an index exists on.
func GetStaticSites(rootDir, rootRequestPath, indexName string) (sites []StaticSite) {
f, err := os.Open(rootDir)
if err != nil {
return nil
}
list, err := f.Readdir(-1)
f.Close()
if err != nil {
return nil
}
if len(list) == 0 {
return nil
}
for _, l := range list {
dir := filepath.Join(rootDir, l.Name())
if l.IsDir() {
sites = append(sites, GetStaticSites(dir, path.Join(rootRequestPath, l.Name()), indexName)...)
continue
}
if l.Name() == strings.TrimPrefix(indexName, "/") {
sites = append(sites, StaticSite{
Dir: filepath.FromSlash(rootDir),
RequestPath: rootRequestPath,
})
continue
}
}
return
}

View File

@ -390,12 +390,11 @@ func (api *APIBuilder) HandleMany(methodOrMulti string, relativePathorMulti stri
// //
// api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"}) // api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"})
// //
// Returns the GET *Route. // Returns all the registered routes, including GET index and path patterm and HEAD.
// //
// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server
func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptions) (getRoute *Route) { func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptions) (routes []*Route) {
options := getDirOptions(opts...) options := getDirOptions(opts...)
h := FileServer(directory, options) h := FileServer(directory, options)
description := directory description := directory
fileName, lineNumber := context.HandlerFileLine(h) // take those before StripPrefix. fileName, lineNumber := context.HandlerFileLine(h) // take those before StripPrefix.
@ -408,42 +407,15 @@ func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptio
h = StripPrefix(fullpath, h) h = StripPrefix(fullpath, h)
} }
requestPath = joinPath(requestPath, WildcardFileParam()) if api.GetRouteByPath(fullpath) == nil {
routes := api.CreateRoutes([]string{http.MethodGet, http.MethodHead}, requestPath, h) // register index if not registered by the end-developer.
getRoute = routes[0] routes = api.CreateRoutes([]string{http.MethodGet, http.MethodHead}, requestPath, h)
// we get all index, including sub directories even if those
// are already managed by the static handler itself.
staticSites := context.GetStaticSites(directory, getRoute.StaticPath(), options.IndexName)
for _, s := range staticSites {
// if the end-dev did manage that index route manually already
// then skip the auto-registration.
//
// Also keep note that end-dev is still able to replace this route and manage by him/herself
// later on by a simple `Handle/Get/` call, refer to `repository#register`.
if api.GetRouteByPath(s.RequestPath) != nil {
continue
}
if n := len(api.relativePath); n > 0 && api.relativePath[n-1] == SubdomainPrefix[0] {
// this api is a subdomain-based.
slashIdx := strings.IndexByte(s.RequestPath, '/')
if slashIdx == -1 {
slashIdx = 0
}
requestPath = s.RequestPath[slashIdx:]
} else {
requestPath = s.RequestPath[strings.Index(s.RequestPath, api.relativePath)+len(api.relativePath):]
}
if requestPath == "" {
requestPath = "/"
}
routes = append(routes, api.CreateRoutes([]string{http.MethodGet}, requestPath, h)...)
getRoute.StaticSites = append(getRoute.StaticSites, s)
} }
requestPath = joinPath(requestPath, WildcardFileParam())
routes = append(routes, api.CreateRoutes([]string{http.MethodGet, http.MethodHead}, requestPath, h)...)
for _, route := range routes { for _, route := range routes {
if route.Method == http.MethodHead { if route.Method == http.MethodHead {
} else { } else {
@ -457,7 +429,7 @@ func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptio
} }
} }
return getRoute return routes
} }
// CreateRoutes returns a list of Party-based Routes. // CreateRoutes returns a list of Party-based Routes.

View File

@ -316,10 +316,22 @@ func FileServer(directory string, opts ...DirOptions) context.Handler {
if d.IsDir() { if d.IsDir() {
name += "/" name += "/"
} }
upath := ""
// if ctx.Path() == "/" && dirName == strings.TrimPrefix(directory, "./") {
if ctx.Path() == "/" {
upath = ctx.GetCurrentRoute().StaticPath() + "/" + name
} else {
upath = "./" + dirName + "/" + name
}
url := url.URL{
Path: upath,
} // edit here to redirect correctly, standard library misses that.
// name may contain '?' or '#', which must be escaped to remain // name may contain '?' or '#', which must be escaped to remain
// part of the URL path, and not indicate the start of a query // part of the URL path, and not indicate the start of a query
// string or fragment. // string or fragment.
url := url.URL{Path: joinPath("./"+dirName, name)} // edit here to redirect correctly, standard library misses that.
_, err = ctx.Writef("<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name)) _, err = ctx.Writef("<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
if err != nil { if err != nil {
return err return err

View File

@ -152,14 +152,14 @@ type Party interface {
// second parameter : the system or the embedded directory that needs to be served // second parameter : the system or the embedded directory that needs to be served
// third parameter : not required, the directory options, set fields is optional. // third parameter : not required, the directory options, set fields is optional.
// //
// for more options look router.FileServer. // Alternatively, to get just the handler for that look the FileServer function instead.
// //
// api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"}) // api.HandleDir("/static", "./assets", DirOptions {ShowList: true, Gzip: true, IndexName: "index.html"})
// //
// Returns the GET *Route. // Returns all the registered routes, including GET index and path patterm and HEAD.
// //
// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server
HandleDir(requestPath, directory string, opts ...DirOptions) *Route HandleDir(requestPath, directory string, opts ...DirOptions) []*Route
// None registers an "offline" route // None registers an "offline" route
// see context.ExecRoute(routeName) and // see context.ExecRoute(routeName) and

View File

@ -51,13 +51,7 @@ type Route struct {
RegisterFileName string `json:"registerFileName"` RegisterFileName string `json:"registerFileName"`
RegisterLineNumber int `json:"registerLineNumber"` RegisterLineNumber int `json:"registerLineNumber"`
// StaticSites if not empty, refers to the system (or virtual if embedded) directory topLink *Route
// and sub directories that this "GET" route was registered to serve files and folders
// that contain index.html (a site). The index handler may registered by other
// route, manually or automatic by the framework,
// get the route by `Application#GetRouteByPath(staticSite.RequestPath)`.
StaticSites []context.StaticSite `json:"staticSites"`
topLink *Route
// Sitemap properties: https://www.sitemaps.org/protocol.html // Sitemap properties: https://www.sitemaps.org/protocol.html
LastMod time.Time `json:"lastMod,omitempty"` LastMod time.Time `json:"lastMod,omitempty"`
@ -521,10 +515,6 @@ func (rd routeReadOnlyWrapper) MainHandlerIndex() int {
return rd.Route.MainHandlerIndex return rd.Route.MainHandlerIndex
} }
func (rd routeReadOnlyWrapper) StaticSites() []context.StaticSite {
return rd.Route.StaticSites
}
func (rd routeReadOnlyWrapper) GetLastMod() time.Time { func (rd routeReadOnlyWrapper) GetLastMod() time.Time {
return rd.Route.LastMod return rd.Route.LastMod
} }

View File

@ -212,6 +212,9 @@ func Default() *Application {
app := New() app := New()
app.Use(recover.New()) app.Use(recover.New())
app.Use(requestLogger.New()) app.Use(requestLogger.New())
app.Use(Gzip)
app.Use(GzipReader)
app.defaultMode = true app.defaultMode = true
return app return app
@ -615,6 +618,12 @@ var (
// //
// A shortcut for the `context#ErrPushNotSupported`. // A shortcut for the `context#ErrPushNotSupported`.
ErrPushNotSupported = context.ErrPushNotSupported ErrPushNotSupported = context.ErrPushNotSupported
// ErrGzipNotSupported may be returned from
// `WriteGzip` and `GzipReader` methods if
// the client does not support the "gzip" compression.
//
// A shortcut for the `context#ErrGzipNotSupported`.
ErrGzipNotSupported = context.ErrGzipNotSupported
) )
// Constants for input argument at `router.RouteRegisterRule`. // Constants for input argument at `router.RouteRegisterRule`.