@@ -12,6 +12,7 @@ import (
12
12
"runtime"
13
13
"strings"
14
14
"sync"
15
+ "time"
15
16
16
17
"encoding/xml"
17
18
34
35
pool sync.Pool
35
36
debug bool
36
37
hook http.HandlerFunc
38
+ autoIndex bool
37
39
router * Router
38
40
}
39
41
@@ -142,7 +144,7 @@ const (
142
144
143
145
WebSocket = "websocket"
144
146
145
- indexFile = "index.html"
147
+ indexPage = "index.html"
146
148
)
147
149
148
150
var (
@@ -178,6 +180,8 @@ var (
178
180
return NewHTTPError (http .StatusMethodNotAllowed )
179
181
}
180
182
183
+ unixEpochTime = time .Unix (0 , 0 )
184
+
181
185
logger = log .New ("echo" )
182
186
)
183
187
@@ -269,6 +273,12 @@ func (e *Echo) Debug() bool {
269
273
return e .debug
270
274
}
271
275
276
+ // AutoIndex enables automatically creates a directory listing if the directory
277
+ // doesn't contain an index page.
278
+ func (e * Echo ) AutoIndex (on bool ) {
279
+ e .autoIndex = on
280
+ }
281
+
272
282
// Hook registers a callback which is invoked from `Echo#ServerHTTP` as the first
273
283
// statement. Hook is useful if you want to modify response/response objects even
274
284
// before it hits the router or any middleware.
@@ -386,19 +396,19 @@ func (e *Echo) Static(path, dir string) {
386
396
// ServeDir serves files from a directory.
387
397
func (e * Echo ) ServeDir (path , dir string ) {
388
398
e .Get (path + "*" , func (c * Context ) error {
389
- return serveFile (dir , c .P (0 ), c ) // Param `_*`
399
+ return e . serveFile (dir , c .P (0 ), c ) // Param `_*`
390
400
})
391
401
}
392
402
393
403
// ServeFile serves a file.
394
404
func (e * Echo ) ServeFile (path , file string ) {
395
405
e .Get (path , func (c * Context ) error {
396
406
dir , file := filepath .Split (file )
397
- return serveFile (dir , file , c )
407
+ return e . serveFile (dir , file , c )
398
408
})
399
409
}
400
410
401
- func serveFile (dir , file string , c * Context ) error {
411
+ func ( e * Echo ) serveFile (dir , file string , c * Context ) ( err error ) {
402
412
fs := http .Dir (dir )
403
413
f , err := fs .Open (file )
404
414
if err != nil {
@@ -408,16 +418,49 @@ func serveFile(dir, file string, c *Context) error {
408
418
409
419
fi , _ := f .Stat ()
410
420
if fi .IsDir () {
411
- file = filepath .Join (file , indexFile )
421
+ if checkLastModified (c .response , c .request , fi .ModTime ()) {
422
+ return
423
+ }
424
+ d := f
425
+
426
+ // Index file
427
+ file = filepath .Join (file , indexPage )
412
428
f , err = fs .Open (file )
413
429
if err != nil {
430
+ if e .autoIndex {
431
+ // Auto index
432
+ return listDir (d , c )
433
+ }
414
434
return NewHTTPError (http .StatusForbidden )
415
435
}
416
- fi , _ = f .Stat ()
436
+ fi , _ = f .Stat () // Index file stat
417
437
}
418
438
419
439
http .ServeContent (c .response , c .request , fi .Name (), fi .ModTime (), f )
420
- return nil
440
+ return
441
+ }
442
+
443
+ func listDir (d http.File , c * Context ) (err error ) {
444
+ dirs , err := d .Readdir (- 1 )
445
+ if err != nil {
446
+ return err
447
+ }
448
+
449
+ // Create directory index
450
+ w := c .Response ()
451
+ w .Header ().Set (ContentType , TextHTMLCharsetUTF8 )
452
+ fmt .Fprintf (w , "<pre>\n " )
453
+ for _ , d := range dirs {
454
+ name := d .Name ()
455
+ color := "#212121"
456
+ if d .IsDir () {
457
+ color = "#e91e63"
458
+ name += "/"
459
+ }
460
+ fmt .Fprintf (w , "<a href=\" %s\" style=\" color: %s;\" >%s</a>\n " , name , color , name )
461
+ }
462
+ fmt .Fprintf (w , "</pre>\n " )
463
+ return
421
464
}
422
465
423
466
// Group creates a new sub router with prefix. It inherits all properties from
@@ -650,3 +693,25 @@ func (binder) Bind(r *http.Request, i interface{}) (err error) {
650
693
}
651
694
return
652
695
}
696
+
697
+ // Source: net/http/fs.go
698
+ func checkLastModified (w http.ResponseWriter , r * http.Request , modtime time.Time ) bool {
699
+ if modtime .IsZero () || modtime .Equal (unixEpochTime ) {
700
+ // If the file doesn't have a modtime (IsZero), or the modtime
701
+ // is obviously garbage (Unix time == 0), then ignore modtimes
702
+ // and don't process the If-Modified-Since header.
703
+ return false
704
+ }
705
+
706
+ // The Date-Modified header truncates sub-second precision, so
707
+ // use mtime < t+1s instead of mtime <= t to check for unmodified.
708
+ if t , err := time .Parse (http .TimeFormat , r .Header .Get ("If-Modified-Since" )); err == nil && modtime .Before (t .Add (1 * time .Second )) {
709
+ h := w .Header ()
710
+ delete (h , "Content-Type" )
711
+ delete (h , "Content-Length" )
712
+ w .WriteHeader (http .StatusNotModified )
713
+ return true
714
+ }
715
+ w .Header ().Set ("Last-Modified" , modtime .UTC ().Format (http .TimeFormat ))
716
+ return false
717
+ }
0 commit comments