From 66287c2d0ee554ffb91a9c24de8d4c14e9f536a2 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 28 Feb 2019 23:57:20 +0800 Subject: [PATCH] update the admin feature and unit test cases of ghttp --- g/net/ghttp/ghttp_server.go | 55 ++-- g/net/ghttp/ghttp_server_admin.go | 265 +---------------- g/net/ghttp/ghttp_server_admin_process.go | 269 ++++++++++++++++++ g/net/ghttp/ghttp_unit_cookie_test.go | 17 +- g/net/ghttp/ghttp_unit_init_test.go | 25 ++ g/net/ghttp/ghttp_unit_param_test.go | 17 +- g/net/ghttp/ghttp_unit_router_basic_test.go | 79 +++-- .../ghttp_unit_router_controller_test.go | 65 +++++ g/net/ghttp/ghttp_unit_router_group_test.go | 93 +++--- g/net/ghttp/ghttp_unit_router_object_test.go | 60 ++++ g/net/ghttp/ghttp_unit_session_test.go | 17 +- geg/net/ghttp/server/reload/multi_port.go | 23 -- 12 files changed, 579 insertions(+), 406 deletions(-) create mode 100644 g/net/ghttp/ghttp_server_admin_process.go create mode 100644 g/net/ghttp/ghttp_unit_init_test.go create mode 100644 g/net/ghttp/ghttp_unit_router_controller_test.go create mode 100644 g/net/ghttp/ghttp_unit_router_object_test.go delete mode 100644 geg/net/ghttp/server/reload/multi_port.go diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 952b3ac01..22a1123e3 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -19,8 +19,8 @@ import ( "github.com/gogf/gf/g/os/glog" "github.com/gogf/gf/g/os/gproc" "github.com/gogf/gf/g/os/gtimer" - "github.com/gogf/gf/g/util/gconv" "github.com/gogf/gf/g/text/gregex" + "github.com/gogf/gf/g/util/gconv" "github.com/gogf/gf/third/github.com/gorilla/websocket" "github.com/gogf/gf/third/github.com/olekukonko/tablewriter" "net/http" @@ -39,6 +39,8 @@ type ( name string // 服务名称,方便识别 config ServerConfig // 配置对象 servers []*gracefulServer // 底层http.Server列表 + serverCount *gtype.Int // 底层http.Server数量 + closeChan chan struct{} // 用以关闭事件通知的通道 methodsMap map[string]struct{} // 所有支持的HTTP Method(初始化时自动填充) servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况),同时作为请求ID // 服务注册相关 @@ -101,8 +103,8 @@ type ( ) const ( - SERVER_STATUS_STOPPED = 0 // Server状态:停止 - SERVER_STATUS_RUNNING = 1 // Server状态:运行 + SERVER_STATUS_STOPPED = 0 // Server状态:停止 + SERVER_STATUS_RUNNING = 1 // Server状态:运行 HOOK_BEFORE_SERVE = "BeforeServe" HOOK_AFTER_SERVE = "AfterServe" HOOK_BEFORE_OUTPUT = "BeforeOutput" @@ -123,21 +125,21 @@ const ( ) var ( - // Server表,用以存储和检索名称与Server对象之间的关联关系 + // WebServer表,用以存储和检索名称与Server对象之间的关联关系 serverMapping = gmap.NewStringInterfaceMap() - // 正常运行的Server数量,如果没有运行、失败或者全部退出,那么该值为0 + // 正常运行的WebServer数量,如果没有运行、失败或者全部退出,那么该值为0 serverRunning = gtype.NewInt() - // Web Socket默认配置 + // WebSocket默认配置 wsUpgrader = websocket.Upgrader { // 默认允许WebSocket请求跨域,权限控制可以由业务层自己负责,灵活度更高 CheckOrigin: func(r *http.Request) bool { return true }, } - // Web Server已完成服务事件通道,当有事件时表示服务完成,当前进程退出 - doneChan = make(chan struct{}, 1000) + // WebServer已完成服务事件通道,当有事件时表示服务完成,当前进程退出 + allDoneChan = make(chan struct{}, 1000) // 用于服务进程初始化,只能初始化一次,采用“懒初始化”(在server运行时才初始化) serverProcessInited = gtype.NewBool() @@ -174,6 +176,11 @@ func serverProcessInit() { if gracefulEnabled { go handleProcessMessage() } + + // 是否处于开发环境 + if gfile.MainPkgPath() != "" { + glog.Debug("GF notices that you're in develop environment, so error logs are auto enabled to stdout.") + } } // 获取/创建一个默认配置的HTTP Server(默认监听端口是80) @@ -189,6 +196,8 @@ func GetServer(name...interface{}) (*Server) { s := &Server { name : sname, servers : make([]*gracefulServer, 0), + closeChan : make(chan struct{}, 100), + serverCount : gtype.NewInt(), methodsMap : make(map[string]struct{}), statusHandlerMap : make(map[string]HandlerFunc), serveTree : make(map[string]interface{}), @@ -212,8 +221,8 @@ func GetServer(name...interface{}) (*Server) { return s } -// 作为守护协程异步执行(当同一进程中存在多个Web Server时,需要采用这种方式执行) -// 需要结合Wait方式一起使用 +// 作为守护协程异步执行(当同一进程中存在多个Web Server时,需要采用这种方式执行), +// 需要结合Wait方式一起使用. func (s *Server) Start() error { // 服务进程初始化,只会初始化一次 serverProcessInit() @@ -228,11 +237,12 @@ func (s *Server) Start() error { s.config.Handler = http.HandlerFunc(s.defaultHttpHandle) } // 不允许访问的路由注册(使用HOOK实现) + // @TODO 去掉HOOK的实现方式 if s.config.DenyRoutes != nil { for _, v := range s.config.DenyRoutes { s.BindHookHandler(v, HOOK_BEFORE_SERVE, func(r *Request) { r.Response.WriteStatus(403) - r.Exit() + r.ExitAll() }) } } @@ -266,10 +276,6 @@ func (s *Server) Start() error { } }) } - // 是否处于开发环境 - if gfile.MainPkgPath() != "" { - glog.Debug("GF notices that you're in develop environment, so error logs are auto enabled to stdout.") - } // 打印展示路由表 s.DumpRoutesMap() @@ -367,7 +373,7 @@ func (s *Server) Run() error { return err } // 阻塞等待服务执行完成 - <- doneChan + <- s.closeChan glog.Printfln("%d: all servers shutdown", gproc.Pid()) return nil @@ -378,7 +384,7 @@ func (s *Server) Run() error { // 这是一个与进程相关的方法 func Wait() { // 阻塞等待服务执行完成 - <- doneChan + <- allDoneChan glog.Printfln("%d: all servers shutdown", gproc.Pid()) } @@ -462,23 +468,28 @@ func (s *Server) startServer(fdMap listenerFdMap) { } } // 开始执行异步监听 + serverRunning.Add(1) for _, v := range s.servers { go func(server *gracefulServer) { - serverRunning.Add(1) + s.serverCount.Add(1) err := (error)(nil) if server.isHttps { err = server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath) } else { err = server.ListenAndServe() } - serverRunning.Add(-1) // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if err != nil && !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { glog.Fatal(err) } - // 如果所有异步的Server都已经停止,并且没有在管理操作(重启/关闭)进行中,那么主Server就可以退出了 - if serverRunning.Val() < 1 && serverProcessStatus.Val() == 0 { - doneChan <- struct{}{} + // 如果所有异步的http.Server都已经停止,那么WebServer就可以退出了 + if s.serverCount.Add(-1) < 1 { + s.closeChan <- struct{}{} + // 如果所有WebServer都退出,那么退出Wait等待 + if serverRunning.Add(-1) < 1 { + serverMapping.Remove(s.name) + allDoneChan <- struct{}{} + } } }(v) } diff --git a/g/net/ghttp/ghttp_server_admin.go b/g/net/ghttp/ghttp_server_admin.go index b76e68ca0..d1ddf760f 100644 --- a/g/net/ghttp/ghttp_server_admin.go +++ b/g/net/ghttp/ghttp_server_admin.go @@ -8,46 +8,14 @@ package ghttp import ( - "github.com/gogf/gf/g/os/gtimer" - "strings" - "github.com/gogf/gf/g/os/gview" "github.com/gogf/gf/g/os/gproc" - "sync" - "github.com/gogf/gf/g/os/gtime" - "errors" - "fmt" - "github.com/gogf/gf/g/container/gtype" - "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/os/gtimer" + "github.com/gogf/gf/g/os/gview" "os" - "github.com/gogf/gf/g/encoding/gjson" - "github.com/gogf/gf/g/util/gconv" + "strings" "time" - "runtime" - "bytes" ) -const ( - gADMIN_ACTION_INTERVAL_LIMIT = 2000 // (毫秒)服务开启后允许执行管理操作的间隔限制 - gADMIN_ACTION_NONE = 0 - gADMIN_ACTION_RESTARTING = 1 - gADMIN_ACTION_SHUTINGDOWN = 2 - gADMIN_ACTION_RELOAD_ENVKEY = "GF_SERVER_RELOAD" - gADMIN_ACTION_RESTART_ENVKEY = "GF_SERVER_RESTART" - gADMIN_GPROC_COMM_GROUP = "GF_GPROC_HTTP_SERVER" -) - -// 用于服务管理的对象 -type utilAdmin struct {} - -// (进程级别)用于Web Server管理操作的互斥锁,保证管理操作的原子性 -var serverActionLocker sync.Mutex - -// (进程级别)用于记录上一次操作的时间(毫秒) -var serverActionLastTime = gtype.NewInt64(gtime.Millisecond()) - -// 当前服务进程所处的互斥管理操作状态 -var serverProcessStatus = gtype.NewInt() - // 服务管理首页 func (p *utilAdmin) Index(r *Request) { data := map[string]interface{}{ @@ -79,9 +47,9 @@ func (p *utilAdmin) Restart(r *Request) { } // 执行重启操作 if len(path) > 0 { - err = r.Server.Restart(path) + err = RestartAllServer(path) } else { - err = r.Server.Restart() + err = RestartAllServer() } if err == nil { r.Response.Write("server restarted") @@ -93,7 +61,7 @@ func (p *utilAdmin) Restart(r *Request) { // 服务关闭 func (p *utilAdmin) Shutdown(r *Request) { r.Server.Shutdown() - if err := r.Server.Shutdown(); err == nil { + if err := ShutdownAllServer(); err == nil { r.Response.Write("server shutdown") } else { r.Response.Write(err.Error()) @@ -109,222 +77,15 @@ func (s *Server) EnableAdmin(pattern...string) { s.BindObject(p, &utilAdmin{}) } -// 重启Web Server,参数支持自定义重启的可执行文件路径,不传递时默认和原有可执行文件路径一致。 -// 针对*niux系统: 平滑重启 -// 针对windows : 完整重启 -func (s *Server) Restart(newExeFilePath...string) error { - serverActionLocker.Lock() - defer serverActionLocker.Unlock() - if err := s.checkActionStatus(); err != nil { - return err - } - if err := s.checkActionFrequence(); err != nil { - return err - } - return restartWebServers("", newExeFilePath...) -} - -// 关闭Web Server +// 关闭当前Web Server func (s *Server) Shutdown() error { - serverActionLocker.Lock() - defer serverActionLocker.Unlock() - if err := s.checkActionStatus(); err != nil { - return err - } - if err := s.checkActionFrequence(); err != nil { - return err - } - shutdownWebServers("") - return nil -} - -// 检测当前操作的频繁度 -func (s *Server) checkActionFrequence() error { - interval := gtime.Millisecond() - serverActionLastTime.Val() - if interval < gADMIN_ACTION_INTERVAL_LIMIT { - return errors.New(fmt.Sprintf("too frequent action, please retry in %d ms", gADMIN_ACTION_INTERVAL_LIMIT - interval)) - } - serverActionLastTime.Set(gtime.Millisecond()) - return nil -} - -// 检查当前服务进程的状态 -func (s *Server) checkActionStatus() error { - status := serverProcessStatus.Val() - if status > 0 { - switch status { - case gADMIN_ACTION_RESTARTING: return errors.New("server is restarting") - case gADMIN_ACTION_SHUTINGDOWN: return errors.New("server is shutting down") - } - } - return nil -} - -// 平滑重启:创建一个子进程,通过环境变量传参 -func forkReloadProcess(newExeFilePath...string) error { - path := os.Args[0] - if len(newExeFilePath) > 0 { - path = newExeFilePath[0] - } - p := gproc.NewProcess(path, os.Args, os.Environ()) - // 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口 - sfm := getServerFdMap() - // 将sfm中的fd按照子进程创建时的文件描述符顺序进行整理,以便子进程获取到正确的fd - for name, m := range sfm { - for fdk, fdv := range m { - if len(fdv) > 0 { - s := "" - for _, item := range strings.Split(fdv, ",") { - array := strings.Split(item, "#") - fd := uintptr(gconv.Uint(array[1])) - if fd > 0 { - s += fmt.Sprintf("%s#%d,", array[0], 3 + len(p.ExtraFiles)) - p.ExtraFiles = append(p.ExtraFiles, os.NewFile(fd, "")) - } else { - s += fmt.Sprintf("%s#%d,", array[0], 0) - } - } - sfm[name][fdk] = strings.TrimRight(s, ",") - } - } - } - buffer, _ := gjson.Encode(sfm) - p.Env = append(p.Env, gADMIN_ACTION_RELOAD_ENVKEY + "=" + string(buffer)) - if _, err := p.Start(); err != nil { - glog.Errorfln("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer)) - return err - } - return nil -} - -// 完整重启:创建一个新的子进程 -func forkRestartProcess(newExeFilePath...string) error { - path := os.Args[0] - if len(newExeFilePath) > 0 { - path = newExeFilePath[0] - } - // 去掉平滑重启的环境变量参数 - os.Unsetenv(gADMIN_ACTION_RELOAD_ENVKEY) - env := os.Environ() - env = append(env, gADMIN_ACTION_RESTART_ENVKEY + "=1") - p := gproc.NewProcess(path, os.Args, env) - if _, err := p.Start(); err != nil { - glog.Errorfln("%d: fork process failed, error:%s", gproc.Pid(), err.Error()) - return err - } - return nil -} - -// 获取所有Web Server的文件描述符map -func getServerFdMap() map[string]listenerFdMap { - sfm := make(map[string]listenerFdMap) - serverMapping.RLockFunc(func(m map[string]interface{}) { - for k, v := range m { - sfm[k] = v.(*Server).getListenerFdMap() + // 非终端信号下,异步1秒后再执行关闭, + // 目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了) + gtimer.SetTimeout(time.Second, func() { + // 只关闭当前的Web Server + for _, v := range s.servers { + v.close() } }) - return sfm -} - -// 二进制转换为FdMap -func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap { - sfm := make(map[string]listenerFdMap) - if len(buffer) > 0 { - j, _ := gjson.LoadContent(buffer, "json") - for k, _ := range j.ToMap() { - m := make(map[string]string) - for k, v := range j.GetMap(k) { - m[k] = gconv.String(v) - } - sfm[k] = m - } - } - return sfm -} - -// Web Server重启 -func restartWebServers(signal string, newExeFilePath...string) error { - serverProcessStatus.Set(gADMIN_ACTION_RESTARTING) - if runtime.GOOS == "windows" { - if len(signal) > 0 { - // 在终端信号下,立即执行重启操作 - forcedlyCloseWebServers() - forkRestartProcess(newExeFilePath...) - } else { - // 非终端信号下,异步1秒后再执行重启,目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了) - gtimer.SetTimeout(time.Second, func() { - forcedlyCloseWebServers() - forkRestartProcess(newExeFilePath...) - }) - } - } else { - if err := forkReloadProcess(newExeFilePath...); err != nil { - glog.Printfln("%d: server restarts failed", gproc.Pid()) - serverProcessStatus.Set(gADMIN_ACTION_NONE) - return err - } else { - if len(signal) > 0 { - glog.Printfln("%d: server restarting by signal: %s", gproc.Pid(), signal) - } else { - glog.Printfln("%d: server restarting by web admin", gproc.Pid()) - } - - } - } return nil } - -// Web Server关闭服务 -func shutdownWebServers(signal string) { - serverProcessStatus.Set(gADMIN_ACTION_SHUTINGDOWN) - if len(signal) > 0 { - glog.Printfln("%d: server shutting down by signal: %s", gproc.Pid(), signal) - // 在终端信号下,立即执行关闭操作 - forcedlyCloseWebServers() - doneChan <- struct{}{} - } else { - glog.Printfln("%d: server shutting down by web admin", gproc.Pid()) - // 非终端信号下,异步1秒后再执行关闭,目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了) - gtimer.SetTimeout(time.Second, func() { - forcedlyCloseWebServers() - doneChan <- struct{}{} - }) - } -} - -// 关优雅闭进程所有端口的Web Server服务 -// 注意,只是关闭Web Server服务,并不是退出进程 -func gracefulShutdownWebServers() { - serverMapping.RLockFunc(func(m map[string]interface{}) { - for _, v := range m { - for _, s := range v.(*Server).servers { - s.shutdown() - } - } - }) -} - -// 强制关闭进程所有端口的Web Server服务 -// 注意,只是关闭Web Server服务,并不是退出进程 -func forcedlyCloseWebServers() { - serverMapping.RLockFunc(func(m map[string]interface{}) { - for _, v := range m { - for _, s := range v.(*Server).servers { - s.close() - } - } - }) -} - -// 异步监听进程间消息 -func handleProcessMessage() { - for { - if msg := gproc.Receive(gADMIN_GPROC_COMM_GROUP); msg != nil { - if bytes.EqualFold(msg.Data, []byte("exit")) { - gracefulShutdownWebServers() - doneChan <- struct{}{} - return - } - } - } -} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_admin_process.go b/g/net/ghttp/ghttp_server_admin_process.go new file mode 100644 index 000000000..376935344 --- /dev/null +++ b/g/net/ghttp/ghttp_server_admin_process.go @@ -0,0 +1,269 @@ +// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf. +// pprof封装. + +package ghttp + +import ( + "bytes" + "errors" + "fmt" + "github.com/gogf/gf/g/container/gtype" + "github.com/gogf/gf/g/encoding/gjson" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/os/gproc" + "github.com/gogf/gf/g/os/gtime" + "github.com/gogf/gf/g/os/gtimer" + "github.com/gogf/gf/g/util/gconv" + "os" + "runtime" + "strings" + "sync" + "time" +) + +const ( + gADMIN_ACTION_INTERVAL_LIMIT = 2000 // (毫秒)服务开启后允许执行管理操作的间隔限制 + gADMIN_ACTION_NONE = 0 + gADMIN_ACTION_RESTARTING = 1 + gADMIN_ACTION_SHUTINGDOWN = 2 + gADMIN_ACTION_RELOAD_ENVKEY = "GF_SERVER_RELOAD" + gADMIN_ACTION_RESTART_ENVKEY = "GF_SERVER_RESTART" + gADMIN_GPROC_COMM_GROUP = "GF_GPROC_HTTP_SERVER" +) + +// 用于服务管理的对象 +type utilAdmin struct {} + +// (进程级别)用于Web Server管理操作的互斥锁,保证管理操作的原子性 +var serverActionLocker sync.Mutex + +// (进程级别)用于记录上一次操作的时间(毫秒) +var serverActionLastTime = gtype.NewInt64(gtime.Millisecond()) + +// 当前服务进程所处的互斥管理操作状态 +var serverProcessStatus = gtype.NewInt() + +// 重启Web Server,参数支持自定义重启的可执行文件路径,不传递时默认和原有可执行文件路径一致。 +// 针对*niux系统: 平滑重启 +// 针对windows : 完整重启 +func RestartAllServer(newExeFilePath...string) error { + serverActionLocker.Lock() + defer serverActionLocker.Unlock() + if err := checkProcessStatus(); err != nil { + return err + } + if err := checkActionFrequence(); err != nil { + return err + } + return restartWebServers("", newExeFilePath...) +} + +// 关闭所有的WebServer +func ShutdownAllServer() error { + serverActionLocker.Lock() + defer serverActionLocker.Unlock() + if err := checkProcessStatus(); err != nil { + return err + } + if err := checkActionFrequence(); err != nil { + return err + } + shutdownWebServers() + return nil +} + +// 检查当前服务进程的状态 +func checkProcessStatus() error { + status := serverProcessStatus.Val() + if status > 0 { + switch status { + case gADMIN_ACTION_RESTARTING: return errors.New("server is restarting") + case gADMIN_ACTION_SHUTINGDOWN: return errors.New("server is shutting down") + } + } + return nil +} + +// 检测当前操作的频繁度 +func checkActionFrequence() error { + interval := gtime.Millisecond() - serverActionLastTime.Val() + if interval < gADMIN_ACTION_INTERVAL_LIMIT { + return errors.New(fmt.Sprintf("too frequent action, please retry in %d ms", gADMIN_ACTION_INTERVAL_LIMIT - interval)) + } + serverActionLastTime.Set(gtime.Millisecond()) + return nil +} + +// 平滑重启:创建一个子进程,通过环境变量传参 +func forkReloadProcess(newExeFilePath...string) error { + path := os.Args[0] + if len(newExeFilePath) > 0 { + path = newExeFilePath[0] + } + p := gproc.NewProcess(path, os.Args, os.Environ()) + // 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口 + sfm := getServerFdMap() + // 将sfm中的fd按照子进程创建时的文件描述符顺序进行整理,以便子进程获取到正确的fd + for name, m := range sfm { + for fdk, fdv := range m { + if len(fdv) > 0 { + s := "" + for _, item := range strings.Split(fdv, ",") { + array := strings.Split(item, "#") + fd := uintptr(gconv.Uint(array[1])) + if fd > 0 { + s += fmt.Sprintf("%s#%d,", array[0], 3 + len(p.ExtraFiles)) + p.ExtraFiles = append(p.ExtraFiles, os.NewFile(fd, "")) + } else { + s += fmt.Sprintf("%s#%d,", array[0], 0) + } + } + sfm[name][fdk] = strings.TrimRight(s, ",") + } + } + } + buffer, _ := gjson.Encode(sfm) + p.Env = append(p.Env, gADMIN_ACTION_RELOAD_ENVKEY + "=" + string(buffer)) + if _, err := p.Start(); err != nil { + glog.Errorfln("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer)) + return err + } + return nil +} + +// 完整重启:创建一个新的子进程 +func forkRestartProcess(newExeFilePath...string) error { + path := os.Args[0] + if len(newExeFilePath) > 0 { + path = newExeFilePath[0] + } + // 去掉平滑重启的环境变量参数 + os.Unsetenv(gADMIN_ACTION_RELOAD_ENVKEY) + env := os.Environ() + env = append(env, gADMIN_ACTION_RESTART_ENVKEY + "=1") + p := gproc.NewProcess(path, os.Args, env) + if _, err := p.Start(); err != nil { + glog.Errorfln("%d: fork process failed, error:%s", gproc.Pid(), err.Error()) + return err + } + return nil +} + +// 获取所有Web Server的文件描述符map +func getServerFdMap() map[string]listenerFdMap { + sfm := make(map[string]listenerFdMap) + serverMapping.RLockFunc(func(m map[string]interface{}) { + for k, v := range m { + sfm[k] = v.(*Server).getListenerFdMap() + } + }) + return sfm +} + +// 二进制转换为FdMap +func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap { + sfm := make(map[string]listenerFdMap) + if len(buffer) > 0 { + j, _ := gjson.LoadContent(buffer, "json") + for k, _ := range j.ToMap() { + m := make(map[string]string) + for k, v := range j.GetMap(k) { + m[k] = gconv.String(v) + } + sfm[k] = m + } + } + return sfm +} + +// Web Server重启 +func restartWebServers(signal string, newExeFilePath...string) error { + serverProcessStatus.Set(gADMIN_ACTION_RESTARTING) + if runtime.GOOS == "windows" { + if len(signal) > 0 { + // 在终端信号下,立即执行重启操作 + forceCloseWebServers() + forkRestartProcess(newExeFilePath...) + } else { + // 非终端信号下,异步1秒后再执行重启,目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了) + gtimer.SetTimeout(time.Second, func() { + forceCloseWebServers() + forkRestartProcess(newExeFilePath...) + }) + } + } else { + if err := forkReloadProcess(newExeFilePath...); err != nil { + glog.Printfln("%d: server restarts failed", gproc.Pid()) + serverProcessStatus.Set(gADMIN_ACTION_NONE) + return err + } else { + if len(signal) > 0 { + glog.Printfln("%d: server restarting by signal: %s", gproc.Pid(), signal) + } else { + glog.Printfln("%d: server restarting by web admin", gproc.Pid()) + } + + } + } + return nil +} + +// 关闭所有Web Server +func shutdownWebServers(signal...string) { + serverProcessStatus.Set(gADMIN_ACTION_SHUTINGDOWN) + if len(signal) > 0 { + glog.Printfln("%d: server shutting down by signal: %s", gproc.Pid(), signal[0]) + // 在终端信号下,立即执行关闭操作 + forceCloseWebServers() + allDoneChan <- struct{}{} + } else { + glog.Printfln("%d: server shutting down by api", gproc.Pid()) + // 非终端信号下,异步1秒后再执行关闭, + // 目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了) + gtimer.SetTimeout(time.Second, func() { + forceCloseWebServers() + allDoneChan <- struct{}{} + }) + } +} + +// 关优雅闭进程所有端口的Web Server服务 +// 注意,只是关闭Web Server服务,并不是退出进程 +func gracefulShutdownWebServers() { + serverMapping.RLockFunc(func(m map[string]interface{}) { + for _, v := range m { + for _, s := range v.(*Server).servers { + s.shutdown() + } + } + }) +} + +// 强制关闭进程所有端口的Web Server服务 +// 注意,只是关闭Web Server服务,并不是退出进程 +func forceCloseWebServers() { + serverMapping.RLockFunc(func(m map[string]interface{}) { + for _, v := range m { + for _, s := range v.(*Server).servers { + s.close() + } + } + }) +} + +// 异步监听进程间消息 +func handleProcessMessage() { + for { + if msg := gproc.Receive(gADMIN_GPROC_COMM_GROUP); msg != nil { + if bytes.EqualFold(msg.Data, []byte("exit")) { + gracefulShutdownWebServers() + allDoneChan <- struct{}{} + return + } + } + } +} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_unit_cookie_test.go b/g/net/ghttp/ghttp_unit_cookie_test.go index 8dc323cdb..ed74c8658 100644 --- a/g/net/ghttp/ghttp_unit_cookie_test.go +++ b/g/net/ghttp/ghttp_unit_cookie_test.go @@ -8,16 +8,17 @@ package ghttp_test import ( + "fmt" "github.com/gogf/gf/g" "github.com/gogf/gf/g/net/ghttp" - "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/test/gtest" "testing" "time" ) func Test_Cookie(t *testing.T) { - s := g.Server(gtime.Nanosecond()) + p := ports.PopRand() + s := g.Server(p) s.BindHandler("/set", func(r *ghttp.Request){ r.Cookie.Set(r.Get("k"), r.Get("v")) }) @@ -28,19 +29,17 @@ func Test_Cookie(t *testing.T) { s.BindHandler("/remove", func(r *ghttp.Request){ r.Cookie.Remove(r.Get("k")) }) - s.SetPort(8500) + s.SetPort(p) s.SetDumpRouteMap(false) - go s.Run() - defer func() { - s.Shutdown() - time.Sleep(time.Second) - }() + s.Start() + defer s.Shutdown() + // 等待启动完成 time.Sleep(time.Second) gtest.Case(t, func() { client := ghttp.NewClient() client.SetBrowserMode(true) - client.SetPrefix("http://127.0.0.1:8500") + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) r1, e1 := client.Get("/set?k=key1&v=100") if r1 != nil { defer r1.Close() diff --git a/g/net/ghttp/ghttp_unit_init_test.go b/g/net/ghttp/ghttp_unit_init_test.go new file mode 100644 index 000000000..e7bd6c78f --- /dev/null +++ b/g/net/ghttp/ghttp_unit_init_test.go @@ -0,0 +1,25 @@ +// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf. + +// 测试初始化 +package ghttp_test + +import ( + "github.com/gogf/gf/g/container/garray" +) + +var ( + // 用于测试的端口数组,随机获取 + ports = garray.NewIntArray() +) + +func init() { + for i := 8000; i <= 8100; i++ { + ports.Append(i) + } +} + + diff --git a/g/net/ghttp/ghttp_unit_param_test.go b/g/net/ghttp/ghttp_unit_param_test.go index 2816e647e..c510242df 100644 --- a/g/net/ghttp/ghttp_unit_param_test.go +++ b/g/net/ghttp/ghttp_unit_param_test.go @@ -8,9 +8,9 @@ package ghttp_test import ( + "fmt" "github.com/gogf/gf/g" "github.com/gogf/gf/g/net/ghttp" - "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/test/gtest" "testing" "time" @@ -23,7 +23,8 @@ func Test_Params_Basic(t *testing.T) { Pass1 string `params:"password1"` Pass2 string `params:"password2"` } - s := g.Server(gtime.Nanosecond()) + p := ports.PopRand() + s := g.Server(p) s.BindHandler("/get", func(r *ghttp.Request){ if r.GetQuery("slice") != nil { r.Response.Write(r.GetQuery("slice")) @@ -96,18 +97,16 @@ func Test_Params_Basic(t *testing.T) { r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2) } }) - s.SetPort(8400) + s.SetPort(p) s.SetDumpRouteMap(false) - go s.Run() - defer func() { - s.Shutdown() - time.Sleep(time.Second) - }() + s.Start() + defer s.Shutdown() + // 等待启动完成 time.Sleep(time.Second) gtest.Case(t, func() { client := ghttp.NewClient() - client.SetPrefix("http://127.0.0.1:8400") + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) // GET gtest.Assert(client.GetContent("/get", "slice=1&slice=2"), `["1","2"]`) gtest.Assert(client.GetContent("/get", "bool=1"), `true`) diff --git a/g/net/ghttp/ghttp_unit_router_basic_test.go b/g/net/ghttp/ghttp_unit_router_basic_test.go index 5b56bd6e6..cbbdc5688 100644 --- a/g/net/ghttp/ghttp_unit_router_basic_test.go +++ b/g/net/ghttp/ghttp_unit_router_basic_test.go @@ -8,18 +8,18 @@ package ghttp_test import ( + "fmt" "github.com/gogf/gf/g" "github.com/gogf/gf/g/net/ghttp" - "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/test/gtest" "testing" "time" ) - // 基本路由功能测试 func Test_Router_Basic(t *testing.T) { - s := g.Server(gtime.Nanosecond()) + p := ports.PopRand() + s := g.Server(p) s.BindHandler("/:name", func(r *ghttp.Request){ r.Response.Write("/:name") }) @@ -35,19 +35,16 @@ func Test_Router_Basic(t *testing.T) { s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){ r.Response.Write(r.Get("field")) }) - s.SetPort(8100) + s.SetPort(p) s.SetDumpRouteMap(false) - go s.Run() - defer func() { - s.Shutdown() - time.Sleep(time.Second) - }() + s.Start() + defer s.Shutdown() + // 等待启动完成 time.Sleep(time.Second) gtest.Case(t, func() { client := ghttp.NewClient() - client.SetPrefix("http://127.0.0.1:8100") - + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) gtest.Assert(client.GetContent("/john"), "") gtest.Assert(client.GetContent("/john/update"), "john") gtest.Assert(client.GetContent("/john/edit"), "edit") @@ -57,25 +54,24 @@ func Test_Router_Basic(t *testing.T) { // 测试HTTP Method注册. func Test_Router_Method(t *testing.T) { - s := g.Server(gtime.Nanosecond()) + p := ports.PopRand() + s := g.Server(p) s.BindHandler("GET:/get", func(r *ghttp.Request){ }) s.BindHandler("POST:/post", func(r *ghttp.Request){ }) - s.SetPort(8105) + s.SetPort(p) s.SetDumpRouteMap(false) - go s.Run() - defer func() { - s.Shutdown() - time.Sleep(time.Second) - }() + s.Start() + defer s.Shutdown() + // 等待启动完成 time.Sleep(time.Second) gtest.Case(t, func() { client := ghttp.NewClient() - client.SetPrefix("http://127.0.0.1:8105") + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) resp1, err := client.Get("/get") defer resp1.Close() @@ -101,7 +97,8 @@ func Test_Router_Method(t *testing.T) { // 测试状态返回. func Test_Router_Status(t *testing.T) { - s := g.Server(gtime.Nanosecond()) + p := ports.PopRand() + s := g.Server(p) s.BindHandler("/200", func(r *ghttp.Request){ r.Response.WriteStatus(200) }) @@ -114,18 +111,16 @@ func Test_Router_Status(t *testing.T) { s.BindHandler("/500", func(r *ghttp.Request){ r.Response.WriteStatus(500) }) - s.SetPort(8110) + s.SetPort(p) s.SetDumpRouteMap(false) - go s.Run() - defer func() { - s.Shutdown() - time.Sleep(time.Second) - }() + s.Start() + defer s.Shutdown() + // 等待启动完成 time.Sleep(time.Second) gtest.Case(t, func() { client := ghttp.NewClient() - client.SetPrefix("http://127.0.0.1:8110") + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) resp1, err := client.Get("/200") defer resp1.Close() @@ -151,25 +146,24 @@ func Test_Router_Status(t *testing.T) { // 自定义状态码处理. func Test_Router_CustomStatusHandler(t *testing.T) { - s := g.Server(gtime.Nanosecond()) + p := ports.PopRand() + s := g.Server(p) s.BindHandler("/", func(r *ghttp.Request){ r.Response.Write("hello") }) s.BindStatusHandler(404, func(r *ghttp.Request){ r.Response.Write("404 page") }) - s.SetPort(8120) + s.SetPort(p) s.SetDumpRouteMap(false) - go s.Run() - defer func() { - s.Shutdown() - time.Sleep(time.Second) - }() + s.Start() + defer s.Shutdown() + // 等待启动完成 time.Sleep(time.Second) gtest.Case(t, func() { client := ghttp.NewClient() - client.SetPrefix("http://127.0.0.1:8120") + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) gtest.Assert(client.GetContent("/"), "hello") resp, err := client.Get("/ThisDoesNotExist") @@ -182,22 +176,21 @@ func Test_Router_CustomStatusHandler(t *testing.T) { // 测试不存在的路由. func Test_Router_404(t *testing.T) { - s := g.Server(gtime.Nanosecond()) + p := ports.PopRand() + s := g.Server(p) s.BindHandler("/", func(r *ghttp.Request){ r.Response.Write("hello") }) - s.SetPort(8130) + s.SetPort(p) s.SetDumpRouteMap(false) - go s.Run() - defer func() { - s.Shutdown() - time.Sleep(time.Second) - }() + s.Start() + defer s.Shutdown() + // 等待启动完成 time.Sleep(time.Second) gtest.Case(t, func() { client := ghttp.NewClient() - client.SetPrefix("http://127.0.0.1:8130") + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) gtest.Assert(client.GetContent("/"), "hello") resp, err := client.Get("/ThisDoesNotExist") diff --git a/g/net/ghttp/ghttp_unit_router_controller_test.go b/g/net/ghttp/ghttp_unit_router_controller_test.go new file mode 100644 index 000000000..3f862a85a --- /dev/null +++ b/g/net/ghttp/ghttp_unit_router_controller_test.go @@ -0,0 +1,65 @@ +// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf. + +package ghttp_test + +import ( + "fmt" + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/frame/gmvc" + "github.com/gogf/gf/g/net/ghttp" + "github.com/gogf/gf/g/test/gtest" + "testing" + "time" +) + +// 控制器 +type ControllerBasic struct { + gmvc.Controller +} + +func (c *ControllerBasic) Init(r *ghttp.Request) { + c.Controller.Init(r) + c.Response.Write("1") +} + +func (c *ControllerBasic) Shut() { + c.Response.Write("2") +} + +func (c *ControllerBasic) Index() { + c.Response.Write("Controller Index") +} + +func (c *ControllerBasic) Show() { + c.Response.Write("Controller Show") +} + + +// 控制器注册测试 +func Test_Router_Controller(t *testing.T) { + p := ports.PopRand() + s := g.Server(p) + s.BindController("/", new(ControllerBasic)) + s.SetPort(p) + s.SetDumpRouteMap(false) + s.Start() + defer s.Shutdown() + + // 等待启动完成 + time.Sleep(time.Second) + gtest.Case(t, func() { + client := ghttp.NewClient() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + gtest.Assert(client.GetContent("/"), "1Controller Index2") + gtest.Assert(client.GetContent("/init"), "Not Found") + gtest.Assert(client.GetContent("/shut"), "Not Found") + gtest.Assert(client.GetContent("/index"), "1Controller Index2") + gtest.Assert(client.GetContent("/show"), "1Controller Show2") + gtest.Assert(client.GetContent("/none-exist"), "Not Found") + }) +} diff --git a/g/net/ghttp/ghttp_unit_router_group_test.go b/g/net/ghttp/ghttp_unit_router_group_test.go index 83c53f972..b355a78c8 100644 --- a/g/net/ghttp/ghttp_unit_router_group_test.go +++ b/g/net/ghttp/ghttp_unit_router_group_test.go @@ -8,10 +8,10 @@ package ghttp_test import ( + "fmt" "github.com/gogf/gf/g" "github.com/gogf/gf/g/frame/gmvc" "github.com/gogf/gf/g/net/ghttp" - "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/test/gtest" "testing" "time" @@ -20,6 +20,14 @@ import ( // 执行对象 type Object struct {} +func (o *Object) Init(r *ghttp.Request) { + r.Response.Write("1") +} + +func (o *Object) Shut(r *ghttp.Request) { + r.Response.Write("2") +} + func (o *Object) Index(r *ghttp.Request) { r.Response.Write("Object Index") } @@ -37,6 +45,15 @@ type Controller struct { gmvc.Controller } +func (c *Controller) Init(r *ghttp.Request) { + c.Controller.Init(r) + c.Response.Write("1") +} + +func (c *Controller) Shut() { + c.Response.Write("2") +} + func (c *Controller) Index() { c.Response.Write("Controller Index") } @@ -54,7 +71,8 @@ func Handler(r *ghttp.Request) { } func Test_Router_Group1(t *testing.T) { - s := g.Server(gtime.Nanosecond()) + p := ports.PopRand() + s := g.Server(p) obj := new(Object) ctl := new(Controller) // 分组路由方法注册 @@ -66,35 +84,33 @@ func Test_Router_Group1(t *testing.T) { g.ALL ("/obj", obj) g.GET ("/obj/my-show", obj, "Show") g.REST("/obj/rest", obj) - s.SetPort(8200) + s.SetPort(p) s.SetDumpRouteMap(false) - go s.Run() - defer func() { - s.Shutdown() - time.Sleep(time.Second) - }() + s.Start() + defer s.Shutdown() + time.Sleep(time.Second) gtest.Case(t, func() { client := ghttp.NewClient() - client.SetPrefix("http://127.0.0.1:8200") + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) gtest.Assert(client.GetContent ("/api/handler"), "Handler") - gtest.Assert(client.GetContent ("/api/ctl"), "Controller Index") - gtest.Assert(client.GetContent ("/api/ctl/"), "Controller Index") - gtest.Assert(client.GetContent ("/api/ctl/index"), "Controller Index") - gtest.Assert(client.GetContent ("/api/ctl/my-show"), "Controller Show") - gtest.Assert(client.GetContent ("/api/ctl/post"), "Controller REST Post") - gtest.Assert(client.GetContent ("/api/ctl/show"), "Controller Show") - gtest.Assert(client.PostContent("/api/ctl/rest"), "Controller REST Post") + gtest.Assert(client.GetContent ("/api/ctl"), "1Controller Index2") + gtest.Assert(client.GetContent ("/api/ctl/"), "1Controller Index2") + gtest.Assert(client.GetContent ("/api/ctl/index"), "1Controller Index2") + gtest.Assert(client.GetContent ("/api/ctl/my-show"), "1Controller Show2") + gtest.Assert(client.GetContent ("/api/ctl/post"), "1Controller REST Post2") + gtest.Assert(client.GetContent ("/api/ctl/show"), "1Controller Show2") + gtest.Assert(client.PostContent("/api/ctl/rest"), "1Controller REST Post2") - gtest.Assert(client.GetContent ("/api/obj"), "Object Index") - gtest.Assert(client.GetContent ("/api/obj/"), "Object Index") - gtest.Assert(client.GetContent ("/api/obj/index"), "Object Index") - gtest.Assert(client.GetContent ("/api/obj/delete"), "Object REST Delete") - gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show") - gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show") - gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete") + gtest.Assert(client.GetContent ("/api/obj"), "1Object Index2") + gtest.Assert(client.GetContent ("/api/obj/"), "1Object Index2") + gtest.Assert(client.GetContent ("/api/obj/index"), "1Object Index2") + gtest.Assert(client.GetContent ("/api/obj/delete"), "1Object REST Delete2") + gtest.Assert(client.GetContent ("/api/obj/my-show"), "1Object Show2") + gtest.Assert(client.GetContent ("/api/obj/show"), "1Object Show2") + gtest.Assert(client.DeleteContent("/api/obj/rest"), "1Object REST Delete2") // 测试404 resp, err := client.Get("/ThisDoesNotExist") @@ -105,7 +121,8 @@ func Test_Router_Group1(t *testing.T) { } func Test_Router_Group2(t *testing.T) { - s := g.Server(gtime.Nanosecond()) + p := ports.PopRand() + s := g.Server(p) obj := new(Object) ctl := new(Controller) // 分组路由批量注册 @@ -118,29 +135,27 @@ func Test_Router_Group2(t *testing.T) { {"GET", "/obj/my-show", obj, "Show"}, {"REST", "/obj/rest", obj}, }) - s.SetPort(8300) + s.SetPort(p) s.SetDumpRouteMap(false) - go s.Run() - defer func() { - s.Shutdown() - time.Sleep(time.Second) - }() + s.Start() + defer s.Shutdown() + time.Sleep(time.Second) gtest.Case(t, func() { client := ghttp.NewClient() - client.SetPrefix("http://127.0.0.1:8300") + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) gtest.Assert(client.GetContent ("/api/handler"), "Handler") - gtest.Assert(client.GetContent ("/api/ctl/my-show"), "Controller Show") - gtest.Assert(client.GetContent ("/api/ctl/post"), "Controller REST Post") - gtest.Assert(client.GetContent ("/api/ctl/show"), "Controller Show") - gtest.Assert(client.PostContent("/api/ctl/rest"), "Controller REST Post") + gtest.Assert(client.GetContent ("/api/ctl/my-show"), "1Controller Show2") + gtest.Assert(client.GetContent ("/api/ctl/post"), "1Controller REST Post2") + gtest.Assert(client.GetContent ("/api/ctl/show"), "1Controller Show2") + gtest.Assert(client.PostContent("/api/ctl/rest"), "1Controller REST Post2") - gtest.Assert(client.GetContent ("/api/obj/delete"), "Object REST Delete") - gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show") - gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show") - gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete") + gtest.Assert(client.GetContent ("/api/obj/delete"), "1Object REST Delete2") + gtest.Assert(client.GetContent ("/api/obj/my-show"), "1Object Show2") + gtest.Assert(client.GetContent ("/api/obj/show"), "1Object Show2") + gtest.Assert(client.DeleteContent("/api/obj/rest"), "1Object REST Delete2") // 测试404 resp, err := client.Get("/ThisDoesNotExist") diff --git a/g/net/ghttp/ghttp_unit_router_object_test.go b/g/net/ghttp/ghttp_unit_router_object_test.go new file mode 100644 index 000000000..7852f0fa7 --- /dev/null +++ b/g/net/ghttp/ghttp_unit_router_object_test.go @@ -0,0 +1,60 @@ +// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf. + +package ghttp_test + +import ( + "fmt" + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/net/ghttp" + "github.com/gogf/gf/g/test/gtest" + "testing" + "time" +) + +// 执行对象 +type ObjectBasic struct {} + +func (o *ObjectBasic) Init(r *ghttp.Request) { + r.Response.Write("1") +} + +func (o *ObjectBasic) Shut(r *ghttp.Request) { + r.Response.Write("2") +} + +func (o *ObjectBasic) Index(r *ghttp.Request) { + r.Response.Write("Object Index") +} + +func (o *ObjectBasic) Show(r *ghttp.Request) { + r.Response.Write("Object Show") +} + +// 执行对象注册 +func Test_Router_Object(t *testing.T) { + p := ports.PopRand() + s := g.Server(p) + s.BindObject("/", new(ObjectBasic)) + s.SetPort(p) + s.SetDumpRouteMap(false) + s.Start() + defer s.Shutdown() + + // 等待启动完成 + time.Sleep(time.Second) + gtest.Case(t, func() { + client := ghttp.NewClient() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + gtest.Assert(client.GetContent("/"), "1Object Index2") + gtest.Assert(client.GetContent("/init"), "Not Found") + gtest.Assert(client.GetContent("/shut"), "Not Found") + gtest.Assert(client.GetContent("/index"), "1Object Index2") + gtest.Assert(client.GetContent("/show"), "1Object Show2") + gtest.Assert(client.GetContent("/none-exist"), "Not Found") + }) +} diff --git a/g/net/ghttp/ghttp_unit_session_test.go b/g/net/ghttp/ghttp_unit_session_test.go index 6ceffd80e..9880f3796 100644 --- a/g/net/ghttp/ghttp_unit_session_test.go +++ b/g/net/ghttp/ghttp_unit_session_test.go @@ -8,16 +8,17 @@ package ghttp_test import ( + "fmt" "github.com/gogf/gf/g" "github.com/gogf/gf/g/net/ghttp" - "github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/test/gtest" "testing" "time" ) func Test_Session(t *testing.T) { - s := g.Server(gtime.Nanosecond()) + p := ports.PopRand() + s := g.Server(p) s.BindHandler("/set", func(r *ghttp.Request){ r.Session.Set(r.Get("k"), r.Get("v")) }) @@ -30,19 +31,17 @@ func Test_Session(t *testing.T) { s.BindHandler("/clear", func(r *ghttp.Request){ r.Session.Clear() }) - s.SetPort(8600) + s.SetPort(p) s.SetDumpRouteMap(false) - go s.Run() - defer func() { - s.Shutdown() - time.Sleep(time.Second) - }() + s.Start() + defer s.Shutdown() + // 等待启动完成 time.Sleep(time.Second) gtest.Case(t, func() { client := ghttp.NewClient() client.SetBrowserMode(true) - client.SetPrefix("http://127.0.0.1:8600") + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) r1, e1 := client.Get("/set?k=key1&v=100") if r1 != nil { defer r1.Close() diff --git a/geg/net/ghttp/server/reload/multi_port.go b/geg/net/ghttp/server/reload/multi_port.go deleted file mode 100644 index 707127027..000000000 --- a/geg/net/ghttp/server/reload/multi_port.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "github.com/gogf/gf/g" - "github.com/gogf/gf/g/net/ghttp" -) - -func main() { - s := g.Server() - s.BindHandler("/", func(r *ghttp.Request){ - r.Response.Writeln("hello") - }) - s.BindHandler("/restart", func(r *ghttp.Request){ - r.Response.Writeln("restart server") - r.Server.Restart() - }) - s.BindHandler("/shutdown", func(r *ghttp.Request){ - r.Response.Writeln("shutdown server") - r.Server.Shutdown() - }) - s.SetPort(8199, 8200) - s.Run() -} \ No newline at end of file