gf/net/ghttp/ghttp_server.go

566 lines
15 KiB
Go
Raw Normal View History

// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
2017-12-29 16:03:30 +08:00
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
2017-12-29 16:03:30 +08:00
2017-11-23 10:21:28 +08:00
package ghttp
import (
2019-06-19 09:06:52 +08:00
"bytes"
"context"
"fmt"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/net/ghttp/internal/swaggerui"
"github.com/gogf/gf/v2/protocol/goai"
"github.com/gogf/gf/v2/text/gstr"
"net/http"
"os"
"runtime"
"strings"
"time"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/os/gsession"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/os/gtimer"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/util/gconv"
"github.com/olekukonko/tablewriter"
2017-11-23 10:21:28 +08:00
)
func init() {
// Initialize the methods map.
for _, v := range strings.Split(supportedHttpMethods, ",") {
2019-06-19 09:06:52 +08:00
methodsMap[v] = struct{}{}
}
}
// serverProcessInit initializes some process configurations, which can only be done once.
2019-02-20 16:07:11 +08:00
func serverProcessInit() {
2021-09-27 21:27:24 +08:00
var (
ctx = context.TODO()
)
if !serverProcessInitialized.Cas(false, true) {
2019-06-19 09:06:52 +08:00
return
}
// This means it is a restart server, it should kill its parent before starting its listening,
// to avoid duplicated port listening in two processes.
2020-12-15 20:16:17 +08:00
if genv.Get(adminActionRestartEnvKey) != "" {
if p, err := os.FindProcess(gproc.PPid()); err == nil {
if err = p.Kill(); err != nil {
intlog.Error(ctx, err)
}
if _, err = p.Wait(); err != nil {
intlog.Error(ctx, err)
}
2019-06-19 09:06:52 +08:00
} else {
glog.Error(ctx, err)
2019-06-19 09:06:52 +08:00
}
}
// Signal handler.
go handleProcessSignal()
// Process message handler.
// It's enabled only graceful feature is enabled.
2019-06-19 09:06:52 +08:00
if gracefulEnabled {
2021-09-27 21:27:24 +08:00
intlog.Printf(ctx, "%d: graceful reload feature is enabled", gproc.Pid())
2019-06-19 09:06:52 +08:00
go handleProcessMessage()
} else {
2021-09-27 21:27:24 +08:00
intlog.Printf(ctx, "%d: graceful reload feature is disabled", gproc.Pid())
2019-06-19 09:06:52 +08:00
}
// It's an ugly calling for better initializing the main package path
// in source development environment. It is useful only be used in main goroutine.
2021-05-15 18:13:51 +08:00
// It fails retrieving the main package path in asynchronous goroutines.
2019-06-19 09:06:52 +08:00
gfile.MainPkgPath()
2018-05-10 23:52:09 +08:00
}
// GetServer creates and returns a server instance using given name and default configurations.
// Note that the parameter <name> should be unique for different servers. It returns an existing
// server instance if given <name> is already existing in the server mapping.
2019-06-19 09:06:52 +08:00
func GetServer(name ...interface{}) *Server {
serverName := DefaultServerName
2019-07-28 17:37:13 +08:00
if len(name) > 0 && name[0] != "" {
2019-09-04 19:23:19 +08:00
serverName = gconv.String(name[0])
2019-06-19 09:06:52 +08:00
}
2019-09-04 19:23:19 +08:00
if s := serverMapping.Get(serverName); s != nil {
2019-06-19 09:06:52 +08:00
return s.(*Server)
}
s := &Server{
2019-09-04 19:23:19 +08:00
name: serverName,
plugins: make([]Plugin, 0),
2019-06-19 09:06:52 +08:00
servers: make([]*gracefulServer, 0),
closeChan: make(chan struct{}, 10000),
2019-06-19 09:06:52 +08:00
serverCount: gtype.NewInt(),
statusHandlerMap: make(map[string][]HandlerFunc),
2019-06-19 09:06:52 +08:00
serveTree: make(map[string]interface{}),
serveCache: gcache.New(),
routesMap: make(map[string][]registeredRouteItem),
openapi: goai.New(),
2019-06-19 09:06:52 +08:00
}
// Initialize the server using default configurations.
if err := s.SetConfig(NewConfig()); err != nil {
panic(gerror.WrapCode(gcode.CodeInvalidConfiguration, err, ""))
}
// Record the server to internal server mapping by name.
2019-09-04 19:23:19 +08:00
serverMapping.Set(serverName, s)
2019-06-19 09:06:52 +08:00
return s
2017-12-07 14:57:16 +08:00
}
// Start starts listening on configured port.
// This function does not block the process, you can use function Wait blocking the process.
func (s *Server) Start() error {
2021-09-27 21:27:24 +08:00
var (
ctx = context.TODO()
)
// Swagger UI.
if s.config.SwaggerPath != "" {
2021-10-13 22:28:49 +08:00
swaggerui.Init()
s.AddStaticPath(s.config.SwaggerPath, swaggerUIPackedPath)
s.BindHookHandler(s.config.SwaggerPath+"/*", HookBeforeServe, s.swaggerUI)
s.Logger().Debugf(
ctx,
`swagger ui is serving at address: %s%s/`,
s.getListenAddress(),
s.config.SwaggerPath,
)
}
// OpenApi specification json producing handler.
if s.config.OpenApiPath != "" {
s.BindHandler(s.config.OpenApiPath, s.openapiSpec)
s.Logger().Debugf(
ctx,
`openapi specification is serving at address: %s%s`,
s.getListenAddress(),
s.config.OpenApiPath,
)
} else {
if s.config.SwaggerPath != "" {
s.Logger().Notice(
ctx,
`openapi specification is disabled but swagger ui is serving, which might make no sense`,
)
} else {
s.Logger().Debug(
ctx,
`openapi specification is disabled`,
)
}
}
// Register group routes.
2021-09-27 21:27:24 +08:00
s.handlePreBindItems(ctx)
// Server process initialization, which can only be initialized once.
2019-06-19 09:06:52 +08:00
serverProcessInit()
// Server can only be run once.
2020-12-14 13:26:48 +08:00
if s.Status() == ServerStatusRunning {
return gerror.NewCode(gcode.CodeInvalidOperation, "server is already running")
2019-06-19 09:06:52 +08:00
}
// Logging path setting check.
if s.config.LogPath != "" && s.config.LogPath != s.config.Logger.GetPath() {
if err := s.config.Logger.SetPath(s.config.LogPath); err != nil {
return err
}
2019-06-19 09:06:52 +08:00
}
// Default session storage.
if s.config.SessionStorage == nil {
path := ""
if s.config.SessionPath != "" {
path = gfile.Join(s.config.SessionPath, s.name)
if !gfile.Exists(path) {
if err := gfile.Mkdir(path); err != nil {
return gerror.WrapCodef(gcode.CodeInternalError, err, `mkdir failed for "%s"`, path)
}
}
}
s.config.SessionStorage = gsession.NewStorageFile(path)
}
// Initialize session manager when start running.
s.sessionManager = gsession.New(
s.config.SessionMaxAge,
s.config.SessionStorage,
)
// PProf feature.
if s.config.PProfEnabled {
s.EnablePProf(s.config.PProfPattern)
}
// Default HTTP handler.
2019-06-19 09:06:52 +08:00
if s.config.Handler == nil {
s.config.Handler = s
2019-06-19 09:06:52 +08:00
}
// Install external plugins.
for _, p := range s.plugins {
if err := p.Install(s); err != nil {
s.Logger().Fatalf(ctx, `%+v`, err)
}
}
2020-01-21 22:18:49 +08:00
// Check the group routes again.
2021-09-27 21:27:24 +08:00
s.handlePreBindItems(ctx)
// If there's no route registered and no static service enabled,
// it then returns an error of invalid usage of server.
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
2021-07-20 23:02:02 +08:00
return gerror.NewCode(
gcode.CodeInvalidOperation,
2021-07-20 23:02:02 +08:00
`there's no route set or static feature enabled, did you forget import the router?`,
)
}
// Start the HTTP server.
2019-06-19 09:06:52 +08:00
reloaded := false
2020-12-15 20:16:17 +08:00
fdMapStr := genv.Get(adminActionReloadEnvKey)
2019-06-19 09:06:52 +08:00
if len(fdMapStr) > 0 {
sfm := bufferToServerFdMap([]byte(fdMapStr))
if v, ok := sfm[s.name]; ok {
s.startServer(v)
reloaded = true
}
}
if !reloaded {
s.startServer(nil)
}
// If this is a child process, it then notifies its parent exit.
2019-06-19 09:06:52 +08:00
if gproc.IsChild() {
2021-04-07 20:52:38 +08:00
gtimer.SetTimeout(time.Duration(s.config.GracefulTimeout)*time.Second, func() {
2020-12-15 20:16:17 +08:00
if err := gproc.Send(gproc.PPid(), []byte("exit"), adminGProcCommGroup); err != nil {
intlog.Error(context.TODO(), "server error in process communication:", err)
2019-06-19 09:06:52 +08:00
}
})
}
s.initOpenApi()
s.dumpRouterMap()
2019-06-19 09:06:52 +08:00
return nil
}
func (s *Server) getListenAddress() string {
var (
array = gstr.SplitAndTrim(s.config.Address, ":")
host = `127.0.0.1`
port = 0
)
if len(array) > 1 {
host = array[0]
port = gconv.Int(array[1])
} else {
port = gconv.Int(array[0])
}
return fmt.Sprintf(`http://%s:%d`, host, port)
}
// DumpRouterMap dumps the router map to the log.
func (s *Server) dumpRouterMap() {
2021-09-27 21:27:24 +08:00
var (
ctx = context.TODO()
)
if s.config.DumpRouterMap && len(s.routesMap) > 0 {
buffer := bytes.NewBuffer(nil)
table := tablewriter.NewWriter(buffer)
table.SetHeader([]string{"SERVER", "DOMAIN", "ADDRESS", "METHOD", "ROUTE", "HANDLER", "MIDDLEWARE"})
table.SetRowLine(true)
table.SetBorder(false)
table.SetCenterSeparator("|")
2018-10-17 16:56:50 +08:00
for _, item := range s.GetRoutes() {
data := make([]string, 7)
data[0] = item.Server
data[1] = item.Domain
data[2] = item.Address
data[3] = item.Method
data[4] = item.Route
data[5] = item.Handler.Name
data[6] = item.Middleware
table.Append(data)
}
table.Render()
2021-09-27 21:27:24 +08:00
s.config.Logger.Header(false).Printf(ctx, "\n%s", buffer.String())
2019-06-19 09:06:52 +08:00
}
}
2019-06-19 09:06:52 +08:00
// GetOpenApi returns the OpenApi specification management object of current server.
func (s *Server) GetOpenApi() *goai.OpenApiV3 {
return s.openapi
}
// GetRoutes retrieves and returns the router array.
// The key of the returned map is the domain of the server.
func (s *Server) GetRoutes() []RouterItem {
2019-06-19 09:06:52 +08:00
m := make(map[string]*garray.SortedArray)
address := s.config.Address
if s.config.HTTPSAddr != "" {
if len(address) > 0 {
address += ","
}
address += "tls" + s.config.HTTPSAddr
}
2019-06-19 09:06:52 +08:00
for k, registeredItems := range s.routesMap {
array, _ := gregex.MatchString(`(.*?)%([A-Z]+):(.+)@(.+)`, k)
for index, registeredItem := range registeredItems {
item := RouterItem{
Server: s.name,
Address: address,
Domain: array[4],
Type: registeredItem.Handler.Type,
Middleware: array[1],
Method: array[2],
Route: array[3],
Priority: len(registeredItems) - index - 1,
Handler: registeredItem.Handler,
}
switch item.Handler.Type {
case HandlerTypeObject, HandlerTypeHandler:
item.IsServiceHandler = true
case HandlerTypeMiddleware:
item.Middleware = "GLOBAL MIDDLEWARE"
}
if len(item.Handler.Middleware) > 0 {
for _, v := range item.Handler.Middleware {
if item.Middleware != "" {
2021-05-02 08:10:35 +08:00
item.Middleware += ","
}
item.Middleware += gdebug.FuncName(v)
}
2019-06-19 09:06:52 +08:00
}
2021-05-01 09:04:16 +08:00
// If the domain does not exist in the dump map, it creates the map.
// The value of the map is a custom sorted array.
if _, ok := m[item.Domain]; !ok {
// Sort in ASC order.
m[item.Domain] = garray.NewSortedArray(func(v1, v2 interface{}) int {
item1 := v1.(RouterItem)
item2 := v2.(RouterItem)
2019-06-19 09:06:52 +08:00
r := 0
if r = strings.Compare(item1.Domain, item2.Domain); r == 0 {
if r = strings.Compare(item1.Route, item2.Route); r == 0 {
if r = strings.Compare(item1.Method, item2.Method); r == 0 {
if item1.Handler.Type == HandlerTypeMiddleware && item2.Handler.Type != HandlerTypeMiddleware {
return -1
} else if item1.Handler.Type == HandlerTypeMiddleware && item2.Handler.Type == HandlerTypeMiddleware {
return 1
} else if r = strings.Compare(item1.Middleware, item2.Middleware); r == 0 {
r = item2.Priority - item1.Priority
2019-06-19 09:06:52 +08:00
}
}
}
}
return r
})
2019-06-19 09:06:52 +08:00
}
m[item.Domain].Add(item)
2019-06-19 09:06:52 +08:00
}
}
routerArray := make([]RouterItem, 0, 128)
for _, array := range m {
for _, v := range array.Slice() {
routerArray = append(routerArray, v.(RouterItem))
2019-06-19 09:06:52 +08:00
}
}
return routerArray
}
// Run starts server listening in blocking way.
// It's commonly used for single server situation.
func (s *Server) Run() {
2021-09-27 21:27:24 +08:00
var (
ctx = context.TODO()
)
2019-06-19 09:06:52 +08:00
if err := s.Start(); err != nil {
s.Logger().Fatalf(ctx, `%+v`, err)
2019-06-19 09:06:52 +08:00
}
// Blocking using channel.
2019-06-19 09:06:52 +08:00
<-s.closeChan
// Remove plugins.
if len(s.plugins) > 0 {
for _, p := range s.plugins {
intlog.Printf(ctx, `remove plugin: %s`, p.Name())
if err := p.Remove(); err != nil {
intlog.Errorf(ctx, "%+v", err)
}
}
}
2021-09-27 21:27:24 +08:00
s.Logger().Printf(ctx, "%d: all servers shutdown", gproc.Pid())
2018-05-10 19:16:41 +08:00
}
2018-05-10 17:48:47 +08:00
// Wait blocks to wait for all servers done.
// It's commonly used in multiple servers situation.
func Wait() {
2021-09-27 21:27:24 +08:00
var (
ctx = context.TODO()
)
2019-06-19 09:06:52 +08:00
<-allDoneChan
// Remove plugins.
serverMapping.Iterator(func(k string, v interface{}) bool {
s := v.(*Server)
if len(s.plugins) > 0 {
for _, p := range s.plugins {
2021-09-27 21:27:24 +08:00
intlog.Printf(ctx, `remove plugin: %s`, p.Name())
if err := p.Remove(); err != nil {
intlog.Error(ctx, err)
}
}
}
return true
})
2021-09-27 21:27:24 +08:00
glog.Printf(ctx, "%d: all servers shutdown", gproc.Pid())
}
// startServer starts the underlying server listening.
2018-05-10 23:52:09 +08:00
func (s *Server) startServer(fdMap listenerFdMap) {
2021-09-27 21:27:24 +08:00
var (
ctx = context.TODO()
httpsEnabled bool
)
// HTTPS
if s.config.TLSConfig != nil || (s.config.HTTPSCertPath != "" && s.config.HTTPSKeyPath != "") {
2019-06-19 09:06:52 +08:00
if len(s.config.HTTPSAddr) == 0 {
if len(s.config.Address) > 0 {
s.config.HTTPSAddr = s.config.Address
s.config.Address = ""
2019-06-19 09:06:52 +08:00
} else {
s.config.HTTPSAddr = defaultHttpsAddr
2019-06-19 09:06:52 +08:00
}
}
httpsEnabled = len(s.config.HTTPSAddr) > 0
var array []string
if v, ok := fdMap["https"]; ok && len(v) > 0 {
array = strings.Split(v, ",")
} else {
array = strings.Split(s.config.HTTPSAddr, ",")
}
for _, v := range array {
if len(v) == 0 {
continue
}
fd := 0
itemFunc := v
2019-06-19 09:06:52 +08:00
array := strings.Split(v, "#")
if len(array) > 1 {
itemFunc = array[0]
2021-09-27 21:27:24 +08:00
// The Windows OS does not support socket file descriptor passing
// from parent process.
2019-06-19 09:06:52 +08:00
if runtime.GOOS != "windows" {
fd = gconv.Int(array[1])
}
}
if fd > 0 {
s.servers = append(s.servers, s.newGracefulServer(itemFunc, fd))
2019-06-19 09:06:52 +08:00
} else {
s.servers = append(s.servers, s.newGracefulServer(itemFunc))
2019-06-19 09:06:52 +08:00
}
s.servers[len(s.servers)-1].isHttps = true
}
}
// HTTP
if !httpsEnabled && len(s.config.Address) == 0 {
s.config.Address = defaultHttpAddr
2019-06-19 09:06:52 +08:00
}
var array []string
if v, ok := fdMap["http"]; ok && len(v) > 0 {
array = strings.Split(v, ",")
} else {
array = strings.Split(s.config.Address, ",")
2019-06-19 09:06:52 +08:00
}
for _, v := range array {
if len(v) == 0 {
continue
}
fd := 0
itemFunc := v
2019-06-19 09:06:52 +08:00
array := strings.Split(v, "#")
if len(array) > 1 {
itemFunc = array[0]
2021-09-27 21:27:24 +08:00
// The Windows OS does not support socket file descriptor passing
// from parent process.
2019-06-19 09:06:52 +08:00
if runtime.GOOS != "windows" {
fd = gconv.Int(array[1])
}
}
if fd > 0 {
s.servers = append(s.servers, s.newGracefulServer(itemFunc, fd))
2019-06-19 09:06:52 +08:00
} else {
s.servers = append(s.servers, s.newGracefulServer(itemFunc))
2019-06-19 09:06:52 +08:00
}
}
2021-05-15 18:13:51 +08:00
// Start listening asynchronously.
2019-06-19 09:06:52 +08:00
serverRunning.Add(1)
for _, v := range s.servers {
go func(server *gracefulServer) {
s.serverCount.Add(1)
err := (error)(nil)
if server.isHttps {
err = server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath, s.config.TLSConfig)
2019-06-19 09:06:52 +08:00
} else {
err = server.ListenAndServe()
}
// The process exits if the server is closed with none closing error.
2019-06-19 09:06:52 +08:00
if err != nil && !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) {
s.Logger().Fatalf(ctx, `%+v`, err)
2019-06-19 09:06:52 +08:00
}
// If all the underlying servers shutdown, the process exits.
2019-06-19 09:06:52 +08:00
if s.serverCount.Add(-1) < 1 {
s.closeChan <- struct{}{}
if serverRunning.Add(-1) < 1 {
serverMapping.Remove(s.name)
allDoneChan <- struct{}{}
}
}
}(v)
}
2018-08-01 13:04:15 +08:00
}
2018-04-23 19:22:59 +08:00
// Status retrieves and returns the server status.
2018-08-01 13:04:15 +08:00
func (s *Server) Status() int {
2019-06-19 09:06:52 +08:00
if serverRunning.Val() == 0 {
2020-12-14 13:26:48 +08:00
return ServerStatusStopped
2019-06-19 09:06:52 +08:00
}
// If any underlying server is running, the server status is running.
2019-06-19 09:06:52 +08:00
for _, v := range s.servers {
2020-12-14 13:26:48 +08:00
if v.status == ServerStatusRunning {
return ServerStatusRunning
2019-06-19 09:06:52 +08:00
}
}
2020-12-14 13:26:48 +08:00
return ServerStatusStopped
}
// getListenerFdMap retrieves and returns the socket file descriptors.
// The key of the returned map is "http" and "https".
2018-05-10 23:52:09 +08:00
func (s *Server) getListenerFdMap() map[string]string {
2019-06-19 09:06:52 +08:00
m := map[string]string{
"https": "",
"http": "",
}
for _, v := range s.servers {
str := v.address + "#" + gconv.String(v.Fd()) + ","
2019-06-19 09:06:52 +08:00
if v.isHttps {
if len(m["https"]) > 0 {
m["https"] += ","
}
2019-06-19 09:06:52 +08:00
m["https"] += str
} else {
if len(m["http"]) > 0 {
m["http"] += ","
}
2019-06-19 09:06:52 +08:00
m["http"] += str
}
}
return m
2018-05-10 19:16:41 +08:00
}