Micro & pluggable web framework for Go
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
4.1KB

  1. // Copyright 2015 The Tango Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package tango
  5. import (
  6. "net/http"
  7. "os"
  8. "path"
  9. "path/filepath"
  10. "strings"
  11. )
  12. // StaticOptions defines Static middleware's options
  13. type StaticOptions struct {
  14. RootPath string
  15. Prefix string
  16. IndexFiles []string
  17. ListDir bool
  18. FilterExts []string
  19. // FileSystem is the interface for supporting any implmentation of file system.
  20. FileSystem http.FileSystem
  21. }
  22. // IsFilterExt decribes if rPath's ext match filter ext
  23. func (s *StaticOptions) IsFilterExt(rPath string) bool {
  24. rext := path.Ext(rPath)
  25. for _, ext := range s.FilterExts {
  26. if rext == ext {
  27. return true
  28. }
  29. }
  30. return false
  31. }
  32. func prepareStaticOptions(options []StaticOptions) StaticOptions {
  33. var opt StaticOptions
  34. if len(options) > 0 {
  35. opt = options[0]
  36. }
  37. // Defaults
  38. if len(opt.RootPath) == 0 {
  39. opt.RootPath = "./public"
  40. }
  41. if len(opt.Prefix) > 0 {
  42. if opt.Prefix[0] != '/' {
  43. opt.Prefix = "/" + opt.Prefix
  44. }
  45. }
  46. if len(opt.IndexFiles) == 0 {
  47. opt.IndexFiles = []string{"index.html", "index.htm"}
  48. }
  49. if opt.FileSystem == nil {
  50. ps, _ := filepath.Abs(opt.RootPath)
  51. opt.FileSystem = http.Dir(ps)
  52. }
  53. return opt
  54. }
  55. // Static return a middleware for serving static files
  56. func Static(opts ...StaticOptions) HandlerFunc {
  57. return func(ctx *Context) {
  58. if ctx.Req().Method != "GET" && ctx.Req().Method != "HEAD" {
  59. ctx.Next()
  60. return
  61. }
  62. opt := prepareStaticOptions(opts)
  63. var rPath = ctx.Req().URL.Path
  64. // if defined prefix, then only check prefix
  65. if opt.Prefix != "" {
  66. if !strings.HasPrefix(ctx.Req().URL.Path, opt.Prefix) {
  67. ctx.Next()
  68. return
  69. }
  70. if len(opt.Prefix) == len(ctx.Req().URL.Path) {
  71. rPath = ""
  72. } else {
  73. rPath = ctx.Req().URL.Path[len(opt.Prefix):]
  74. }
  75. }
  76. f, err := opt.FileSystem.Open(strings.TrimLeft(rPath, "/"))
  77. if err != nil {
  78. if os.IsNotExist(err) {
  79. if opt.Prefix != "" {
  80. ctx.Result = NotFound()
  81. } else {
  82. ctx.Next()
  83. return
  84. }
  85. } else {
  86. ctx.Result = InternalServerError(err.Error())
  87. }
  88. ctx.HandleError()
  89. return
  90. }
  91. defer f.Close()
  92. finfo, err := f.Stat()
  93. if err != nil {
  94. ctx.Result = InternalServerError(err.Error())
  95. ctx.HandleError()
  96. return
  97. }
  98. if !finfo.IsDir() {
  99. if len(opt.FilterExts) > 0 && !opt.IsFilterExt(rPath) {
  100. ctx.Next()
  101. return
  102. }
  103. http.ServeContent(ctx, ctx.Req(), finfo.Name(), finfo.ModTime(), f)
  104. return
  105. }
  106. // try serving index.html or index.htm
  107. if len(opt.IndexFiles) > 0 {
  108. for _, index := range opt.IndexFiles {
  109. fi, err := opt.FileSystem.Open(strings.TrimLeft(path.Join(rPath, index), "/"))
  110. if err != nil {
  111. if !os.IsNotExist(err) {
  112. ctx.Result = InternalServerError(err.Error())
  113. ctx.HandleError()
  114. return
  115. }
  116. } else {
  117. finfo, err = fi.Stat()
  118. if err != nil {
  119. fi.Close()
  120. ctx.Result = InternalServerError(err.Error())
  121. ctx.HandleError()
  122. return
  123. }
  124. if !finfo.IsDir() {
  125. http.ServeContent(ctx, ctx.Req(), finfo.Name(), finfo.ModTime(), fi)
  126. fi.Close()
  127. return
  128. }
  129. fi.Close()
  130. }
  131. }
  132. }
  133. // list dir files
  134. if opt.ListDir {
  135. ctx.Header().Set("Content-Type", "text/html; charset=UTF-8")
  136. ctx.WriteString(`<ul style="list-style-type:none;line-height:32px;">`)
  137. if rPath != "/" {
  138. ctx.WriteString(`<li>&nbsp; &nbsp; <a href="` + path.Join("/", opt.Prefix, filepath.Dir(rPath)) + `">..</a></li>`)
  139. }
  140. fs, err := f.Readdir(0)
  141. if err != nil {
  142. ctx.Result = InternalServerError(err.Error())
  143. ctx.HandleError()
  144. return
  145. }
  146. for _, fi := range fs {
  147. if fi.IsDir() {
  148. ctx.WriteString(`<li>┖ <a href="` + path.Join("/", opt.Prefix, rPath, fi.Name()) + `">` + path.Base(fi.Name()) + `</a></li>`)
  149. } else {
  150. if len(opt.FilterExts) > 0 && !opt.IsFilterExt(fi.Name()) {
  151. continue
  152. }
  153. ctx.WriteString(`<li>&nbsp; &nbsp; <a href="` + path.Join("/", opt.Prefix, rPath, fi.Name()) + `">` + filepath.Base(fi.Name()) + `</a></li>`)
  154. }
  155. }
  156. ctx.WriteString("</ul>")
  157. return
  158. }
  159. ctx.Next()
  160. }
  161. }