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