github.com/gogf/gf/v2@v2.7.4/net/ghttp/ghttp_server.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 "bytes" 11 "context" 12 "fmt" 13 "net/http" 14 "os" 15 "runtime" 16 "strings" 17 "sync" 18 "time" 19 20 "github.com/olekukonko/tablewriter" 21 22 "github.com/gogf/gf/v2/container/garray" 23 "github.com/gogf/gf/v2/container/gset" 24 "github.com/gogf/gf/v2/container/gtype" 25 "github.com/gogf/gf/v2/debug/gdebug" 26 "github.com/gogf/gf/v2/errors/gcode" 27 "github.com/gogf/gf/v2/errors/gerror" 28 "github.com/gogf/gf/v2/internal/intlog" 29 "github.com/gogf/gf/v2/net/ghttp/internal/swaggerui" 30 "github.com/gogf/gf/v2/net/goai" 31 "github.com/gogf/gf/v2/net/gsvc" 32 "github.com/gogf/gf/v2/os/gcache" 33 "github.com/gogf/gf/v2/os/gctx" 34 "github.com/gogf/gf/v2/os/genv" 35 "github.com/gogf/gf/v2/os/gfile" 36 "github.com/gogf/gf/v2/os/glog" 37 "github.com/gogf/gf/v2/os/gproc" 38 "github.com/gogf/gf/v2/os/gsession" 39 "github.com/gogf/gf/v2/os/gtimer" 40 "github.com/gogf/gf/v2/text/gregex" 41 "github.com/gogf/gf/v2/text/gstr" 42 "github.com/gogf/gf/v2/util/gconv" 43 ) 44 45 func init() { 46 // Initialize the method map. 47 for _, v := range strings.Split(supportedHttpMethods, ",") { 48 methodsMap[v] = struct{}{} 49 } 50 } 51 52 // serverProcessInit initializes some process configurations, which can only be done once. 53 func serverProcessInit() { 54 var ctx = context.TODO() 55 if !serverProcessInitialized.Cas(false, true) { 56 return 57 } 58 // This means it is a restart server. It should kill its parent before starting its listening, 59 // to avoid duplicated port listening in two processes. 60 if !genv.Get(adminActionRestartEnvKey).IsEmpty() { 61 if p, err := os.FindProcess(gproc.PPid()); err == nil { 62 if err = p.Kill(); err != nil { 63 intlog.Errorf(ctx, `%+v`, err) 64 } 65 if _, err = p.Wait(); err != nil { 66 intlog.Errorf(ctx, `%+v`, err) 67 } 68 } else { 69 glog.Error(ctx, err) 70 } 71 } 72 73 // Process message handler. 74 // It enabled only a graceful feature is enabled. 75 if gracefulEnabled { 76 intlog.Printf(ctx, "pid[%d]: graceful reload feature is enabled", gproc.Pid()) 77 go handleProcessMessage() 78 } else { 79 intlog.Printf(ctx, "pid[%d]: graceful reload feature is disabled", gproc.Pid()) 80 } 81 82 // It's an ugly calling for better initializing the main package path 83 // in source development environment. It is useful only be used in main goroutine. 84 // It fails to retrieve the main package path in asynchronous goroutines. 85 gfile.MainPkgPath() 86 } 87 88 // GetServer creates and returns a server instance using given name and default configurations. 89 // Note that the parameter `name` should be unique for different servers. It returns an existing 90 // server instance if given `name` is already existing in the server mapping. 91 func GetServer(name ...interface{}) *Server { 92 serverName := DefaultServerName 93 if len(name) > 0 && name[0] != "" { 94 serverName = gconv.String(name[0]) 95 } 96 v := serverMapping.GetOrSetFuncLock(serverName, func() interface{} { 97 s := &Server{ 98 instance: serverName, 99 plugins: make([]Plugin, 0), 100 servers: make([]*gracefulServer, 0), 101 closeChan: make(chan struct{}, 10000), 102 serverCount: gtype.NewInt(), 103 statusHandlerMap: make(map[string][]HandlerFunc), 104 serveTree: make(map[string]interface{}), 105 serveCache: gcache.New(), 106 routesMap: make(map[string][]*HandlerItem), 107 openapi: goai.New(), 108 registrar: gsvc.GetRegistry(), 109 } 110 // Initialize the server using default configurations. 111 if err := s.SetConfig(NewConfig()); err != nil { 112 panic(gerror.WrapCode(gcode.CodeInvalidConfiguration, err, "")) 113 } 114 // It enables OpenTelemetry for server in default. 115 s.Use(internalMiddlewareServerTracing) 116 return s 117 }) 118 return v.(*Server) 119 } 120 121 // Start starts listening on configured port. 122 // This function does not block the process, you can use function Wait blocking the process. 123 func (s *Server) Start() error { 124 var ctx = gctx.GetInitCtx() 125 126 // Swagger UI. 127 if s.config.SwaggerPath != "" { 128 swaggerui.Init() 129 s.AddStaticPath(s.config.SwaggerPath, swaggerUIPackedPath) 130 s.BindHookHandler(s.config.SwaggerPath+"/*", HookBeforeServe, s.swaggerUI) 131 } 132 133 // OpenApi specification json producing handler. 134 if s.config.OpenApiPath != "" { 135 s.BindHandler(s.config.OpenApiPath, s.openapiSpec) 136 } 137 138 // Register group routes. 139 s.handlePreBindItems(ctx) 140 141 // Server process initialization, which can only be initialized once. 142 serverProcessInit() 143 144 // Server can only be run once. 145 if s.Status() == ServerStatusRunning { 146 return gerror.NewCode(gcode.CodeInvalidOperation, "server is already running") 147 } 148 149 // Logging path setting check. 150 if s.config.LogPath != "" && s.config.LogPath != s.config.Logger.GetPath() { 151 if err := s.config.Logger.SetPath(s.config.LogPath); err != nil { 152 return err 153 } 154 } 155 // Default session storage. 156 if s.config.SessionStorage == nil { 157 sessionStoragePath := "" 158 if s.config.SessionPath != "" { 159 sessionStoragePath = gfile.Join(s.config.SessionPath, s.config.Name) 160 if !gfile.Exists(sessionStoragePath) { 161 if err := gfile.Mkdir(sessionStoragePath); err != nil { 162 return gerror.Wrapf(err, `mkdir failed for "%s"`, sessionStoragePath) 163 } 164 } 165 } 166 s.config.SessionStorage = gsession.NewStorageFile(sessionStoragePath, s.config.SessionMaxAge) 167 } 168 // Initialize session manager when start running. 169 s.sessionManager = gsession.New( 170 s.config.SessionMaxAge, 171 s.config.SessionStorage, 172 ) 173 174 // PProf feature. 175 if s.config.PProfEnabled { 176 s.EnablePProf(s.config.PProfPattern) 177 } 178 179 // Default HTTP handler. 180 if s.config.Handler == nil { 181 s.config.Handler = s.ServeHTTP 182 } 183 184 // Install external plugins. 185 for _, p := range s.plugins { 186 if err := p.Install(s); err != nil { 187 s.Logger().Fatalf(ctx, `%+v`, err) 188 } 189 } 190 // Check the group routes again for internally registered routes. 191 s.handlePreBindItems(ctx) 192 193 // If there's no route registered and no static service enabled, 194 // it then returns an error of invalid usage of server. 195 if len(s.routesMap) == 0 && !s.config.FileServerEnabled { 196 return gerror.NewCode( 197 gcode.CodeInvalidOperation, 198 `there's no route set or static feature enabled, did you forget import the router?`, 199 ) 200 } 201 // ================================================================================================ 202 // Start the HTTP server. 203 // ================================================================================================ 204 reloaded := false 205 fdMapStr := genv.Get(adminActionReloadEnvKey).String() 206 if len(fdMapStr) > 0 { 207 sfm := bufferToServerFdMap([]byte(fdMapStr)) 208 if v, ok := sfm[s.config.Name]; ok { 209 s.startServer(v) 210 reloaded = true 211 } 212 } 213 if !reloaded { 214 s.startServer(nil) 215 } 216 217 // Swagger UI info. 218 if s.config.SwaggerPath != "" { 219 s.Logger().Infof( 220 ctx, 221 `swagger ui is serving at address: %s%s/`, 222 s.getLocalListenedAddress(), 223 s.config.SwaggerPath, 224 ) 225 } 226 // OpenApi specification info. 227 if s.config.OpenApiPath != "" { 228 s.Logger().Infof( 229 ctx, 230 `openapi specification is serving at address: %s%s`, 231 s.getLocalListenedAddress(), 232 s.config.OpenApiPath, 233 ) 234 } else { 235 if s.config.SwaggerPath != "" { 236 s.Logger().Warning( 237 ctx, 238 `openapi specification is disabled but swagger ui is serving, which might make no sense`, 239 ) 240 } else { 241 s.Logger().Info( 242 ctx, 243 `openapi specification is disabled`, 244 ) 245 } 246 } 247 248 // If this is a child process, it then notifies its parent exit. 249 if gproc.IsChild() { 250 var gracefulTimeout = time.Duration(s.config.GracefulTimeout) * time.Second 251 gtimer.SetTimeout(ctx, gracefulTimeout, func(ctx context.Context) { 252 intlog.Printf( 253 ctx, 254 `pid[%d]: notice parent server graceful shuttingdown, ppid: %d`, 255 gproc.Pid(), gproc.PPid(), 256 ) 257 if err := gproc.Send(gproc.PPid(), []byte("exit"), adminGProcCommGroup); err != nil { 258 intlog.Errorf(ctx, `server error in process communication: %+v`, err) 259 } 260 }) 261 } 262 s.initOpenApi() 263 s.doServiceRegister() 264 s.doRouterMapDump() 265 266 return nil 267 } 268 269 func (s *Server) getLocalListenedAddress() string { 270 return fmt.Sprintf(`http://127.0.0.1:%d`, s.GetListenedPort()) 271 } 272 273 // doRouterMapDump checks and dumps the router map to the log. 274 func (s *Server) doRouterMapDump() { 275 if !s.config.DumpRouterMap { 276 return 277 } 278 279 var ( 280 ctx = context.TODO() 281 routes = s.GetRoutes() 282 isJustDefaultServerAndDomain = true 283 headers = []string{ 284 "SERVER", "DOMAIN", "ADDRESS", "METHOD", "ROUTE", "HANDLER", "MIDDLEWARE", 285 } 286 ) 287 for _, item := range routes { 288 if item.Server != DefaultServerName || item.Domain != DefaultDomainName { 289 isJustDefaultServerAndDomain = false 290 break 291 } 292 } 293 if isJustDefaultServerAndDomain { 294 headers = []string{"ADDRESS", "METHOD", "ROUTE", "HANDLER", "MIDDLEWARE"} 295 } 296 if len(routes) > 0 { 297 buffer := bytes.NewBuffer(nil) 298 table := tablewriter.NewWriter(buffer) 299 table.SetHeader(headers) 300 table.SetRowLine(true) 301 table.SetBorder(false) 302 table.SetCenterSeparator("|") 303 304 for _, item := range routes { 305 var ( 306 data = make([]string, 0) 307 handlerName = gstr.TrimRightStr(item.Handler.Name, "-fm") 308 middlewares = gstr.SplitAndTrim(item.Middleware, ",") 309 ) 310 311 // No printing special internal middleware that may lead confused. 312 if gstr.SubStrFromREx(handlerName, ".") == noPrintInternalRoute { 313 continue 314 } 315 for k, v := range middlewares { 316 middlewares[k] = gstr.TrimRightStr(v, "-fm") 317 } 318 item.Middleware = gstr.Join(middlewares, "\n") 319 if isJustDefaultServerAndDomain { 320 data = append( 321 data, 322 item.Address, 323 item.Method, 324 item.Route, 325 handlerName, 326 item.Middleware, 327 ) 328 } else { 329 data = append( 330 data, 331 item.Server, 332 item.Domain, 333 item.Address, 334 item.Method, 335 item.Route, 336 handlerName, 337 item.Middleware, 338 ) 339 } 340 table.Append(data) 341 } 342 table.Render() 343 s.config.Logger.Header(false).Printf(ctx, "\n%s", buffer.String()) 344 } 345 } 346 347 // GetOpenApi returns the OpenApi specification management object of current server. 348 func (s *Server) GetOpenApi() *goai.OpenApiV3 { 349 return s.openapi 350 } 351 352 // GetRoutes retrieves and returns the router array. 353 func (s *Server) GetRoutes() []RouterItem { 354 var ( 355 m = make(map[string]*garray.SortedArray) 356 routeFilterSet = gset.NewStrSet() 357 address = s.GetListenedAddress() 358 ) 359 if s.config.HTTPSAddr != "" { 360 if len(address) > 0 { 361 address += "," 362 } 363 address += "tls" + s.config.HTTPSAddr 364 } 365 for k, handlerItems := range s.routesMap { 366 array, _ := gregex.MatchString(`(.*?)%([A-Z]+):(.+)@(.+)`, k) 367 for index := len(handlerItems) - 1; index >= 0; index-- { 368 var ( 369 handlerItem = handlerItems[index] 370 item = RouterItem{ 371 Server: s.config.Name, 372 Address: address, 373 Domain: array[4], 374 Type: handlerItem.Type, 375 Middleware: array[1], 376 Method: array[2], 377 Route: array[3], 378 Priority: index, 379 Handler: handlerItem, 380 } 381 ) 382 switch item.Handler.Type { 383 case HandlerTypeObject, HandlerTypeHandler: 384 item.IsServiceHandler = true 385 386 case HandlerTypeMiddleware: 387 item.Middleware = "GLOBAL MIDDLEWARE" 388 } 389 // Repeated route filtering for dump. 390 var setKey = fmt.Sprintf( 391 `%s|%s|%s|%s`, 392 item.Method, item.Route, item.Domain, item.Type, 393 ) 394 if !routeFilterSet.AddIfNotExist(setKey) { 395 continue 396 } 397 if len(item.Handler.Middleware) > 0 { 398 for _, v := range item.Handler.Middleware { 399 if item.Middleware != "" { 400 item.Middleware += "," 401 } 402 item.Middleware += gdebug.FuncName(v) 403 } 404 } 405 // If the domain does not exist in the dump map, it creates the map. 406 // The value of the map is a custom sorted array. 407 if _, ok := m[item.Domain]; !ok { 408 // Sort in ASC order. 409 m[item.Domain] = garray.NewSortedArray(func(v1, v2 interface{}) int { 410 item1 := v1.(RouterItem) 411 item2 := v2.(RouterItem) 412 r := 0 413 if r = strings.Compare(item1.Domain, item2.Domain); r == 0 { 414 if r = strings.Compare(item1.Route, item2.Route); r == 0 { 415 if r = strings.Compare(item1.Method, item2.Method); r == 0 { 416 if item1.Handler.Type == HandlerTypeMiddleware && item2.Handler.Type != HandlerTypeMiddleware { 417 return -1 418 } else if item1.Handler.Type == HandlerTypeMiddleware && item2.Handler.Type == HandlerTypeMiddleware { 419 return 1 420 } else if r = strings.Compare(item1.Middleware, item2.Middleware); r == 0 { 421 r = item2.Priority - item1.Priority 422 } 423 } 424 } 425 } 426 return r 427 }) 428 } 429 m[item.Domain].Add(item) 430 } 431 } 432 433 routerArray := make([]RouterItem, 0, 128) 434 for _, array := range m { 435 for _, v := range array.Slice() { 436 routerArray = append(routerArray, v.(RouterItem)) 437 } 438 } 439 return routerArray 440 } 441 442 // Run starts server listening in blocking way. 443 // It's commonly used for single server situation. 444 func (s *Server) Run() { 445 var ctx = context.TODO() 446 447 if err := s.Start(); err != nil { 448 s.Logger().Fatalf(ctx, `%+v`, err) 449 } 450 451 // Signal handler in asynchronous way. 452 go handleProcessSignal() 453 454 // Blocking using channel for graceful restart. 455 <-s.closeChan 456 // Remove plugins. 457 if len(s.plugins) > 0 { 458 for _, p := range s.plugins { 459 intlog.Printf(ctx, `remove plugin: %s`, p.Name()) 460 if err := p.Remove(); err != nil { 461 intlog.Errorf(ctx, "%+v", err) 462 } 463 } 464 } 465 s.doServiceDeregister() 466 s.Logger().Infof(ctx, "pid[%d]: all servers shutdown", gproc.Pid()) 467 } 468 469 // Wait blocks to wait for all servers done. 470 // It's commonly used in multiple server situation. 471 func Wait() { 472 var ctx = context.TODO() 473 474 // Signal handler in asynchronous way. 475 go handleProcessSignal() 476 477 <-allShutdownChan 478 479 // Remove plugins. 480 serverMapping.Iterator(func(k string, v interface{}) bool { 481 s := v.(*Server) 482 if len(s.plugins) > 0 { 483 for _, p := range s.plugins { 484 intlog.Printf(ctx, `remove plugin: %s`, p.Name()) 485 if err := p.Remove(); err != nil { 486 intlog.Errorf(ctx, `%+v`, err) 487 } 488 } 489 } 490 return true 491 }) 492 glog.Infof(ctx, "pid[%d]: all servers shutdown", gproc.Pid()) 493 } 494 495 // startServer starts the underlying server listening. 496 func (s *Server) startServer(fdMap listenerFdMap) { 497 var ( 498 ctx = context.TODO() 499 httpsEnabled bool 500 ) 501 // HTTPS 502 if s.config.TLSConfig != nil || (s.config.HTTPSCertPath != "" && s.config.HTTPSKeyPath != "") { 503 if len(s.config.HTTPSAddr) == 0 { 504 if len(s.config.Address) > 0 { 505 s.config.HTTPSAddr = s.config.Address 506 s.config.Address = "" 507 } else { 508 s.config.HTTPSAddr = defaultHttpsAddr 509 } 510 } 511 httpsEnabled = len(s.config.HTTPSAddr) > 0 512 var array []string 513 if v, ok := fdMap["https"]; ok && len(v) > 0 { 514 array = strings.Split(v, ",") 515 } else { 516 array = strings.Split(s.config.HTTPSAddr, ",") 517 } 518 for _, v := range array { 519 if len(v) == 0 { 520 continue 521 } 522 var ( 523 fd = 0 524 itemFunc = v 525 addrAndFd = strings.Split(v, "#") 526 ) 527 if len(addrAndFd) > 1 { 528 itemFunc = addrAndFd[0] 529 // The Windows OS does not support socket file descriptor passing 530 // from parent process. 531 if runtime.GOOS != "windows" { 532 fd = gconv.Int(addrAndFd[1]) 533 } 534 } 535 if fd > 0 { 536 s.servers = append(s.servers, s.newGracefulServer(itemFunc, fd)) 537 } else { 538 s.servers = append(s.servers, s.newGracefulServer(itemFunc)) 539 } 540 s.servers[len(s.servers)-1].isHttps = true 541 } 542 } 543 // HTTP 544 if !httpsEnabled && len(s.config.Address) == 0 { 545 s.config.Address = defaultHttpAddr 546 } 547 var array []string 548 if v, ok := fdMap["http"]; ok && len(v) > 0 { 549 array = gstr.SplitAndTrim(v, ",") 550 } else { 551 array = gstr.SplitAndTrim(s.config.Address, ",") 552 } 553 for _, v := range array { 554 if len(v) == 0 { 555 continue 556 } 557 var ( 558 fd = 0 559 itemFunc = v 560 addrAndFd = strings.Split(v, "#") 561 ) 562 if len(addrAndFd) > 1 { 563 itemFunc = addrAndFd[0] 564 // The Window OS does not support socket file descriptor passing 565 // from the parent process. 566 if runtime.GOOS != "windows" { 567 fd = gconv.Int(addrAndFd[1]) 568 } 569 } 570 if fd > 0 { 571 s.servers = append(s.servers, s.newGracefulServer(itemFunc, fd)) 572 } else { 573 s.servers = append(s.servers, s.newGracefulServer(itemFunc)) 574 } 575 } 576 // Start listening asynchronously. 577 serverRunning.Add(1) 578 var wg = &sync.WaitGroup{} 579 for _, gs := range s.servers { 580 wg.Add(1) 581 go s.startGracefulServer(ctx, wg, gs) 582 } 583 wg.Wait() 584 } 585 586 func (s *Server) startGracefulServer(ctx context.Context, wg *sync.WaitGroup, server *gracefulServer) { 587 s.serverCount.Add(1) 588 var err error 589 // Create listener. 590 if server.isHttps { 591 err = server.CreateListenerTLS( 592 s.config.HTTPSCertPath, s.config.HTTPSKeyPath, s.config.TLSConfig, 593 ) 594 } else { 595 err = server.CreateListener() 596 } 597 if err != nil { 598 s.Logger().Fatalf(ctx, `%+v`, err) 599 } 600 wg.Done() 601 // Start listening and serving in blocking way. 602 err = server.Serve(ctx) 603 // The process exits if the server is closed with none closing error. 604 if err != nil && !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { 605 s.Logger().Fatalf(ctx, `%+v`, err) 606 } 607 // If all the underlying servers' shutdown, the process exits. 608 if s.serverCount.Add(-1) < 1 { 609 s.closeChan <- struct{}{} 610 if serverRunning.Add(-1) < 1 { 611 serverMapping.Remove(s.instance) 612 allShutdownChan <- struct{}{} 613 } 614 } 615 } 616 617 // Status retrieves and returns the server status. 618 func (s *Server) Status() ServerStatus { 619 if serverRunning.Val() == 0 { 620 return ServerStatusStopped 621 } 622 // If any underlying server is running, the server status is running. 623 for _, v := range s.servers { 624 if v.status.Val() == ServerStatusRunning { 625 return ServerStatusRunning 626 } 627 } 628 return ServerStatusStopped 629 } 630 631 // getListenerFdMap retrieves and returns the socket file descriptors. 632 // The key of the returned map is "http" and "https". 633 func (s *Server) getListenerFdMap() map[string]string { 634 m := map[string]string{ 635 "https": "", 636 "http": "", 637 } 638 for _, v := range s.servers { 639 str := v.address + "#" + gconv.String(v.Fd()) + "," 640 if v.isHttps { 641 if len(m["https"]) > 0 { 642 m["https"] += "," 643 } 644 m["https"] += str 645 } else { 646 if len(m["http"]) > 0 { 647 m["http"] += "," 648 } 649 m["http"] += str 650 } 651 } 652 return m 653 } 654 655 // GetListenedPort retrieves and returns one port which is listened by current server. 656 func (s *Server) GetListenedPort() int { 657 ports := s.GetListenedPorts() 658 if len(ports) > 0 { 659 return ports[0] 660 } 661 return 0 662 } 663 664 // GetListenedPorts retrieves and returns the ports which are listened by current server. 665 func (s *Server) GetListenedPorts() []int { 666 ports := make([]int, 0) 667 for _, server := range s.servers { 668 ports = append(ports, server.GetListenedPort()) 669 } 670 return ports 671 } 672 673 // GetListenedAddress retrieves and returns the address string which are listened by current server. 674 func (s *Server) GetListenedAddress() string { 675 if !gstr.Contains(s.config.Address, FreePortAddress) { 676 return s.config.Address 677 } 678 var ( 679 address = s.config.Address 680 listenedPorts = s.GetListenedPorts() 681 ) 682 for _, listenedPort := range listenedPorts { 683 address = gstr.Replace(address, FreePortAddress, fmt.Sprintf(`:%d`, listenedPort), 1) 684 } 685 return address 686 }