mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:16:28 +01:00
add a DirListRich helper to make the file listing a bit more appealing than its default
Former-commit-id: 1d8338cddac0856be1c9f1e7b6d8d400bee71bef
This commit is contained in:
parent
bdfe8de66d
commit
16a6372cc9
|
@ -31,6 +31,7 @@ func main() {
|
||||||
app.HandleDir("/files", "./uploads", iris.DirOptions{
|
app.HandleDir("/files", "./uploads", iris.DirOptions{
|
||||||
Gzip: true,
|
Gzip: true,
|
||||||
ShowList: true,
|
ShowList: true,
|
||||||
|
DirList: iris.DirListRich,
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Listen(":8080")
|
app.Listen(":8080")
|
||||||
|
@ -49,7 +50,7 @@ func uploadView(ctx iris.Context) {
|
||||||
ctx.View("upload.html", token)
|
ctx.View("upload.html", token)
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxSize = 10 * iris.MB
|
const maxSize = 1 * iris.GB
|
||||||
|
|
||||||
func upload(ctx iris.Context) {
|
func upload(ctx iris.Context) {
|
||||||
ctx.SetMaxRequestBodySize(maxSize)
|
ctx.SetMaxRequestBodySize(maxSize)
|
||||||
|
|
|
@ -3,6 +3,8 @@ package router
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -19,6 +21,9 @@ import (
|
||||||
|
|
||||||
const indexName = "/index.html"
|
const indexName = "/index.html"
|
||||||
|
|
||||||
|
// DirListFunc is the function signature for customizing directory and file listing.
|
||||||
|
type DirListFunc func(ctx context.Context, dirName string, dir http.File) error
|
||||||
|
|
||||||
// DirOptions contains the optional settings that
|
// DirOptions contains the optional settings that
|
||||||
// `FileServer` and `Party#HandleDir` can use to serve files and assets.
|
// `FileServer` and `Party#HandleDir` can use to serve files and assets.
|
||||||
type DirOptions struct {
|
type DirOptions struct {
|
||||||
|
@ -33,7 +38,7 @@ type DirOptions struct {
|
||||||
// List the files inside the current requested directory if `IndexName` not found.
|
// List the files inside the current requested directory if `IndexName` not found.
|
||||||
ShowList bool
|
ShowList bool
|
||||||
// If `ShowList` is true then this function will be used instead of the default one to show the list of files of a current requested directory(dir).
|
// If `ShowList` is true then this function will be used instead of the default one to show the list of files of a current requested directory(dir).
|
||||||
DirList func(ctx context.Context, dirName string, dir http.File) error
|
DirList DirListFunc
|
||||||
|
|
||||||
// When embedded.
|
// When embedded.
|
||||||
Asset func(name string) ([]byte, error) // we need this to make it compatible os.File.
|
Asset func(name string) ([]byte, error) // we need this to make it compatible os.File.
|
||||||
|
@ -283,16 +288,6 @@ func FileServer(directory string, opts ...DirOptions) context.Handler {
|
||||||
ctx.StatusCode(statusCode)
|
ctx.StatusCode(statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlReplacer := strings.NewReplacer(
|
|
||||||
"&", "&",
|
|
||||||
"<", "<",
|
|
||||||
">", ">",
|
|
||||||
// """ is shorter than """.
|
|
||||||
`"`, """,
|
|
||||||
// "'" is shorter than "'" and apos was not in HTML until HTML5.
|
|
||||||
"'", "'",
|
|
||||||
)
|
|
||||||
|
|
||||||
dirList := options.DirList
|
dirList := options.DirList
|
||||||
if dirList == nil {
|
if dirList == nil {
|
||||||
dirList = func(ctx context.Context, dirName string, dir http.File) error {
|
dirList = func(ctx context.Context, dirName string, dir http.File) error {
|
||||||
|
@ -301,9 +296,6 @@ func FileServer(directory string, opts ...DirOptions) context.Handler {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// dst, _ := dir.Stat()
|
|
||||||
// dirName := dst.Name()
|
|
||||||
|
|
||||||
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
|
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
|
||||||
|
|
||||||
ctx.ContentType(context.ContentHTMLHeaderValue)
|
ctx.ContentType(context.ContentHTMLHeaderValue)
|
||||||
|
@ -332,7 +324,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler {
|
||||||
// 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.
|
||||||
_, 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(), html.EscapeString(name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -403,6 +395,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler {
|
||||||
ctx.SetLastModified(info.ModTime())
|
ctx.SetLastModified(info.ModTime())
|
||||||
err = dirList(ctx, info.Name(), f)
|
err = dirList(ctx, info.Name(), f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
println(err.Error())
|
||||||
plainStatusCode(ctx, http.StatusInternalServerError)
|
plainStatusCode(ctx, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -572,3 +565,175 @@ func DirectoryExists(dir string) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DirListRich is a `DirListFunc` which can be passed to `DirOptions.DirList` field
|
||||||
|
// to override the default file listing appearance.
|
||||||
|
// See `DirListRichTemplate` to modify the template, if necessary.
|
||||||
|
func DirListRich(ctx context.Context, dirName string, dir http.File) error {
|
||||||
|
dirs, err := dir.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sortBy := ctx.URLParam("sort")
|
||||||
|
switch sortBy {
|
||||||
|
case "name":
|
||||||
|
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
|
||||||
|
case "size":
|
||||||
|
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Size() < dirs[j].Size() })
|
||||||
|
default:
|
||||||
|
sort.Slice(dirs, func(i, j int) bool { return dirs[i].ModTime().After(dirs[j].ModTime()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
pageData := listPageData{
|
||||||
|
Title: fmt.Sprintf("List of %d files", len(dirs)),
|
||||||
|
Files: make([]fileInfoData, 0, len(dirs)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range dirs {
|
||||||
|
name := d.Name()
|
||||||
|
if d.IsDir() {
|
||||||
|
name += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
upath := ""
|
||||||
|
if ctx.Path() == "/" {
|
||||||
|
upath = ctx.GetCurrentRoute().StaticPath() + "/" + name
|
||||||
|
} else {
|
||||||
|
upath = "./" + dirName + "/" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
url := url.URL{Path: upath}
|
||||||
|
|
||||||
|
pageData.Files = append(pageData.Files, fileInfoData{
|
||||||
|
Info: d,
|
||||||
|
ModTime: d.ModTime().UTC().Format(http.TimeFormat),
|
||||||
|
Path: url.String(),
|
||||||
|
Name: html.EscapeString(name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return DirListRichTemplate.Execute(ctx, pageData)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
listPageData struct {
|
||||||
|
Title string // the document's title.
|
||||||
|
Files []fileInfoData
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfoData struct {
|
||||||
|
Info os.FileInfo
|
||||||
|
ModTime string // format-ed time.
|
||||||
|
Path string // the request path.
|
||||||
|
Name string // the html-escaped name.
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// DirListRichTemplate is the html template the `DirListRich` function is using to render
|
||||||
|
// the directories and files.
|
||||||
|
var DirListRichTemplate = template.Must(template.New("").
|
||||||
|
Funcs(template.FuncMap{
|
||||||
|
"formatBytes": func(b int64) string {
|
||||||
|
const unit = 1000
|
||||||
|
if b < unit {
|
||||||
|
return fmt.Sprintf("%d B", b)
|
||||||
|
}
|
||||||
|
div, exp := int64(unit), 0
|
||||||
|
for n := b / unit; n >= unit; n /= unit {
|
||||||
|
div *= unit
|
||||||
|
exp++
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.1f %cB",
|
||||||
|
float64(b)/float64(div), "kMGTPE"[exp])
|
||||||
|
},
|
||||||
|
}).Parse(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{.Title}}</title>
|
||||||
|
<style>
|
||||||
|
a {
|
||||||
|
padding: 8px 8px;
|
||||||
|
text-decoration:none;
|
||||||
|
cursor:pointer;
|
||||||
|
color: #10a2ff;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
empty-cells: show;
|
||||||
|
border: 1px solid #cbcbcb;
|
||||||
|
}
|
||||||
|
|
||||||
|
table caption {
|
||||||
|
color: #000;
|
||||||
|
font: italic 85%/1 arial, sans-serif;
|
||||||
|
padding: 1em 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td,
|
||||||
|
table th {
|
||||||
|
border-left: 1px solid #cbcbcb;
|
||||||
|
border-width: 0 0 0 1px;
|
||||||
|
font-size: inherit;
|
||||||
|
margin: 0;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead {
|
||||||
|
background-color: #10a2ff;
|
||||||
|
color: #fff;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-odd td {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-bordered td {
|
||||||
|
border-bottom: 1px solid #cbcbcb;
|
||||||
|
}
|
||||||
|
.table-bordered tbody > tr:last-child > td {
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="table-bordered table-odd">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Size</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range $idx, $file := .Files }}
|
||||||
|
<tr>
|
||||||
|
<td>{{ $idx }}</td>
|
||||||
|
<td><a href="{{ $file.Path }}" title="{{ $file.ModTime }}">{{ $file.Name }}</a></td>
|
||||||
|
<td>{{ formatBytes $file.Info.Size }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body></html>
|
||||||
|
`))
|
||||||
|
|
4
iris.go
4
iris.go
|
@ -439,6 +439,10 @@ var (
|
||||||
// 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
|
||||||
// A shortcut for the `router.FileServer`.
|
// A shortcut for the `router.FileServer`.
|
||||||
FileServer = router.FileServer
|
FileServer = router.FileServer
|
||||||
|
// DirListRich can be passed to `DirOptions.DirList` field
|
||||||
|
// to override the default file listing appearance.
|
||||||
|
// Read more at: `core/router.DirListRich`.
|
||||||
|
DirListRich = router.DirListRich
|
||||||
// StripPrefix returns a handler that serves HTTP requests
|
// StripPrefix returns a handler that serves HTTP requests
|
||||||
// by removing the given prefix from the request URL's Path
|
// by removing the given prefix from the request URL's Path
|
||||||
// and invoking the handler h. StripPrefix handles a
|
// and invoking the handler h. StripPrefix handles a
|
||||||
|
|
Loading…
Reference in New Issue
Block a user