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