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  }