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