ghttp_server_handler.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. package ghttp
  7. import (
  8. "github.com/gogf/gf/errors/gcode"
  9. "github.com/gogf/gf/internal/intlog"
  10. "net/http"
  11. "os"
  12. "sort"
  13. "strings"
  14. "github.com/gogf/gf/text/gstr"
  15. "github.com/gogf/gf/errors/gerror"
  16. "github.com/gogf/gf/os/gres"
  17. "github.com/gogf/gf/encoding/ghtml"
  18. "github.com/gogf/gf/os/gfile"
  19. "github.com/gogf/gf/os/gspath"
  20. "github.com/gogf/gf/os/gtime"
  21. )
  22. // ServeHTTP is the default handler for http request.
  23. // It should not create new goroutine handling the request as
  24. // it's called by am already created new goroutine from http.Server.
  25. //
  26. // This function also make serve implementing the interface of http.Handler.
  27. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  28. // Max body size limit.
  29. if s.config.ClientMaxBodySize > 0 {
  30. r.Body = http.MaxBytesReader(w, r.Body, s.config.ClientMaxBodySize)
  31. }
  32. // Rewrite feature checks.
  33. if len(s.config.Rewrites) > 0 {
  34. if rewrite, ok := s.config.Rewrites[r.URL.Path]; ok {
  35. r.URL.Path = rewrite
  36. }
  37. }
  38. // Remove char '/' in the tail of URI.
  39. if r.URL.Path != "/" {
  40. for len(r.URL.Path) > 0 && r.URL.Path[len(r.URL.Path)-1] == '/' {
  41. r.URL.Path = r.URL.Path[:len(r.URL.Path)-1]
  42. }
  43. }
  44. // Default URI value if it's empty.
  45. if r.URL.Path == "" {
  46. r.URL.Path = "/"
  47. }
  48. // Create a new request object.
  49. request := newRequest(s, r, w)
  50. defer func() {
  51. request.LeaveTime = gtime.TimestampMilli()
  52. // error log handling.
  53. if request.error != nil {
  54. s.handleErrorLog(request.error, request)
  55. } else {
  56. if exception := recover(); exception != nil {
  57. request.Response.WriteStatus(http.StatusInternalServerError)
  58. if err, ok := exception.(error); ok {
  59. if code := gerror.Code(err); code != gcode.CodeNil {
  60. s.handleErrorLog(err, request)
  61. } else {
  62. s.handleErrorLog(gerror.WrapCodeSkip(gcode.CodeInternalError, 1, err, ""), request)
  63. }
  64. } else {
  65. s.handleErrorLog(gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%+v", exception), request)
  66. }
  67. }
  68. }
  69. // access log handling.
  70. s.handleAccessLog(request)
  71. // Close the session, which automatically update the TTL
  72. // of the session if it exists.
  73. request.Session.Close()
  74. // Close the request and response body
  75. // to release the file descriptor in time.
  76. err := request.Request.Body.Close()
  77. if err != nil {
  78. intlog.Error(request.Context(), err)
  79. }
  80. if request.Request.Response != nil {
  81. err = request.Request.Response.Body.Close()
  82. if err != nil {
  83. intlog.Error(request.Context(), err)
  84. }
  85. }
  86. }()
  87. // ============================================================
  88. // Priority:
  89. // Static File > Dynamic Service > Static Directory
  90. // ============================================================
  91. // Search the static file with most high priority,
  92. // which also handle the index files feature.
  93. if s.config.FileServerEnabled {
  94. request.StaticFile = s.searchStaticFile(r.URL.Path)
  95. if request.StaticFile != nil {
  96. request.isFileRequest = true
  97. }
  98. }
  99. // Search the dynamic service handler.
  100. request.handlers, request.hasHookHandler, request.hasServeHandler = s.getHandlersWithCache(request)
  101. // Check the service type static or dynamic for current request.
  102. if request.StaticFile != nil && request.StaticFile.IsDir && request.hasServeHandler {
  103. request.isFileRequest = false
  104. }
  105. // HOOK - BeforeServe
  106. s.callHookHandler(HookBeforeServe, request)
  107. // Core serving handling.
  108. if !request.IsExited() {
  109. if request.isFileRequest {
  110. // Static file service.
  111. s.serveFile(request, request.StaticFile)
  112. } else {
  113. if len(request.handlers) > 0 {
  114. // Dynamic service.
  115. request.Middleware.Next()
  116. } else {
  117. if request.StaticFile != nil && request.StaticFile.IsDir {
  118. // Serve the directory.
  119. s.serveFile(request, request.StaticFile)
  120. } else {
  121. if len(request.Response.Header()) == 0 &&
  122. request.Response.Status == 0 &&
  123. request.Response.BufferLength() == 0 {
  124. request.Response.WriteHeader(http.StatusNotFound)
  125. }
  126. }
  127. }
  128. }
  129. }
  130. // HOOK - AfterServe
  131. if !request.IsExited() {
  132. s.callHookHandler(HookAfterServe, request)
  133. }
  134. // HOOK - BeforeOutput
  135. if !request.IsExited() {
  136. s.callHookHandler(HookBeforeOutput, request)
  137. }
  138. // HTTP status checking.
  139. if request.Response.Status == 0 {
  140. if request.StaticFile != nil || request.Middleware.served || request.Response.buffer.Len() > 0 {
  141. request.Response.WriteHeader(http.StatusOK)
  142. } else {
  143. request.Response.WriteHeader(http.StatusNotFound)
  144. }
  145. }
  146. // HTTP status handler.
  147. if request.Response.Status != http.StatusOK {
  148. statusFuncArray := s.getStatusHandler(request.Response.Status, request)
  149. for _, f := range statusFuncArray {
  150. // Call custom status handler.
  151. niceCallFunc(func() {
  152. f(request)
  153. })
  154. if request.IsExited() {
  155. break
  156. }
  157. }
  158. }
  159. // Automatically set the session id to cookie
  160. // if it creates a new session id in this request
  161. // and SessionCookieOutput is enabled.
  162. if s.config.SessionCookieOutput &&
  163. request.Session.IsDirty() &&
  164. request.Session.Id() != request.GetSessionId() {
  165. request.Cookie.SetSessionId(request.Session.Id())
  166. }
  167. // Output the cookie content to client.
  168. request.Cookie.Flush()
  169. // Output the buffer content to client.
  170. request.Response.Flush()
  171. // HOOK - AfterOutput
  172. if !request.IsExited() {
  173. s.callHookHandler(HookAfterOutput, request)
  174. }
  175. }
  176. // searchStaticFile searches the file with given URI.
  177. // It returns a file struct specifying the file information.
  178. func (s *Server) searchStaticFile(uri string) *staticFile {
  179. var (
  180. file *gres.File
  181. path string
  182. dir bool
  183. )
  184. // Firstly search the StaticPaths mapping.
  185. if len(s.config.StaticPaths) > 0 {
  186. for _, item := range s.config.StaticPaths {
  187. if len(uri) >= len(item.prefix) && strings.EqualFold(item.prefix, uri[0:len(item.prefix)]) {
  188. // To avoid case like: /static/style -> /static/style.css
  189. if len(uri) > len(item.prefix) && uri[len(item.prefix)] != '/' {
  190. continue
  191. }
  192. file = gres.GetWithIndex(item.path+uri[len(item.prefix):], s.config.IndexFiles)
  193. if file != nil {
  194. return &staticFile{
  195. File: file,
  196. IsDir: file.FileInfo().IsDir(),
  197. }
  198. }
  199. path, dir = gspath.Search(item.path, uri[len(item.prefix):], s.config.IndexFiles...)
  200. if path != "" {
  201. return &staticFile{
  202. Path: path,
  203. IsDir: dir,
  204. }
  205. }
  206. }
  207. }
  208. }
  209. // Secondly search the root and searching paths.
  210. if len(s.config.SearchPaths) > 0 {
  211. for _, p := range s.config.SearchPaths {
  212. file = gres.GetWithIndex(p+uri, s.config.IndexFiles)
  213. if file != nil {
  214. return &staticFile{
  215. File: file,
  216. IsDir: file.FileInfo().IsDir(),
  217. }
  218. }
  219. if path, dir = gspath.Search(p, uri, s.config.IndexFiles...); path != "" {
  220. return &staticFile{
  221. Path: path,
  222. IsDir: dir,
  223. }
  224. }
  225. }
  226. }
  227. // Lastly search the resource manager.
  228. if len(s.config.StaticPaths) == 0 && len(s.config.SearchPaths) == 0 {
  229. if file = gres.GetWithIndex(uri, s.config.IndexFiles); file != nil {
  230. return &staticFile{
  231. File: file,
  232. IsDir: file.FileInfo().IsDir(),
  233. }
  234. }
  235. }
  236. return nil
  237. }
  238. // serveFile serves the static file for client.
  239. // The optional parameter <allowIndex> specifies if allowing directory listing if <f> is directory.
  240. func (s *Server) serveFile(r *Request, f *staticFile, allowIndex ...bool) {
  241. // Use resource file from memory.
  242. if f.File != nil {
  243. if f.IsDir {
  244. if s.config.IndexFolder || (len(allowIndex) > 0 && allowIndex[0]) {
  245. s.listDir(r, f.File)
  246. } else {
  247. r.Response.WriteStatus(http.StatusForbidden)
  248. }
  249. } else {
  250. info := f.File.FileInfo()
  251. r.Response.wroteHeader = true
  252. http.ServeContent(r.Response.Writer.RawWriter(), r.Request, info.Name(), info.ModTime(), f.File)
  253. }
  254. return
  255. }
  256. // Use file from dist.
  257. file, err := os.Open(f.Path)
  258. if err != nil {
  259. r.Response.WriteStatus(http.StatusForbidden)
  260. return
  261. }
  262. defer file.Close()
  263. // Clear the response buffer before file serving.
  264. // It ignores all custom buffer content and uses the file content.
  265. r.Response.ClearBuffer()
  266. info, _ := file.Stat()
  267. if info.IsDir() {
  268. if s.config.IndexFolder || (len(allowIndex) > 0 && allowIndex[0]) {
  269. s.listDir(r, file)
  270. } else {
  271. r.Response.WriteStatus(http.StatusForbidden)
  272. }
  273. } else {
  274. r.Response.wroteHeader = true
  275. http.ServeContent(r.Response.Writer.RawWriter(), r.Request, info.Name(), info.ModTime(), file)
  276. }
  277. }
  278. // listDir lists the sub files of specified directory as HTML content to client.
  279. func (s *Server) listDir(r *Request, f http.File) {
  280. files, err := f.Readdir(-1)
  281. if err != nil {
  282. r.Response.WriteStatus(http.StatusInternalServerError, "Error reading directory")
  283. return
  284. }
  285. // The folder type has the most priority than file.
  286. sort.Slice(files, func(i, j int) bool {
  287. if files[i].IsDir() && !files[j].IsDir() {
  288. return true
  289. }
  290. if !files[i].IsDir() && files[j].IsDir() {
  291. return false
  292. }
  293. return files[i].Name() < files[j].Name()
  294. })
  295. if r.Response.Header().Get("Content-Type") == "" {
  296. r.Response.Header().Set("Content-Type", "text/html; charset=utf-8")
  297. }
  298. r.Response.Write(`<html>`)
  299. r.Response.Write(`<head>`)
  300. r.Response.Write(`<style>`)
  301. r.Response.Write(`body {font-family:Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;}`)
  302. r.Response.Write(`</style>`)
  303. r.Response.Write(`</head>`)
  304. r.Response.Write(`<body>`)
  305. r.Response.Writef(`<h1>Index of %s</h1>`, r.URL.Path)
  306. r.Response.Writef(`<hr />`)
  307. r.Response.Write(`<table>`)
  308. if r.URL.Path != "/" {
  309. r.Response.Write(`<tr>`)
  310. r.Response.Writef(`<td><a href="%s">..</a></td>`, gfile.Dir(r.URL.Path))
  311. r.Response.Write(`</tr>`)
  312. }
  313. name := ""
  314. size := ""
  315. prefix := gstr.TrimRight(r.URL.Path, "/")
  316. for _, file := range files {
  317. name = file.Name()
  318. size = gfile.FormatSize(file.Size())
  319. if file.IsDir() {
  320. name += "/"
  321. size = "-"
  322. }
  323. r.Response.Write(`<tr>`)
  324. r.Response.Writef(`<td><a href="%s/%s">%s</a></td>`, prefix, name, ghtml.SpecialChars(name))
  325. r.Response.Writef(`<td style="width:300px;text-align:center;">%s</td>`, gtime.New(file.ModTime()).ISO8601())
  326. r.Response.Writef(`<td style="width:80px;text-align:right;">%s</td>`, size)
  327. r.Response.Write(`</tr>`)
  328. }
  329. r.Response.Write(`</table>`)
  330. r.Response.Write(`</body>`)
  331. r.Response.Write(`</html>`)
  332. }