// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. package ghttp import ( "net/http" "os" "sort" "strings" "github.com/gogf/gf/os/gres" "github.com/gogf/gf/encoding/ghtml" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/os/gspath" "github.com/gogf/gf/os/gtime" ) // 服务静态文件信息 type staticServeFile struct { file *gres.File // 资源文件 path string // 文件路径 dir bool // 是否目录 } // 默认HTTP Server处理入口,http包底层默认使用了gorutine异步处理请求,所以这里不再异步执行 func (s *Server) defaultHttpHandle(w http.ResponseWriter, r *http.Request) { s.handleRequest(w, r) } // 执行处理HTTP请求, // 首先,查找是否有对应域名的处理接口配置; // 其次,如果没有对应的自定义处理接口配置,那么走默认的域名处理接口配置; // 最后,如果以上都没有找到处理接口,那么进行文件处理; func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) { // 重写规则判断 if len(s.config.Rewrites) > 0 { if rewrite, ok := s.config.Rewrites[r.URL.Path]; ok { r.URL.Path = rewrite } } // URI默认值 if r.URL.Path == "" { r.URL.Path = "/" } // 去掉末尾的"/"号 if r.URL.Path != "/" { for r.URL.Path[len(r.URL.Path)-1] == '/' { r.URL.Path = r.URL.Path[:len(r.URL.Path)-1] } } // 创建请求处理对象 request := newRequest(s, r, w) defer func() { // 设置请求完成时间 request.LeaveTime = gtime.Microsecond() // 事件 - BeforeOutput if !request.IsExited() { s.callHookHandler(HOOK_BEFORE_OUTPUT, request) } // 如果没有产生异常状态,那么设置返回状态为200 if request.Response.Status == 0 { if request.Middleware.served || request.Response.buffer.Len() > 0 { request.Response.Status = http.StatusOK } else { request.Response.WriteStatus(http.StatusNotFound) } } // error log if e := recover(); e != nil { request.Response.WriteStatus(http.StatusInternalServerError) s.handleErrorLog(e, request) } // access log s.handleAccessLog(request) // 输出Cookie request.Cookie.Output() // 输出缓冲区 request.Response.Output() // 事件 - AfterOutput if !request.IsExited() { s.callHookHandler(HOOK_AFTER_OUTPUT, request) } // 更新Session会话超时时间 request.Session.UpdateExpire() }() // ============================================================ // 优先级控制: // 静态文件 > 动态服务 > 静态目录 // ============================================================ serveFile := (*staticServeFile)(nil) // 优先执行静态文件检索(检测是否存在对应的静态文件,包括index files处理) if s.config.FileServerEnabled { serveFile = s.searchStaticFile(r.URL.Path) if serveFile != nil { request.isFileRequest = true } } // 动态服务检索 if serveFile == nil || serveFile.dir { request.handlers, request.hasHookHandler = s.getHandlersWithCache(request) } // 判断最终对该请求提供的服务方式 if serveFile != nil && serveFile.dir && request.handlers != nil { request.isFileRequest = false } // 事件 - BeforeServe s.callHookHandler(HOOK_BEFORE_SERVE, request) // 执行静态文件服务/回调控制器/执行对象/方法 if !request.IsExited() { if request.isFileRequest { // 静态服务 s.serveFile(request, serveFile) } else { if len(request.handlers) > 0 { // 动态服务 request.Middleware.Next() } else { if serveFile != nil && serveFile.dir { // 静态目录 s.serveFile(request, serveFile) } else { if len(request.Response.Header()) == 0 && request.Response.Status == 0 && request.Response.BufferLength() == 0 { request.Response.WriteStatus(http.StatusNotFound) } } } } } // 事件 - AfterServe if !request.IsExited() { s.callHookHandler(HOOK_AFTER_SERVE, request) } } // 查找静态文件的绝对路径 func (s *Server) searchStaticFile(uri string) *staticServeFile { // 优先查找URI映射关系 var file *gres.File var path string var dir bool if len(s.config.StaticPaths) > 0 { for _, item := range s.config.StaticPaths { if len(uri) >= len(item.prefix) && strings.EqualFold(item.prefix, uri[0:len(item.prefix)]) { // 防止类似 /static/style 映射到 /static/style.css 的情况 if len(uri) > len(item.prefix) && uri[len(item.prefix)] != '/' { continue } // 优先检索资源管理器 file = gres.GetWithIndex(item.path+uri[len(item.prefix):], s.config.IndexFiles) if file != nil { return &staticServeFile{ file: file, dir: file.FileInfo().IsDir(), } } // 其次检索文件系统 path, dir = gspath.Search(item.path, uri[len(item.prefix):], s.config.IndexFiles...) if path != "" { return &staticServeFile{ path: path, dir: dir, } } } } } // 其次查找root和search path if len(s.config.SearchPaths) > 0 { for _, p := range s.config.SearchPaths { // 优先检索资源管理器 file = gres.GetWithIndex(p+uri, s.config.IndexFiles) if file != nil { return &staticServeFile{ file: file, dir: file.FileInfo().IsDir(), } } // 其次检索文件系统 if path, dir = gspath.Search(p, uri, s.config.IndexFiles...); path != "" { return &staticServeFile{ path: path, dir: dir, } } } } // 最后通过资源对象+URI进行文件检索 if len(s.config.StaticPaths) == 0 && len(s.config.SearchPaths) == 0 { if file = gres.GetWithIndex(uri, s.config.IndexFiles); file != nil { return &staticServeFile{ file: file, dir: file.FileInfo().IsDir(), } } } return nil } // http server静态文件处理,path可以为相对路径也可以为绝对路径 func (s *Server) serveFile(r *Request, f *staticServeFile, allowIndex ...bool) { // 使用资源文件 if f.file != nil { if f.dir { if s.config.IndexFolder || (len(allowIndex) > 0 && allowIndex[0]) { s.listDir(r, f.file) } else { r.Response.WriteStatus(http.StatusForbidden) } } else { info := f.file.FileInfo() http.ServeContent(r.Response.Writer, r.Request, info.Name(), info.ModTime(), f.file) } return } // 使用磁盘文件 file, err := os.Open(f.path) if err != nil { r.Response.WriteStatus(http.StatusForbidden) return } defer file.Close() info, _ := file.Stat() if info.IsDir() { if s.config.IndexFolder || (len(allowIndex) > 0 && allowIndex[0]) { s.listDir(r, file) } else { r.Response.WriteStatus(http.StatusForbidden) } } else { // 读取文件内容返回, no buffer http.ServeContent(r.Response.Writer, r.Request, info.Name(), info.ModTime(), file) } } // 显示目录列表 func (s *Server) listDir(r *Request, f http.File) { files, err := f.Readdir(-1) if err != nil { r.Response.WriteStatus(http.StatusInternalServerError, "Error reading directory") return } sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() }) if r.Response.Header().Get("Content-Type") == "" { r.Response.Header().Set("Content-Type", "text/html; charset=utf-8") } r.Response.Write(``) r.Response.Write(``) r.Response.Write(``) r.Response.Writef(`

Index of %s

`, r.URL.Path) r.Response.Writef(`
`) r.Response.Write(``) if r.URL.Path != "/" { r.Response.Write(``) r.Response.Writef(``, gfile.Dir(r.URL.Path)) r.Response.Write(``) } name := "" size := "" for _, file := range files { name = file.Name() size = gfile.FormatSize(file.Size()) if file.IsDir() { name += "/" size = "-" } r.Response.Write(``) r.Response.Writef(``, r.URL.Path, name, ghtml.SpecialChars(name)) r.Response.Writef(``, gtime.New(file.ModTime()).ISO8601()) r.Response.Writef(``, size) r.Response.Write(``) } r.Response.Write(`
..
%s%s%s
`) r.Response.Write(``) r.Response.Write(``) }