github.com/zhongdalu/gf@v1.0.0/g/net/ghttp/ghttp_server_handler.go (about)

     1  // Copyright 2017 gf Author(https://github.com/zhongdalu/gf). 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/zhongdalu/gf.
     6  
     7  package ghttp
     8  
     9  import (
    10  	"fmt"
    11  	"github.com/zhongdalu/gf/g/encoding/ghtml"
    12  	"github.com/zhongdalu/gf/g/os/gspath"
    13  	"github.com/zhongdalu/gf/g/os/gtime"
    14  	"net/http"
    15  	"os"
    16  	"reflect"
    17  	"sort"
    18  	"strings"
    19  )
    20  
    21  // 默认HTTP Server处理入口,http包底层默认使用了gorutine异步处理请求,所以这里不再异步执行
    22  func (s *Server) defaultHttpHandle(w http.ResponseWriter, r *http.Request) {
    23  	s.handleRequest(w, r)
    24  }
    25  
    26  // 执行处理HTTP请求,
    27  // 首先,查找是否有对应域名的处理接口配置;
    28  // 其次,如果没有对应的自定义处理接口配置,那么走默认的域名处理接口配置;
    29  // 最后,如果以上都没有找到处理接口,那么进行文件处理;
    30  func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
    31  	// 重写规则判断
    32  	if len(s.config.Rewrites) > 0 {
    33  		if rewrite, ok := s.config.Rewrites[r.URL.Path]; ok {
    34  			r.URL.Path = rewrite
    35  		}
    36  	}
    37  
    38  	// URI默认值
    39  	if r.URL.Path == "" {
    40  		r.URL.Path = "/"
    41  	}
    42  
    43  	// 去掉末尾的"/"号
    44  	if r.URL.Path != "/" {
    45  		for r.URL.Path[len(r.URL.Path)-1] == '/' {
    46  			r.URL.Path = r.URL.Path[:len(r.URL.Path)-1]
    47  		}
    48  	}
    49  
    50  	// 创建请求处理对象
    51  	request := newRequest(s, r, w)
    52  
    53  	defer func() {
    54  		// 设置请求完成时间
    55  		request.LeaveTime = gtime.Microsecond()
    56  		// 事件 - BeforeOutput
    57  		if !request.IsExited() {
    58  			s.callHookHandler(HOOK_BEFORE_OUTPUT, request)
    59  		}
    60  		// 如果没有产生异常状态,那么设置返回状态为200
    61  		if request.Response.Status == 0 {
    62  			request.Response.Status = http.StatusOK
    63  		}
    64  		// error log
    65  		if e := recover(); e != nil {
    66  			request.Response.WriteStatus(http.StatusInternalServerError)
    67  			s.handleErrorLog(e, request)
    68  		}
    69  		// access log
    70  		s.handleAccessLog(request)
    71  		// 输出Cookie
    72  		request.Cookie.Output()
    73  		// 输出缓冲区
    74  		request.Response.Output()
    75  		// 事件 - AfterOutput
    76  		if !request.IsExited() {
    77  			s.callHookHandler(HOOK_AFTER_OUTPUT, request)
    78  		}
    79  		// 更新Session会话超时时间
    80  		request.Session.UpdateExpire()
    81  	}()
    82  
    83  	// ============================================================
    84  	// 优先级控制:
    85  	// 静态文件 > 动态服务 > 静态目录
    86  	// ============================================================
    87  
    88  	staticFile := ""
    89  	isStaticDir := false
    90  	// 优先执行静态文件检索(检测是否存在对应的静态文件,包括index files处理)
    91  	if s.config.FileServerEnabled {
    92  		staticFile, isStaticDir = s.searchStaticFile(r.URL.Path)
    93  		if staticFile != "" {
    94  			request.isFileRequest = true
    95  		}
    96  	}
    97  
    98  	// 动态服务检索
    99  	handler := (*handlerItem)(nil)
   100  	if !request.isFileRequest || isStaticDir {
   101  		if parsedItem := s.getServeHandlerWithCache(request); parsedItem != nil {
   102  			handler = parsedItem.handler
   103  			for k, v := range parsedItem.values {
   104  				request.routerVars[k] = v
   105  			}
   106  			request.Router = parsedItem.handler.router
   107  		}
   108  	}
   109  
   110  	// 判断最终对该请求提供的服务方式
   111  	if isStaticDir && handler != nil {
   112  		request.isFileRequest = false
   113  	}
   114  
   115  	// 事件 - BeforeServe
   116  	s.callHookHandler(HOOK_BEFORE_SERVE, request)
   117  
   118  	// 执行静态文件服务/回调控制器/执行对象/方法
   119  	if !request.IsExited() {
   120  		// 需要再次判断文件是否真实存在,
   121  		// 因为文件检索可能使用了缓存,从健壮性考虑这里需要二次判断
   122  		if request.isFileRequest /* && gfile.Exists(staticFile) */ {
   123  			s.serveFile(request, staticFile)
   124  		} else {
   125  			if handler != nil {
   126  				// 动态服务
   127  				s.callServeHandler(handler, request)
   128  			} else {
   129  				if isStaticDir {
   130  					// 静态目录
   131  					s.serveFile(request, staticFile)
   132  				} else {
   133  					if len(request.Response.Header()) == 0 &&
   134  						request.Response.Status == 0 &&
   135  						request.Response.BufferLength() == 0 {
   136  						request.Response.WriteStatus(http.StatusNotFound)
   137  					}
   138  				}
   139  			}
   140  		}
   141  	}
   142  
   143  	// 事件 - AfterServe
   144  	if !request.IsExited() {
   145  		s.callHookHandler(HOOK_AFTER_SERVE, request)
   146  	}
   147  }
   148  
   149  // 查找静态文件的绝对路径
   150  func (s *Server) searchStaticFile(uri string) (filePath string, isDir bool) {
   151  	// 优先查找URI映射
   152  	if len(s.config.StaticPaths) > 0 {
   153  		for _, item := range s.config.StaticPaths {
   154  			if len(uri) >= len(item.prefix) && strings.EqualFold(item.prefix, uri[0:len(item.prefix)]) {
   155  				// 防止类似 /static/style 映射到 /static/style.css 的情况
   156  				if len(uri) > len(item.prefix) && uri[len(item.prefix)] != '/' {
   157  					continue
   158  				}
   159  				return gspath.Search(item.path, uri[len(item.prefix):], s.config.IndexFiles...)
   160  			}
   161  		}
   162  	}
   163  	// 其次查找root和search path
   164  	if len(s.config.SearchPaths) > 0 {
   165  		for _, path := range s.config.SearchPaths {
   166  			if filePath, isDir = gspath.Search(path, uri, s.config.IndexFiles...); filePath != "" {
   167  				return filePath, isDir
   168  			}
   169  		}
   170  	}
   171  	return "", false
   172  }
   173  
   174  // 调用服务接口
   175  func (s *Server) callServeHandler(h *handlerItem, r *Request) {
   176  	if h.faddr == nil {
   177  		c := reflect.New(h.ctype)
   178  		s.niceCallFunc(func() {
   179  			c.MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(r)})
   180  		})
   181  		if !r.IsExited() {
   182  			s.niceCallFunc(func() {
   183  				c.MethodByName(h.fname).Call(nil)
   184  			})
   185  		}
   186  		if !r.IsExited() {
   187  			s.niceCallFunc(func() {
   188  				c.MethodByName("Shut").Call(nil)
   189  			})
   190  		}
   191  	} else {
   192  		if h.finit != nil {
   193  			s.niceCallFunc(func() {
   194  				h.finit(r)
   195  			})
   196  		}
   197  		if !r.IsExited() {
   198  			s.niceCallFunc(func() {
   199  				h.faddr(r)
   200  			})
   201  		}
   202  		if h.fshut != nil && !r.IsExited() {
   203  			s.niceCallFunc(func() {
   204  				h.fshut(r)
   205  			})
   206  		}
   207  	}
   208  }
   209  
   210  // 友好地调用方法
   211  func (s *Server) niceCallFunc(f func()) {
   212  	defer func() {
   213  		if err := recover(); err != nil {
   214  			switch err {
   215  			case gEXCEPTION_EXIT:
   216  				fallthrough
   217  			case gEXCEPTION_EXIT_ALL:
   218  				return
   219  			default:
   220  				panic(err)
   221  			}
   222  		}
   223  	}()
   224  	f()
   225  }
   226  
   227  // http server静态文件处理,path可以为相对路径也可以为绝对路径
   228  func (s *Server) serveFile(r *Request, path string) {
   229  	f, err := os.Open(path)
   230  	if err != nil {
   231  		r.Response.WriteStatus(http.StatusForbidden)
   232  		return
   233  	}
   234  	defer f.Close()
   235  	info, _ := f.Stat()
   236  	if info.IsDir() {
   237  		if s.config.IndexFolder {
   238  			s.listDir(r, f)
   239  		} else {
   240  			r.Response.WriteStatus(http.StatusForbidden)
   241  		}
   242  	} else {
   243  		// 读取文件内容返回, no buffer
   244  		http.ServeContent(r.Response.Writer, r.Request, info.Name(), info.ModTime(), f)
   245  	}
   246  }
   247  
   248  // 显示目录列表
   249  func (s *Server) listDir(r *Request, f http.File) {
   250  	files, err := f.Readdir(-1)
   251  	if err != nil {
   252  		r.Response.WriteStatus(http.StatusInternalServerError, "Error reading directory")
   253  		return
   254  	}
   255  	sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
   256  
   257  	r.Response.Header().Set("Content-Type", "text/html; charset=utf-8")
   258  	r.Response.Write("<pre>\n")
   259  	if r.URL.Path != "/" {
   260  		r.Response.Write(fmt.Sprint("<a href=\"..\">..</a>\n"))
   261  	}
   262  	for _, file := range files {
   263  		name := file.Name()
   264  		if file.IsDir() {
   265  			name += "/"
   266  		}
   267  		r.Response.Write(fmt.Sprintf("<a href=\"%s\">%s</a>\n", name, ghtml.SpecialChars(name)))
   268  	}
   269  	r.Response.Write("</pre>\n")
   270  }