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  }