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  }