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  }