diff --git a/g/net/ghttp/http_client_request.go b/g/net/ghttp/ghttp_client_request.go similarity index 100% rename from g/net/ghttp/http_client_request.go rename to g/net/ghttp/ghttp_client_request.go diff --git a/g/net/ghttp/http_client_response.go b/g/net/ghttp/ghttp_client_response.go similarity index 100% rename from g/net/ghttp/http_client_response.go rename to g/net/ghttp/ghttp_client_response.go diff --git a/g/net/ghttp/http_controller.go b/g/net/ghttp/ghttp_controller.go similarity index 100% rename from g/net/ghttp/http_controller.go rename to g/net/ghttp/ghttp_controller.go diff --git a/g/net/ghttp/http_func.go b/g/net/ghttp/ghttp_func.go similarity index 100% rename from g/net/ghttp/http_func.go rename to g/net/ghttp/ghttp_func.go diff --git a/g/net/ghttp/http_request.go b/g/net/ghttp/ghttp_request.go similarity index 100% rename from g/net/ghttp/http_request.go rename to g/net/ghttp/ghttp_request.go diff --git a/g/net/ghttp/http_response.go b/g/net/ghttp/ghttp_response.go similarity index 100% rename from g/net/ghttp/http_response.go rename to g/net/ghttp/ghttp_response.go diff --git a/g/net/ghttp/http_server.go b/g/net/ghttp/ghttp_server.go similarity index 88% rename from g/net/ghttp/http_server.go rename to g/net/ghttp/ghttp_server.go index 1315bb00b..d8e6fcb8e 100644 --- a/g/net/ghttp/http_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -7,6 +7,7 @@ package ghttp import ( + "os" "sync" "errors" "strings" @@ -31,36 +32,45 @@ const ( gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称 ) -// http server结构体 +// ghttp.Server结构体 type Server struct { - hmmu sync.RWMutex // handler互斥锁 - hhmu sync.RWMutex // hooks互斥锁 - hsmu sync.RWMutex // status handler互斥锁 - hmcmu sync.RWMutex // handlerCache互斥锁 - hhcmu sync.RWMutex // hooksCache互斥锁 + // 基本属性变量 name string // 服务名称,方便识别 config ServerConfig // 配置对象 status int8 // 当前服务器状态(0:未启动,1:运行中) + servers []*http.Server // 底层http.Server列表 methodsMap map[string]bool // 所有支持的HTTP Method(初始化时自动填充) + servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况),同时作为请求ID + closeQueue *gqueue.Queue // 请求结束的关闭队列(存放的是需要异步关闭处理的*Request对象) + signalQueue chan os.Signal // 终端命令行监听队列 + // 服务注册相关 + hmmu sync.RWMutex // handler互斥锁 + hmcmu sync.RWMutex // handlerCache互斥锁 handlerMap HandlerMap // 所有注册的回调函数(静态匹配) - statusHandlerMap map[string]HandlerFunc // 不同状态码下的注册处理方法(例如404状态时的处理方法) handlerTree map[string]interface{} // 所有注册的回调函数(动态匹配,树型+链表优先级匹配) - hooksTree map[string]interface{} // 所有注册的事件回调函数(动态匹配,树型+链表优先级匹配) handlerCache *gcache.Cache // 服务注册路由内存缓存 + // 事件回调注册 + hhmu sync.RWMutex // hooks互斥锁 + hhcmu sync.RWMutex // hooksCache互斥锁 + hooksTree map[string]interface{} // 所有注册的事件回调函数(动态匹配,树型+链表优先级匹配) hooksCache *gcache.Cache // 回调事件注册路由内存缓存 - servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况) + // 自定义状态码回调 + hsmu sync.RWMutex // status handler互斥锁 + statusHandlerMap map[string]HandlerFunc // 不同状态码下的注册处理方法(例如404状态时的处理方法) + // COOKIE cookieMaxAge *gtype.Int // Cookie有效期 + cookies *gmap.IntInterfaceMap // 当前服务器正在服务(请求正在执行)的Cookie(每个请求一个Cookie对象) + // SESSION sessionMaxAge *gtype.Int // Session有效期 sessionIdName *gtype.String // SessionId名称 - cookies *gmap.IntInterfaceMap // 当前服务器正在服务(请求正在执行)的Cookie(每个请求一个Cookie对象) sessions *gcache.Cache // Session内存缓存 - closeQueue *gqueue.Queue // 请求结束的关闭队列(存放的是需要异步关闭处理的*Request对象) + // 日志相关属性 logPath *gtype.String // 存放日志的目录路径 + logHandler *gtype.Interface // 自定义日志处理回调方法 errorLogEnabled *gtype.Bool // 是否开启error log accessLogEnabled *gtype.Bool // 是否开启access log accessLogger *glog.Logger // access log日志对象 errorLogger *glog.Logger // error log日志对象 - logHandler *gtype.Interface // 自定义的日志处理回调方法 } // 域名、URI与回调函数的绑定记录表 @@ -100,6 +110,7 @@ func GetServer(name...interface{}) (*Server) { } s := &Server { name : sname, + servers : make([]*http.Server, 0), methodsMap : make(map[string]bool), handlerMap : make(HandlerMap), statusHandlerMap : make(map[string]HandlerFunc), @@ -114,6 +125,7 @@ func GetServer(name...interface{}) (*Server) { sessionIdName : gtype.NewString(gDEFAULT_SESSION_ID_NAME), servedCount : gtype.NewInt(), closeQueue : gqueue.New(), + signalQueue : make(chan os.Signal), logPath : gtype.NewString(), accessLogEnabled : gtype.NewBool(), errorLogEnabled : gtype.NewBool(true), @@ -145,7 +157,7 @@ func (s *Server) Run() error { s.config.Handler = http.HandlerFunc(s.defaultHttpHandle) } - // 开启异步处理队列处理循环 + // 开启异步关闭队列处理循环 s.startCloseQueueLoop() // 开始执行底层Web Server创建,端口监听 @@ -163,9 +175,12 @@ func (s *Server) Run() error { for _, v := range array { wg.Add(1) go func(addr string) { - if err := s.newServer(addr).ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { + server := s.newGracefulServer(addr) + if err := server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { glog.Error(err) wg.Done() + } else { + //s.servers = append(s.servers, server) } }(v) } @@ -178,9 +193,12 @@ func (s *Server) Run() error { for _, v := range array { wg.Add(1) go func(addr string) { - if err := s.newServer(addr).ListenAndServe(); err != nil { + server := s.newGracefulServer(addr) + if err := server.ListenAndServe(); err != nil { glog.Error(err) wg.Done() + } else { + //s.servers = append(s.servers, server) } }(v) } diff --git a/g/net/ghttp/http_server_auto.go b/g/net/ghttp/ghttp_server_auto.go similarity index 100% rename from g/net/ghttp/http_server_auto.go rename to g/net/ghttp/ghttp_server_auto.go diff --git a/g/net/ghttp/http_server_config.go b/g/net/ghttp/ghttp_server_config.go similarity index 100% rename from g/net/ghttp/http_server_config.go rename to g/net/ghttp/ghttp_server_config.go diff --git a/g/net/ghttp/http_server_cookie.go b/g/net/ghttp/ghttp_server_cookie.go similarity index 100% rename from g/net/ghttp/http_server_cookie.go rename to g/net/ghttp/ghttp_server_cookie.go diff --git a/g/net/ghttp/http_server_domain.go b/g/net/ghttp/ghttp_server_domain.go similarity index 100% rename from g/net/ghttp/http_server_domain.go rename to g/net/ghttp/ghttp_server_domain.go diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go new file mode 100644 index 000000000..3c324ea92 --- /dev/null +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -0,0 +1,189 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +package ghttp + +import ( + "fmt" + "net" + "os" + "syscall" + "context" + "net/http" + "os/signal" + "crypto/tls" + "gitee.com/johng/gf/g/os/glog" +) + +const ( + gGF_WEB_SERVER_GRACEFUL_ENVIRON_KEY = "GF_WEB_SERVER_GRACEFUL" + gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING = gGF_WEB_SERVER_GRACEFUL_ENVIRON_KEY + "=1" +) + +// 优雅的Web Server对象封装 +type gracefulServer struct { + httpServer *http.Server + listener net.Listener + isGraceful bool + signalChan chan os.Signal + shutdownChan chan bool +} + +// 创建一个优雅的Http Server +func (s *Server) newGracefulServer(addr string) *gracefulServer { + isGraceful := false + if os.Getenv(gGF_WEB_SERVER_GRACEFUL_ENVIRON_KEY) != "" { + isGraceful = true + } + return &gracefulServer { + httpServer : s.newServer(addr), + isGraceful : isGraceful, + signalChan : make(chan os.Signal), + shutdownChan : make(chan bool), + } +} + +// 执行HTTP监听 +func (s *gracefulServer) ListenAndServe() error { + addr := s.httpServer.Addr + ln, err := s.getNetListener(addr) + if err != nil { + return err + } + s.listener = ln + return s.doServe() +} + +// 执行HTTPS监听 +func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string) error { + addr := s.httpServer.Addr + config := &tls.Config{} + if s.httpServer.TLSConfig != nil { + *config = *s.httpServer.TLSConfig + } + if config.NextProtos == nil { + config.NextProtos = []string{"http/1.1"} + } + var err error + config.Certificates = make([]tls.Certificate, 1) + config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + ln, err := s.getNetListener(addr) + if err != nil { + return err + } + s.listener = tls.NewListener(ln, config) + return s.doServe() +} + +// 开始执行Web Server服务处理 +func (s *gracefulServer) doServe() error { + go s.handleSignals() + err := s.httpServer.Serve(s.listener) + <-s.shutdownChan + return err +} + +// 自定义的net.Listener +func (s *gracefulServer) getNetListener(addr string) (net.Listener, error) { + var ln net.Listener + var err error + if s.isGraceful { + //path := fmt.Sprintf("%s%sgf.web.server.fd.%d", gfile.TempDir(), gfile.Separator,gtime.Nanosecond()) + //f, err := gfile.Open(path) + //if err != nil { + // return nil, err + //} + f := os.NewFile(3, "") + ln, err = net.FileListener(f) + if err != nil { + err = fmt.Errorf("net.FileListener error: %v", err) + return nil, err + } + } else { + ln, err = net.Listen("tcp", addr) + if err != nil { + err = fmt.Errorf("net.Listen error: %v", err) + return nil, err + } + } + return ln, nil +} + +// 处理终端信号指令监听 +func (s *gracefulServer) handleSignals() { + var sig os.Signal + + signal.Notify( + s.signalChan, + syscall.SIGTERM, + syscall.SIGUSR2, + ) + + for { + sig = <-s.signalChan + switch sig { + case syscall.SIGTERM: + glog.Println("received SIGTERM, graceful shutting down HTTP server") + s.shutdown() + + case syscall.SIGUSR2: + glog.Println("received SIGUSR2, graceful restarting HTTP server") + if pid, err := s.startNewProcess(); err != nil { + glog.Printf("start new process failed: %v, continue serving\n", err) + } else { + glog.Printf("start new process successfully, the new pid is %d\n", pid) + s.shutdown() + } + default: + } + } +} + +// 执行请求优雅关闭 +func (s *gracefulServer) shutdown() { + if err := s.httpServer.Shutdown(context.Background()); err != nil { + glog.Errorf("server shutdown error: %v\n", err) + } else { + glog.Println("server shutdown success") + s.shutdownChan <- true + } +} + +// 创建子进程来监听并处理新的HTTP请求,与父进程使用的是同一个socket文件描述符 +func (s *gracefulServer) startNewProcess() (uintptr, error) { + listenerFd, err := s.getTCPListenerFd() + if err != nil { + return 0, fmt.Errorf("failed to get socket file descriptor: %v", err) + } + // 构造子进程的环境变量,并增加环境变量参数以标识该进程是graceful子进程 + envs := []string{} + for _, value := range os.Environ() { + if value != gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING { + envs = append(envs, value) + } + } + envs = append(envs, gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING) + fork, err := syscall.ForkExec(os.Args[0], os.Args, &syscall.ProcAttr { + Env : os.Environ(), + Files : []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFd}, + }) + if err != nil { + return 0, fmt.Errorf("failed to forkexec: %v", err) + } + return uintptr(fork), nil +} + +// 获得对应net.TCPListener的文件描述符文件ID +func (s *gracefulServer) getTCPListenerFd() (uintptr, error) { + file, err := s.listener.(*net.TCPListener).File() + if err != nil { + return 0, err + } + return file.Fd(), nil +} diff --git a/g/net/ghttp/http_server_handler.go b/g/net/ghttp/ghttp_server_handler.go similarity index 100% rename from g/net/ghttp/http_server_handler.go rename to g/net/ghttp/ghttp_server_handler.go diff --git a/g/net/ghttp/http_server_hooks.go b/g/net/ghttp/ghttp_server_hooks.go similarity index 100% rename from g/net/ghttp/http_server_hooks.go rename to g/net/ghttp/ghttp_server_hooks.go diff --git a/g/net/ghttp/http_server_log.go b/g/net/ghttp/ghttp_server_log.go similarity index 100% rename from g/net/ghttp/http_server_log.go rename to g/net/ghttp/ghttp_server_log.go diff --git a/g/net/ghttp/http_server_options.go b/g/net/ghttp/ghttp_server_options.go similarity index 100% rename from g/net/ghttp/http_server_options.go rename to g/net/ghttp/ghttp_server_options.go diff --git a/g/net/ghttp/http_server_pprof.go b/g/net/ghttp/ghttp_server_pprof.go similarity index 100% rename from g/net/ghttp/http_server_pprof.go rename to g/net/ghttp/ghttp_server_pprof.go diff --git a/g/net/ghttp/http_server_router.go b/g/net/ghttp/ghttp_server_router.go similarity index 100% rename from g/net/ghttp/http_server_router.go rename to g/net/ghttp/ghttp_server_router.go diff --git a/g/net/ghttp/http_server_service.go b/g/net/ghttp/ghttp_server_service.go similarity index 100% rename from g/net/ghttp/http_server_service.go rename to g/net/ghttp/ghttp_server_service.go diff --git a/g/net/ghttp/http_server_session.go b/g/net/ghttp/ghttp_server_session.go similarity index 100% rename from g/net/ghttp/http_server_session.go rename to g/net/ghttp/ghttp_server_session.go diff --git a/g/net/ghttp/http_server_status.go b/g/net/ghttp/ghttp_server_status.go similarity index 100% rename from g/net/ghttp/http_server_status.go rename to g/net/ghttp/ghttp_server_status.go diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go index 2ab52d11c..4993dba69 100644 --- a/g/os/gfile/gfile.go +++ b/g/os/gfile/gfile.go @@ -462,4 +462,9 @@ func MainPkgPath() string { return p } return "" +} + +// 系统临时目录 +func TempDir() string { + return os.TempDir() } \ No newline at end of file diff --git a/geg/encoding/gbase64.go b/geg/encoding/gbase64.go new file mode 100644 index 000000000..416d35d1a --- /dev/null +++ b/geg/encoding/gbase64.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "gitee.com/johng/gf/g/encoding/gbase64" +) + +func main() { + s := "john" + b := gbase64.Encode(s) + c, e := gbase64.Decode(b) + fmt.Println(b) + fmt.Println(c) + fmt.Println(e) +} + + + diff --git a/geg/other/graceful.go b/geg/other/graceful.go new file mode 100644 index 000000000..dbe5b5168 --- /dev/null +++ b/geg/other/graceful.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "net/http" + + "github.com/tabalt/gracehttp" +) + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello world") + }) + + err := gracehttp.ListenAndServe(":8888", nil) + if err != nil { + fmt.Println(err) + } +} \ No newline at end of file diff --git a/geg/other/test.go b/geg/other/test.go index 56d488a57..dbe5b5168 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -2,8 +2,18 @@ package main import ( "fmt" + "net/http" + + "github.com/tabalt/gracehttp" ) func main() { - fmt.Println("我要加入gf框架开发团队!!") -} + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello world") + }) + + err := gracehttp.ListenAndServe(":8888", nil) + if err != nil { + fmt.Println(err) + } +} \ No newline at end of file