github.com/zhongdalu/gf@v1.0.0/g/net/ghttp/ghttp_server.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 "bytes" 11 "errors" 12 "fmt" 13 "net/http" 14 "os" 15 "reflect" 16 "runtime" 17 "strings" 18 "sync" 19 "time" 20 21 "github.com/zhongdalu/gf/g/container/garray" 22 "github.com/zhongdalu/gf/g/container/gmap" 23 "github.com/zhongdalu/gf/g/container/gtype" 24 "github.com/zhongdalu/gf/g/os/gcache" 25 "github.com/zhongdalu/gf/g/os/genv" 26 "github.com/zhongdalu/gf/g/os/gfile" 27 "github.com/zhongdalu/gf/g/os/glog" 28 "github.com/zhongdalu/gf/g/os/gproc" 29 "github.com/zhongdalu/gf/g/os/gtimer" 30 "github.com/zhongdalu/gf/g/text/gregex" 31 "github.com/zhongdalu/gf/g/util/gconv" 32 "github.com/zhongdalu/gf/third/github.com/gorilla/websocket" 33 "github.com/zhongdalu/gf/third/github.com/olekukonko/tablewriter" 34 ) 35 36 type ( 37 // Server结构体 38 Server struct { 39 name string // 服务名称 40 config ServerConfig // 配置对象 41 servers []*gracefulServer // 底层http.Server列表 42 serverCount *gtype.Int // 底层http.Server数量 43 closeChan chan struct{} // 用以关闭事件通知的通道 44 servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况),同时作为请求ID 45 serveTree map[string]interface{} // 所有注册的服务回调函数(路由表,树型结构,哈希表+链表优先级匹配) 46 hooksTree map[string]interface{} // 所有注册的事件回调函数(路由表,树型结构,哈希表+链表优先级匹配) 47 serveCache *gcache.Cache // 服务注册路由内存缓存 48 hooksCache *gcache.Cache // 事件回调路由内存缓存 49 routesMap map[string][]registeredRouteItem // 已经注册的路由及对应的注册方法文件地址(用以路由重复注册判断) 50 statusHandlerMu sync.RWMutex // status handler互斥锁 51 statusHandlerMap map[string]HandlerFunc // 不同状态码下的注册处理方法(例如404状态时的处理方法) 52 sessions *gcache.Cache // Session内存缓存 53 logger *glog.Logger // 日志管理对象 54 } 55 56 // 路由对象 57 Router struct { 58 Uri string // 注册时的pattern - uri 59 Method string // 注册时的pattern - method 60 Domain string // 注册时的pattern - domain 61 RegRule string // 路由规则解析后对应的正则表达式 62 RegNames []string // 路由规则解析后对应的变量名称数组 63 Priority int // 优先级,用于链表排序,值越大优先级越高 64 } 65 66 // http回调函数注册信息 67 handlerItem struct { 68 name string // 注册的方法名称信息 69 rtype int // 注册方式(执行对象/回调函数/控制器) 70 ctype reflect.Type // 控制器类型(反射类型) 71 fname string // 回调方法名称 72 faddr HandlerFunc // 准确的执行方法内存地址(与以上两个参数二选一) 73 finit HandlerFunc // 初始化请求回调方法(执行对象注册方式下有效) 74 fshut HandlerFunc // 完成请求回调方法(执行对象注册方式下有效) 75 router *Router // 注册时绑定的路由对象 76 } 77 78 // 根据特定URL.Path解析后的路由检索结果项 79 handlerParsedItem struct { 80 handler *handlerItem // 路由注册项 81 values map[string][]string // 特定URL.Path的Router解析参数 82 } 83 84 // 已注册的路由项 85 registeredRouteItem struct { 86 file string // 文件路径及行数地址 87 handler *handlerItem // 路由注册项 88 } 89 90 // pattern与回调函数的绑定map 91 handlerMap = map[string]*handlerItem 92 93 // HTTP注册函数 94 HandlerFunc = func(r *Request) 95 96 // 文件描述符map 97 listenerFdMap = map[string]string 98 ) 99 100 const ( 101 SERVER_STATUS_STOPPED = 0 // Server状态:停止 102 SERVER_STATUS_RUNNING = 1 // Server状态:运行 103 HOOK_BEFORE_SERVE = "BeforeServe" // 回调事件,在执行服务前 104 HOOK_AFTER_SERVE = "AfterServe" // 回调事件,在执行服务后 105 HOOK_BEFORE_OUTPUT = "BeforeOutput" // 回调事件,在输出结果前 106 HOOK_AFTER_OUTPUT = "AfterOutput" // 回调事件,在输出结果后 107 HOOK_BEFORE_CLOSE = "BeforeClose" // Deprecated. 108 HOOK_AFTER_CLOSE = "AfterClose" // Deprecated. 109 110 HTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE" 111 gDEFAULT_SERVER = "default" 112 gDEFAULT_DOMAIN = "default" 113 gDEFAULT_METHOD = "ALL" 114 gROUTE_REGISTER_HANDLER = 1 115 gROUTE_REGISTER_OBJECT = 2 116 gROUTE_REGISTER_CONTROLLER = 3 117 gEXCEPTION_EXIT = "exit" 118 gEXCEPTION_EXIT_ALL = "exit_all" 119 gEXCEPTION_EXIT_HOOK = "exit_hook" 120 ) 121 122 var ( 123 // 所有支持的HTTP Method Map(初始化时自动填充), 124 // 用于快速检索需要 125 methodsMap = make(map[string]struct{}) 126 127 // WebServer表,用以存储和检索名称与Server对象之间的关联关系 128 serverMapping = gmap.NewStrAnyMap() 129 130 // 正常运行的WebServer数量,如果没有运行、失败或者全部退出,那么该值为0 131 serverRunning = gtype.NewInt() 132 133 // WebSocket默认配置 134 wsUpgrader = websocket.Upgrader{ 135 // 默认允许WebSocket请求跨域,权限控制可以由业务层自己负责,灵活度更高 136 CheckOrigin: func(r *http.Request) bool { 137 return true 138 }, 139 } 140 // WebServer已完成服务事件通道,当有事件时表示服务完成,当前进程退出 141 allDoneChan = make(chan struct{}, 1000) 142 143 // 用于服务进程初始化,只能初始化一次,采用“懒初始化”(在server运行时才初始化) 144 serverProcessInited = gtype.NewBool() 145 146 // 是否开启WebServer平滑重启特性, 会开启额外的本地端口监听,用于进程管理通信(默认开启) 147 gracefulEnabled = true 148 ) 149 150 func init() { 151 for _, v := range strings.Split(HTTP_METHODS, ",") { 152 methodsMap[v] = struct{}{} 153 } 154 } 155 156 // 是否开启平滑重启特性 157 func SetGraceful(enabled bool) { 158 gracefulEnabled = enabled 159 } 160 161 // Web Server进程初始化. 162 // 注意该方法不能放置于包初始化方法init中,不使用ghttp.Server的功能便不能初始化对应的协程goroutine逻辑. 163 func serverProcessInit() { 164 if serverProcessInited.Val() { 165 return 166 } 167 serverProcessInited.Set(true) 168 // 如果是完整重启,那么需要等待主进程销毁后,才开始执行监听,防止端口冲突 169 if genv.Get(gADMIN_ACTION_RESTART_ENVKEY) != "" { 170 if p, e := os.FindProcess(gproc.PPid()); e == nil { 171 p.Kill() 172 p.Wait() 173 } else { 174 glog.Error(e) 175 } 176 } 177 178 // 信号量管理操作监听 179 go handleProcessSignal() 180 // 异步监听进程间消息 181 if gracefulEnabled { 182 go handleProcessMessage() 183 } 184 185 // 是否处于开发环境,这里调用该方法初始化main包路径值, 186 // 防止异步服务goroutine获取main包路径失败, 187 // 该方法只有在main协程中才会执行。 188 gfile.MainPkgPath() 189 } 190 191 // 获取/创建一个默认配置的HTTP Server(默认监听端口是80) 192 // 单例模式,请保证name的唯一性 193 func GetServer(name ...interface{}) *Server { 194 sname := gDEFAULT_SERVER 195 if len(name) > 0 { 196 sname = gconv.String(name[0]) 197 } 198 if s := serverMapping.Get(sname); s != nil { 199 return s.(*Server) 200 } 201 s := &Server{ 202 name: sname, 203 servers: make([]*gracefulServer, 0), 204 closeChan: make(chan struct{}, 100), 205 serverCount: gtype.NewInt(), 206 statusHandlerMap: make(map[string]HandlerFunc), 207 serveTree: make(map[string]interface{}), 208 hooksTree: make(map[string]interface{}), 209 serveCache: gcache.New(), 210 hooksCache: gcache.New(), 211 routesMap: make(map[string][]registeredRouteItem), 212 sessions: gcache.New(), 213 servedCount: gtype.NewInt(), 214 logger: glog.New(), 215 } 216 // 初始化时使用默认配置 217 s.SetConfig(defaultServerConfig) 218 // 记录到全局ServerMap中 219 serverMapping.Set(sname, s) 220 return s 221 } 222 223 // 作为守护协程异步执行(当同一进程中存在多个Web Server时,需要采用这种方式执行), 224 // 需要结合Wait方式一起使用. 225 func (s *Server) Start() error { 226 // 服务进程初始化,只会初始化一次 227 serverProcessInit() 228 229 // 当前Web Server状态判断 230 if s.Status() == SERVER_STATUS_RUNNING { 231 return errors.New("server is already running") 232 } 233 234 // 没有注册任何路由,且没有开启文件服务,那么提示错误 235 if len(s.routesMap) == 0 && !s.config.FileServerEnabled { 236 glog.Fatal("[ghttp] no router set or static feature enabled, did you forget import the router?") 237 } 238 239 // 底层http server配置 240 if s.config.Handler == nil { 241 s.config.Handler = http.HandlerFunc(s.defaultHttpHandle) 242 } 243 // 不允许访问的路由注册(使用HOOK实现) 244 // TODO 去掉HOOK的实现方式 245 if s.config.DenyRoutes != nil { 246 for _, v := range s.config.DenyRoutes { 247 s.BindHookHandler(v, HOOK_BEFORE_SERVE, func(r *Request) { 248 r.Response.WriteStatus(403) 249 r.ExitAll() 250 }) 251 } 252 } 253 254 // gzip压缩文件类型 255 //if s.config.GzipContentTypes != nil { 256 // for _, v := range s.config.GzipContentTypes { 257 // s.gzipMimesMap[v] = struct{}{} 258 // } 259 //} 260 261 // 启动http server 262 reloaded := false 263 fdMapStr := genv.Get(gADMIN_ACTION_RELOAD_ENVKEY) 264 if len(fdMapStr) > 0 { 265 sfm := bufferToServerFdMap([]byte(fdMapStr)) 266 if v, ok := sfm[s.name]; ok { 267 s.startServer(v) 268 reloaded = true 269 } 270 } 271 if !reloaded { 272 s.startServer(nil) 273 } 274 275 // 如果是子进程,那么服务开启后通知父进程销毁 276 if gproc.IsChild() { 277 gtimer.SetTimeout(2*time.Second, func() { 278 if err := gproc.Send(gproc.PPid(), []byte("exit"), gADMIN_GPROC_COMM_GROUP); err != nil { 279 glog.Error("[ghttp] server error in process communication:", err) 280 } 281 }) 282 } 283 284 // 打印展示路由表 285 s.DumpRoutesMap() 286 return nil 287 } 288 289 // 打印展示路由表 290 func (s *Server) DumpRoutesMap() { 291 if s.config.DumpRouteMap && len(s.routesMap) > 0 { 292 // (等待一定时间后)当所有框架初始化信息打印完毕之后才打印路由表信息 293 gtimer.SetTimeout(50*time.Millisecond, func() { 294 glog.Header(false).Println(fmt.Sprintf("\n%s", s.GetRouteMap())) 295 }) 296 } 297 } 298 299 // 获得路由表(格式化字符串) 300 func (s *Server) GetRouteMap() string { 301 type tableItem struct { 302 hook string 303 domain string 304 method string 305 route string 306 handler string 307 priority int 308 } 309 310 buf := bytes.NewBuffer(nil) 311 table := tablewriter.NewWriter(buf) 312 table.SetHeader([]string{"SERVER", "ADDRESS", "DOMAIN", "METHOD", "P", "ROUTE", "HANDLER", "HOOK"}) 313 table.SetRowLine(true) 314 table.SetBorder(false) 315 table.SetCenterSeparator("|") 316 317 m := make(map[string]*garray.SortedArray) 318 for k, registeredItems := range s.routesMap { 319 array, _ := gregex.MatchString(`(.*?)%([A-Z]+):(.+)@(.+)`, k) 320 for index, registeredItem := range registeredItems { 321 item := &tableItem{ 322 hook: array[1], 323 domain: array[4], 324 method: array[2], 325 route: array[3], 326 handler: registeredItem.handler.name, 327 priority: len(registeredItems) - index - 1, 328 } 329 if _, ok := m[item.domain]; !ok { 330 // 注意排序函数的逻辑 331 m[item.domain] = garray.NewSortedArraySize(100, func(v1, v2 interface{}) int { 332 item1 := v1.(*tableItem) 333 item2 := v2.(*tableItem) 334 r := 0 335 if r = strings.Compare(item1.domain, item2.domain); r == 0 { 336 if r = strings.Compare(item1.route, item2.route); r == 0 { 337 if r = strings.Compare(item1.method, item2.method); r == 0 { 338 if r = strings.Compare(item1.hook, item2.hook); r == 0 { 339 r = item2.priority - item1.priority 340 } 341 } 342 } 343 } 344 return r 345 }, false) 346 } 347 m[item.domain].Add(item) 348 } 349 } 350 addr := s.config.Addr 351 if s.config.HTTPSAddr != "" { 352 if len(addr) > 0 { 353 addr += "," 354 } 355 addr += "tls" + s.config.HTTPSAddr 356 } 357 for _, a := range m { 358 data := make([]string, 8) 359 for _, v := range a.Slice() { 360 item := v.(*tableItem) 361 data[0] = s.name 362 data[1] = addr 363 data[2] = item.domain 364 data[3] = item.method 365 data[4] = gconv.String(len(strings.Split(item.route, "/")) - 1 + item.priority) 366 data[5] = item.route 367 data[6] = item.handler 368 data[7] = item.hook 369 table.Append(data) 370 } 371 } 372 table.Render() 373 374 return buf.String() 375 } 376 377 // 阻塞执行监听 378 func (s *Server) Run() { 379 if err := s.Start(); err != nil { 380 glog.Fatal(err) 381 } 382 // 阻塞等待服务执行完成 383 <-s.closeChan 384 385 glog.Printf("%d: all servers shutdown", gproc.Pid()) 386 } 387 388 // 阻塞等待所有Web Server停止,常用于多Web Server场景,以及需要将Web Server异步运行的场景 389 // 这是一个与进程相关的方法 390 func Wait() { 391 // 阻塞等待服务执行完成 392 <-allDoneChan 393 394 glog.Printf("%d: all servers shutdown", gproc.Pid()) 395 } 396 397 // 开启底层Web Server执行 398 func (s *Server) startServer(fdMap listenerFdMap) { 399 var httpsEnabled bool 400 // 判断是否启用HTTPS 401 if len(s.config.TLSConfig.Certificates) > 0 || (len(s.config.HTTPSCertPath) > 0 && len(s.config.HTTPSKeyPath) > 0) { 402 // ================ 403 // HTTPS 404 // ================ 405 if len(s.config.HTTPSAddr) == 0 { 406 if len(s.config.Addr) > 0 { 407 s.config.HTTPSAddr = s.config.Addr 408 s.config.Addr = "" 409 } else { 410 s.config.HTTPSAddr = gDEFAULT_HTTPS_ADDR 411 } 412 } 413 httpsEnabled = len(s.config.HTTPSAddr) > 0 414 var array []string 415 if v, ok := fdMap["https"]; ok && len(v) > 0 { 416 array = strings.Split(v, ",") 417 } else { 418 array = strings.Split(s.config.HTTPSAddr, ",") 419 } 420 for _, v := range array { 421 if len(v) == 0 { 422 continue 423 } 424 fd := 0 425 addr := v 426 array := strings.Split(v, "#") 427 if len(array) > 1 { 428 addr = array[0] 429 // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 430 if runtime.GOOS != "windows" { 431 fd = gconv.Int(array[1]) 432 } 433 } 434 if fd > 0 { 435 s.servers = append(s.servers, s.newGracefulServer(addr, fd)) 436 } else { 437 s.servers = append(s.servers, s.newGracefulServer(addr)) 438 } 439 s.servers[len(s.servers)-1].isHttps = true 440 } 441 } 442 // ================ 443 // HTTP 444 // ================ 445 // 当HTTPS服务未启用时,默认HTTP地址才会生效 446 if !httpsEnabled && len(s.config.Addr) == 0 { 447 s.config.Addr = gDEFAULT_HTTP_ADDR 448 } 449 var array []string 450 if v, ok := fdMap["http"]; ok && len(v) > 0 { 451 array = strings.Split(v, ",") 452 } else { 453 array = strings.Split(s.config.Addr, ",") 454 } 455 for _, v := range array { 456 if len(v) == 0 { 457 continue 458 } 459 fd := 0 460 addr := v 461 array := strings.Split(v, "#") 462 if len(array) > 1 { 463 addr = array[0] 464 // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 465 if runtime.GOOS != "windows" { 466 fd = gconv.Int(array[1]) 467 } 468 } 469 if fd > 0 { 470 s.servers = append(s.servers, s.newGracefulServer(addr, fd)) 471 } else { 472 s.servers = append(s.servers, s.newGracefulServer(addr)) 473 } 474 } 475 // 开始执行异步监听 476 serverRunning.Add(1) 477 for _, v := range s.servers { 478 go func(server *gracefulServer) { 479 s.serverCount.Add(1) 480 err := (error)(nil) 481 if server.isHttps { 482 err = server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath, &s.config.TLSConfig) 483 } else { 484 err = server.ListenAndServe() 485 } 486 // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 487 if err != nil && !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { 488 glog.Fatal(err) 489 } 490 // 如果所有异步的http.Server都已经停止,那么WebServer就可以退出了 491 if s.serverCount.Add(-1) < 1 { 492 s.closeChan <- struct{}{} 493 // 如果所有WebServer都退出,那么退出Wait等待 494 if serverRunning.Add(-1) < 1 { 495 serverMapping.Remove(s.name) 496 allDoneChan <- struct{}{} 497 } 498 } 499 }(v) 500 } 501 } 502 503 // 获取当前服务器的状态 504 func (s *Server) Status() int { 505 // 当全局运行的Web Server数量为0时表示所有Server都是停止状态 506 if serverRunning.Val() == 0 { 507 return SERVER_STATUS_STOPPED 508 } 509 // 只要有一个Server处于运行状态,那么都表示运行状态 510 for _, v := range s.servers { 511 if v.status == SERVER_STATUS_RUNNING { 512 return SERVER_STATUS_RUNNING 513 } 514 } 515 return SERVER_STATUS_STOPPED 516 } 517 518 // 获取当前监听的文件描述符信息,构造成map返回 519 func (s *Server) getListenerFdMap() map[string]string { 520 m := map[string]string{ 521 "https": "", 522 "http": "", 523 } 524 // s.servers是从HTTPS到HTTP优先级遍历,解析的时候也应当按照这个顺序读取fd 525 for _, v := range s.servers { 526 str := v.addr + "#" + gconv.String(v.Fd()) + "," 527 if v.isHttps { 528 m["https"] += str 529 } else { 530 m["http"] += str 531 } 532 } 533 // 去掉末尾的","号 534 if len(m["https"]) > 0 { 535 m["https"] = m["https"][0 : len(m["https"])-1] 536 } 537 if len(m["http"]) > 0 { 538 m["http"] = m["http"][0 : len(m["http"])-1] 539 } 540 541 return m 542 }