From d455805a0a4590894f290419d8f03c6f062091f0 Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Wed, 9 Sep 2020 22:55:18 +0800 Subject: [PATCH] Multiple server refactor --- pkg/adapter/app.go | 12 +- pkg/server/web/beego.go | 68 ++--- pkg/server/web/config.go | 26 +- pkg/server/web/error.go | 4 +- pkg/server/web/router.go | 79 ++---- pkg/server/web/router_test.go | 2 +- pkg/server/web/{app.go => server.go} | 379 +++++++++++++++++++-------- pkg/server/web/server_test.go | 31 +++ pkg/server/web/template.go | 8 +- 9 files changed, 382 insertions(+), 227 deletions(-) rename pkg/server/web/{app.go => server.go} (54%) create mode 100644 pkg/server/web/server_test.go diff --git a/pkg/adapter/app.go b/pkg/adapter/app.go index c1046c79..10ffa96a 100644 --- a/pkg/adapter/app.go +++ b/pkg/adapter/app.go @@ -33,11 +33,11 @@ func init() { } // App defines beego application with a new PatternServeMux. -type App web.App +type App web.HttpServer // NewApp returns a new beego application. func NewApp() *App { - return (*App)(web.NewApp()) + return (*App)(web.NewHttpSever()) } // MiddleWare function for http.Handler @@ -46,7 +46,7 @@ type MiddleWare web.MiddleWare // Run beego application. func (app *App) Run(mws ...MiddleWare) { newMws := oldMiddlewareToNew(mws) - (*web.App)(app).Run(newMws...) + (*web.HttpServer)(app).Run("", newMws...) } func oldMiddlewareToNew(mws []MiddleWare) []web.MiddleWare { @@ -58,7 +58,7 @@ func oldMiddlewareToNew(mws []MiddleWare) []web.MiddleWare { } // Router adds a patterned controller handler to BeeApp. -// it's an alias method of App.Router. +// it's an alias method of HttpServer.Router. // usage: // simple router // beego.Router("/admin", &admin.UserController{}) @@ -138,7 +138,7 @@ func RESTRouter(rootpath string, c ControllerInterface) *App { } // AutoRouter adds defined controller handler to BeeApp. -// it's same to App.AutoRouter. +// it's same to HttpServer.AutoRouter. // if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page, // visit the url /main/list to exec List function or /main/page to exec Page function. func AutoRouter(c ControllerInterface) *App { @@ -146,7 +146,7 @@ func AutoRouter(c ControllerInterface) *App { } // AutoPrefix adds controller handler to BeeApp with prefix. -// it's same to App.AutoRouterWithPrefix. +// it's same to HttpServer.AutoRouterWithPrefix. // if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page, // visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function. func AutoPrefix(prefix string, c ControllerInterface) *App { diff --git a/pkg/server/web/beego.go b/pkg/server/web/beego.go index 42bfc8be..14e51a94 100644 --- a/pkg/server/web/beego.go +++ b/pkg/server/web/beego.go @@ -17,8 +17,7 @@ package web import ( "os" "path/filepath" - "strconv" - "strings" + "sync" ) const ( @@ -35,7 +34,7 @@ type M map[string]interface{} type hookfunc func() error var ( - hooks = make([]hookfunc, 0) //hook function slice to store the hookfunc + hooks = make([]hookfunc, 0) // hook function slice to store the hookfunc ) // AddAPPStartHook is used to register the hookfunc @@ -52,56 +51,39 @@ func AddAPPStartHook(hf ...hookfunc) { // beego.Run("127.0.0.1:8089") func Run(params ...string) { - initBeforeHTTPRun() - if len(params) > 0 && params[0] != "" { - strs := strings.Split(params[0], ":") - if len(strs) > 0 && strs[0] != "" { - BConfig.Listen.HTTPAddr = strs[0] - } - if len(strs) > 1 && strs[1] != "" { - BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) - } - - BConfig.Listen.Domains = params + BeeApp.Run(params[0]) } - - BeeApp.Run() + BeeApp.Run("") } // RunWithMiddleWares Run beego application with middlewares. func RunWithMiddleWares(addr string, mws ...MiddleWare) { - initBeforeHTTPRun() - - strs := strings.Split(addr, ":") - if len(strs) > 0 && strs[0] != "" { - BConfig.Listen.HTTPAddr = strs[0] - BConfig.Listen.Domains = []string{strs[0]} - } - if len(strs) > 1 && strs[1] != "" { - BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) - } - - BeeApp.Run(mws...) + BeeApp.Run(addr, mws...) } -func initBeforeHTTPRun() { - //init hooks - AddAPPStartHook( - registerMime, - registerDefaultErrorHandler, - registerSession, - registerTemplate, - registerAdmin, - registerGzip, - registerCommentRouter, - ) +var initHttpOnce sync.Once - for _, hk := range hooks { - if err := hk(); err != nil { - panic(err) +// TODO move to module init function +func initBeforeHTTPRun() { + initHttpOnce.Do(func() { + // init hooks + AddAPPStartHook( + registerMime, + registerDefaultErrorHandler, + registerSession, + registerTemplate, + registerAdmin, + registerGzip, + registerCommentRouter, + ) + + for _, hk := range hooks { + if err := hk(); err != nil { + panic(err) + } } - } + }) } // TestBeegoInit is for test package init diff --git a/pkg/server/web/config.go b/pkg/server/web/config.go index 309f9b87..add81b8c 100644 --- a/pkg/server/web/config.go +++ b/pkg/server/web/config.go @@ -34,6 +34,7 @@ import ( ) // Config is the main struct for BConfig +// TODO after supporting multiple servers, remove common config to somewhere else type Config struct { AppName string // Application name RunMode string // Running Mode: dev | prod @@ -168,15 +169,15 @@ func init() { } } -func recoverPanic(ctx *context.Context) { +func (cfg *Config) defaultRecoverPanic(ctx *context.Context) { if err := recover(); err != nil { if err == ErrAbort { return } - if !BConfig.RecoverPanic { + if !cfg.RecoverPanic { panic(err) } - if BConfig.EnableErrorsShow { + if cfg.EnableErrorsShow { if _, ok := ErrorMaps[fmt.Sprint(err)]; ok { exception(fmt.Sprint(err), ctx) return @@ -193,7 +194,7 @@ func recoverPanic(ctx *context.Context) { logs.Critical(fmt.Sprintf("%s:%d", file, line)) stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line)) } - if BConfig.RunMode == DEV && BConfig.EnableErrorsRender { + if cfg.RunMode == DEV && cfg.EnableErrorsRender { showErr(err, ctx, stack) } if ctx.Output.Status != 0 { @@ -205,18 +206,18 @@ func recoverPanic(ctx *context.Context) { } func newBConfig() *Config { - return &Config{ + res := &Config{ AppName: "beego", RunMode: PROD, RouterCaseSensitive: true, ServerName: "beegoServer:" + pkg.VERSION, RecoverPanic: true, - RecoverFunc: recoverPanic, - CopyRequestBody: false, - EnableGzip: false, - MaxMemory: 1 << 26, // 64MB - EnableErrorsShow: true, - EnableErrorsRender: true, + + CopyRequestBody: false, + EnableGzip: false, + MaxMemory: 1 << 26, // 64MB + EnableErrorsShow: true, + EnableErrorsRender: true, Listen: Listen{ Graceful: false, ServerTimeOut: 0, @@ -279,6 +280,9 @@ func newBConfig() *Config { Outputs: map[string]string{"console": ""}, }, } + + res.RecoverFunc = res.defaultRecoverPanic + return res } // now only support ini, next will support json. diff --git a/pkg/server/web/error.go b/pkg/server/web/error.go index 69e7ff02..a005c110 100644 --- a/pkg/server/web/error.go +++ b/pkg/server/web/error.go @@ -389,7 +389,7 @@ func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errCont // usage: // beego.ErrorHandler("404",NotFound) // beego.ErrorHandler("500",InternalServerError) -func ErrorHandler(code string, h http.HandlerFunc) *App { +func ErrorHandler(code string, h http.HandlerFunc) *HttpServer { ErrorMaps[code] = &errorInfo{ errorType: errorTypeHandler, handler: h, @@ -401,7 +401,7 @@ func ErrorHandler(code string, h http.HandlerFunc) *App { // ErrorController registers ControllerInterface to each http err code string. // usage: // beego.ErrorController(&controllers.ErrorController{}) -func ErrorController(c ControllerInterface) *App { +func ErrorController(c ControllerInterface) *HttpServer { reflectVal := reflect.ValueOf(c) rt := reflectVal.Type() ct := reflect.Indirect(reflectVal).Type() diff --git a/pkg/server/web/router.go b/pkg/server/web/router.go index 3dd19a6f..1a0183bd 100644 --- a/pkg/server/web/router.go +++ b/pkg/server/web/router.go @@ -135,10 +135,18 @@ type ControllerRegister struct { // the filter created by FilterChain chainRoot *FilterRouter + + cfg *Config } // NewControllerRegister returns a new ControllerRegister. +// Usually you should not use this method +// please use NewControllerRegisterWithCfg func NewControllerRegister() *ControllerRegister { + return NewControllerRegisterWithCfg(BeeApp.Cfg) +} + +func NewControllerRegisterWithCfg(cfg *Config) *ControllerRegister { res := &ControllerRegister{ routers: make(map[string]*Tree), policies: make(map[string]*Tree), @@ -240,7 +248,7 @@ func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInt } func (p *ControllerRegister) addToRouter(method, pattern string, r *ControllerInfo) { - if !BConfig.RouterCaseSensitive { + if !p.cfg.RouterCaseSensitive { pattern = strings.ToLower(pattern) } if t, ok := p.routers[method]; ok { @@ -453,7 +461,7 @@ func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface) // 1. setting the returnOnOutput value (false allows multiple filters to execute) // 2. determining whether or not params need to be reset. func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt) error { - opts = append(opts, WithCaseSensitive(BConfig.RouterCaseSensitive)) + opts = append(opts, WithCaseSensitive(p.cfg.RouterCaseSensitive)) mr := newFilterRouter(pattern, filter, opts...) return p.insertFilterRouter(pos, mr) } @@ -472,7 +480,7 @@ func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter Filter func (p *ControllerRegister) InsertFilterChain(pattern string, chain FilterChain, opts ...FilterOpt) { root := p.chainRoot filterFunc := chain(root.filterFunc) - opts = append(opts, WithCaseSensitive(BConfig.RouterCaseSensitive)) + opts = append(opts, WithCaseSensitive(p.cfg.RouterCaseSensitive)) p.chainRoot = newFilterRouter(pattern, filterFunc, opts...) p.chainRoot.next = root @@ -669,14 +677,14 @@ func (p *ControllerRegister) serveHttp(ctx *beecontext.Context) { isRunnable bool ) - if BConfig.RecoverFunc != nil { - defer BConfig.RecoverFunc(ctx) + if p.cfg.RecoverFunc != nil { + defer p.cfg.RecoverFunc(ctx) } - ctx.Output.EnableGzip = BConfig.EnableGzip + ctx.Output.EnableGzip = p.cfg.EnableGzip - if BConfig.RunMode == DEV { - ctx.Output.Header("Server", BConfig.ServerName) + if p.cfg.RunMode == DEV { + ctx.Output.Header("Server", p.cfg.ServerName) } urlPath := p.getUrlPath(ctx) @@ -700,20 +708,20 @@ func (p *ControllerRegister) serveHttp(ctx *beecontext.Context) { } if r.Method != http.MethodGet && r.Method != http.MethodHead { - if BConfig.CopyRequestBody && !ctx.Input.IsUpload() { + if p.cfg.CopyRequestBody && !ctx.Input.IsUpload() { // connection will close if the incoming data are larger (RFC 7231, 6.5.11) - if r.ContentLength > BConfig.MaxMemory { + if r.ContentLength > p.cfg.MaxMemory { logs.Error(errors.New("payload too large")) exception("413", ctx) goto Admin } - ctx.Input.CopyBody(BConfig.MaxMemory) + ctx.Input.CopyBody(p.cfg.MaxMemory) } - ctx.Input.ParseFormOrMulitForm(BConfig.MaxMemory) + ctx.Input.ParseFormOrMulitForm(p.cfg.MaxMemory) } // session init - if BConfig.WebConfig.Session.SessionOn { + if p.cfg.WebConfig.Session.SessionOn { var err error ctx.Input.CruSession, err = GlobalSessions.SessionStart(rw, r) if err != nil { @@ -819,7 +827,7 @@ func (p *ControllerRegister) serveHttp(ctx *beecontext.Context) { execController.Prepare() // if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf - if BConfig.WebConfig.EnableXSRF { + if p.cfg.WebConfig.EnableXSRF { execController.XSRFToken() if r.Method == http.MethodPost || r.Method == http.MethodDelete || r.Method == http.MethodPut || (r.Method == http.MethodPost && (ctx.Input.Query("_method") == http.MethodDelete || ctx.Input.Query("_method") == http.MethodPut)) { @@ -864,7 +872,7 @@ func (p *ControllerRegister) serveHttp(ctx *beecontext.Context) { // render template if !ctx.ResponseWriter.Started && ctx.Output.Status == 0 { - if BConfig.WebConfig.AutoRender { + if p.cfg.WebConfig.AutoRender { if err := execController.Render(); err != nil { logs.Error(err) } @@ -897,7 +905,7 @@ Admin: timeDur := time.Since(startTime) ctx.ResponseWriter.Elapsed = timeDur - if BConfig.Listen.EnableAdmin { + if p.cfg.Listen.EnableAdmin { pattern := "" if routerInfo != nil { pattern = routerInfo.pattern @@ -912,7 +920,7 @@ Admin: } } - if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs { + if p.cfg.RunMode == DEV && !p.cfg.Log.AccessLogs { match := map[bool]string{true: "match", false: "nomatch"} devInfo := fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", ctx.Input.IP(), @@ -935,7 +943,7 @@ Admin: func (p *ControllerRegister) getUrlPath(ctx *beecontext.Context) string { urlPath := ctx.Request.URL.Path - if !BConfig.RouterCaseSensitive { + if !p.cfg.RouterCaseSensitive { urlPath = strings.ToLower(urlPath) } return urlPath @@ -958,7 +966,7 @@ func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, ex // FindRouter Find Router info for URL func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo *ControllerInfo, isFind bool) { var urlPath = context.Input.URL() - if !BConfig.RouterCaseSensitive { + if !p.cfg.RouterCaseSensitive { urlPath = strings.ToLower(urlPath) } httpMethod := context.Input.Method() @@ -984,36 +992,5 @@ func toURL(params map[string]string) string { // LogAccess logging info HTTP Access func LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) { - // Skip logging if AccessLogs config is false - if !BConfig.Log.AccessLogs { - return - } - // Skip logging static requests unless EnableStaticLogs config is true - if !BConfig.Log.EnableStaticLogs && DefaultAccessLogFilter.Filter(ctx) { - return - } - var ( - requestTime time.Time - elapsedTime time.Duration - r = ctx.Request - ) - if startTime != nil { - requestTime = *startTime - elapsedTime = time.Since(*startTime) - } - record := &logs.AccessLogRecord{ - RemoteAddr: ctx.Input.IP(), - RequestTime: requestTime, - RequestMethod: r.Method, - Request: fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto), - ServerProtocol: r.Proto, - Host: r.Host, - Status: statusCode, - ElapsedTime: elapsedTime, - HTTPReferrer: r.Header.Get("Referer"), - HTTPUserAgent: r.Header.Get("User-Agent"), - RemoteUser: r.Header.Get("Remote-User"), - BodyBytesSent: r.ContentLength, - } - logs.AccessLog(record, BConfig.Log.AccessLogsFormat) + BeeApp.LogAccess(ctx, startTime, statusCode) } diff --git a/pkg/server/web/router_test.go b/pkg/server/web/router_test.go index 2863da3a..da8efbcb 100644 --- a/pkg/server/web/router_test.go +++ b/pkg/server/web/router_test.go @@ -381,7 +381,7 @@ func TestRouterHandlerAll(t *testing.T) { } // -// Benchmarks NewApp: +// Benchmarks NewHttpSever: // func beegoFilterFunc(ctx *context.Context) { diff --git a/pkg/server/web/app.go b/pkg/server/web/server.go similarity index 54% rename from pkg/server/web/app.go rename to pkg/server/web/server.go index 7511c7fe..c3ab1696 100644 --- a/pkg/server/web/app.go +++ b/pkg/server/web/server.go @@ -24,12 +24,14 @@ import ( "net/http/fcgi" "os" "path" + "strconv" "strings" "time" "golang.org/x/crypto/acme/autocert" "github.com/astaxie/beego/pkg/infrastructure/logs" + beecontext "github.com/astaxie/beego/pkg/server/web/context" "github.com/astaxie/beego/pkg/infrastructure/utils" "github.com/astaxie/beego/pkg/server/web/grace" @@ -37,24 +39,39 @@ import ( var ( // BeeApp is an application instance - BeeApp *App + // If you are using single server, you could use this + // But if you need multiple servers, do not use this + BeeApp *HttpServer ) func init() { // create beego application - BeeApp = NewApp() + BeeApp = NewHttpSever() } -// App defines beego application with a new PatternServeMux. -type App struct { +// HttpServer defines beego application with a new PatternServeMux. +type HttpServer struct { Handlers *ControllerRegister Server *http.Server + Cfg *Config } -// NewApp returns a new beego application. -func NewApp() *App { - cr := NewControllerRegister() - app := &App{Handlers: cr, Server: &http.Server{}} +// NewHttpSever returns a new beego application. +// this method will use the BConfig as the configure to create HttpServer +// Be careful that when you update BConfig, the server's Cfg will not be updated +func NewHttpSever() *HttpServer { + return NewHttpServerWithCfg(*BConfig) +} + +// NewHttpServerWithCfg will create an sever with specific cfg +func NewHttpServerWithCfg(cfg Config) *HttpServer { + cfgPtr := &cfg + cr := NewControllerRegisterWithCfg(cfgPtr) + app := &HttpServer{ + Handlers: cr, + Server: &http.Server{}, + Cfg: cfgPtr, + } return app } @@ -62,11 +79,16 @@ func NewApp() *App { type MiddleWare func(http.Handler) http.Handler // Run beego application. -func (app *App) Run(mws ...MiddleWare) { - addr := BConfig.Listen.HTTPAddr +func (app *HttpServer) Run(addr string, mws ...MiddleWare) { - if BConfig.Listen.HTTPPort != 0 { - addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPAddr, BConfig.Listen.HTTPPort) + initBeforeHTTPRun() + + app.initAddr(addr) + + addr = app.Cfg.Listen.HTTPAddr + + if app.Cfg.Listen.HTTPPort != 0 { + addr = fmt.Sprintf("%s:%d", app.Cfg.Listen.HTTPAddr, app.Cfg.Listen.HTTPPort) } var ( @@ -76,8 +98,8 @@ func (app *App) Run(mws ...MiddleWare) { ) // run cgi server - if BConfig.Listen.EnableFcgi { - if BConfig.Listen.EnableStdIo { + if app.Cfg.Listen.EnableFcgi { + if app.Cfg.Listen.EnableStdIo { if err = fcgi.Serve(nil, app.Handlers); err == nil { // standard I/O logs.Info("Use FCGI via standard I/O") } else { @@ -85,7 +107,7 @@ func (app *App) Run(mws ...MiddleWare) { } return } - if BConfig.Listen.HTTPPort == 0 { + if app.Cfg.Listen.HTTPPort == 0 { // remove the Socket file before start if utils.FileExists(addr) { os.Remove(addr) @@ -110,40 +132,42 @@ func (app *App) Run(mws ...MiddleWare) { } app.Server.Handler = mws[i](app.Server.Handler) } - app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second - app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second + app.Server.ReadTimeout = time.Duration(app.Cfg.Listen.ServerTimeOut) * time.Second + app.Server.WriteTimeout = time.Duration(app.Cfg.Listen.ServerTimeOut) * time.Second app.Server.ErrorLog = logs.GetLogger("HTTP") // run graceful mode - if BConfig.Listen.Graceful { - httpsAddr := BConfig.Listen.HTTPSAddr + if app.Cfg.Listen.Graceful { + httpsAddr := app.Cfg.Listen.HTTPSAddr app.Server.Addr = httpsAddr - if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS { + if app.Cfg.Listen.EnableHTTPS || app.Cfg.Listen.EnableMutualHTTPS { go func() { time.Sleep(1000 * time.Microsecond) - if BConfig.Listen.HTTPSPort != 0 { - httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort) + if app.Cfg.Listen.HTTPSPort != 0 { + httpsAddr = fmt.Sprintf("%s:%d", app.Cfg.Listen.HTTPSAddr, app.Cfg.Listen.HTTPSPort) app.Server.Addr = httpsAddr } server := grace.NewServer(httpsAddr, app.Server.Handler) server.Server.ReadTimeout = app.Server.ReadTimeout server.Server.WriteTimeout = app.Server.WriteTimeout - if BConfig.Listen.EnableMutualHTTPS { - if err := server.ListenAndServeMutualTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile, BConfig.Listen.TrustCaFile); err != nil { + if app.Cfg.Listen.EnableMutualHTTPS { + if err := server.ListenAndServeMutualTLS(app.Cfg.Listen.HTTPSCertFile, + app.Cfg.Listen.HTTPSKeyFile, + app.Cfg.Listen.TrustCaFile); err != nil { logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) time.Sleep(100 * time.Microsecond) } } else { - if BConfig.Listen.AutoTLS { + if app.Cfg.Listen.AutoTLS { m := autocert.Manager{ Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...), - Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir), + HostPolicy: autocert.HostWhitelist(app.Cfg.Listen.Domains...), + Cache: autocert.DirCache(app.Cfg.Listen.TLSCacheDir), } app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} - BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", "" + app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile = "", "" } - if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil { + if err := server.ListenAndServeTLS(app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile); err != nil { logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) time.Sleep(100 * time.Microsecond) } @@ -151,12 +175,12 @@ func (app *App) Run(mws ...MiddleWare) { endRunning <- true }() } - if BConfig.Listen.EnableHTTP { + if app.Cfg.Listen.EnableHTTP { go func() { server := grace.NewServer(addr, app.Server.Handler) server.Server.ReadTimeout = app.Server.ReadTimeout server.Server.WriteTimeout = app.Server.WriteTimeout - if BConfig.Listen.ListenTCP4 { + if app.Cfg.Listen.ListenTCP4 { server.Network = "tcp4" } if err := server.ListenAndServe(); err != nil { @@ -171,27 +195,27 @@ func (app *App) Run(mws ...MiddleWare) { } // run normal mode - if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS { + if app.Cfg.Listen.EnableHTTPS || app.Cfg.Listen.EnableMutualHTTPS { go func() { time.Sleep(1000 * time.Microsecond) - if BConfig.Listen.HTTPSPort != 0 { - app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort) - } else if BConfig.Listen.EnableHTTP { + if app.Cfg.Listen.HTTPSPort != 0 { + app.Server.Addr = fmt.Sprintf("%s:%d", app.Cfg.Listen.HTTPSAddr, app.Cfg.Listen.HTTPSPort) + } else if app.Cfg.Listen.EnableHTTP { logs.Info("Start https server error, conflict with http. Please reset https port") return } logs.Info("https server Running on https://%s", app.Server.Addr) - if BConfig.Listen.AutoTLS { + if app.Cfg.Listen.AutoTLS { m := autocert.Manager{ Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...), - Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir), + HostPolicy: autocert.HostWhitelist(app.Cfg.Listen.Domains...), + Cache: autocert.DirCache(app.Cfg.Listen.TLSCacheDir), } app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} - BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", "" - } else if BConfig.Listen.EnableMutualHTTPS { + app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile = "", "" + } else if app.Cfg.Listen.EnableMutualHTTPS { pool := x509.NewCertPool() - data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile) + data, err := ioutil.ReadFile(app.Cfg.Listen.TrustCaFile) if err != nil { logs.Info("MutualHTTPS should provide TrustCaFile") return @@ -199,10 +223,10 @@ func (app *App) Run(mws ...MiddleWare) { pool.AppendCertsFromPEM(data) app.Server.TLSConfig = &tls.Config{ ClientCAs: pool, - ClientAuth: tls.ClientAuthType(BConfig.Listen.ClientAuth), + ClientAuth: tls.ClientAuthType(app.Cfg.Listen.ClientAuth), } } - if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil { + if err := app.Server.ListenAndServeTLS(app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile); err != nil { logs.Critical("ListenAndServeTLS: ", err) time.Sleep(100 * time.Microsecond) endRunning <- true @@ -210,11 +234,11 @@ func (app *App) Run(mws ...MiddleWare) { }() } - if BConfig.Listen.EnableHTTP { + if app.Cfg.Listen.EnableHTTP { go func() { app.Server.Addr = addr logs.Info("http server Running on http://%s", app.Server.Addr) - if BConfig.Listen.ListenTCP4 { + if app.Cfg.Listen.ListenTCP4 { ln, err := net.Listen("tcp4", app.Server.Addr) if err != nil { logs.Critical("ListenAndServe: ", err) @@ -240,8 +264,17 @@ func (app *App) Run(mws ...MiddleWare) { <-endRunning } +func (app *HttpServer) Start() { + +} + +// Router see HttpServer.Router +func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *HttpServer { + return BeeApp.Router(rootpath, c, mappingMethods...) +} + // Router adds a patterned controller handler to BeeApp. -// it's an alias method of App.Router. +// it's an alias method of HttpServer.Router. // usage: // simple router // beego.Router("/admin", &admin.UserController{}) @@ -256,9 +289,14 @@ func (app *App) Run(mws ...MiddleWare) { // beego.Router("/api/create",&RestController{},"post:CreateFood") // beego.Router("/api/update",&RestController{},"put:UpdateFood") // beego.Router("/api/delete",&RestController{},"delete:DeleteFood") -func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App { - BeeApp.Handlers.Add(rootpath, c, mappingMethods...) - return BeeApp +func (app *HttpServer) Router(rootPath string, c ControllerInterface, mappingMethods ...string) *HttpServer { + app.Handlers.Add(rootPath, c, mappingMethods...) + return app +} + +// UnregisterFixedRoute see HttpServer.UnregisterFixedRoute +func UnregisterFixedRoute(fixedRoute string, method string) *HttpServer { + return BeeApp.UnregisterFixedRoute(fixedRoute, method) } // UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful @@ -270,31 +308,31 @@ func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *A // Usage (replace "GET" with "*" for all methods): // beego.UnregisterFixedRoute("/yourpreviouspath", "GET") // beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage") -func UnregisterFixedRoute(fixedRoute string, method string) *App { +func (app *HttpServer) UnregisterFixedRoute(fixedRoute string, method string) *HttpServer { subPaths := splitPath(fixedRoute) if method == "" || method == "*" { for m := range HTTPMETHOD { - if _, ok := BeeApp.Handlers.routers[m]; !ok { + if _, ok := app.Handlers.routers[m]; !ok { continue } - if BeeApp.Handlers.routers[m].prefix == strings.Trim(fixedRoute, "/ ") { - findAndRemoveSingleTree(BeeApp.Handlers.routers[m]) + if app.Handlers.routers[m].prefix == strings.Trim(fixedRoute, "/ ") { + findAndRemoveSingleTree(app.Handlers.routers[m]) continue } - findAndRemoveTree(subPaths, BeeApp.Handlers.routers[m], m) + findAndRemoveTree(subPaths, app.Handlers.routers[m], m) } - return BeeApp + return app } // Single HTTP method um := strings.ToUpper(method) - if _, ok := BeeApp.Handlers.routers[um]; ok { - if BeeApp.Handlers.routers[um].prefix == strings.Trim(fixedRoute, "/ ") { - findAndRemoveSingleTree(BeeApp.Handlers.routers[um]) - return BeeApp + if _, ok := app.Handlers.routers[um]; ok { + if app.Handlers.routers[um].prefix == strings.Trim(fixedRoute, "/ ") { + findAndRemoveSingleTree(app.Handlers.routers[um]) + return app } - findAndRemoveTree(subPaths, BeeApp.Handlers.routers[um], um) + findAndRemoveTree(subPaths, app.Handlers.routers[um], um) } - return BeeApp + return app } func findAndRemoveTree(paths []string, entryPointTree *Tree, method string) { @@ -339,6 +377,11 @@ func findAndRemoveSingleTree(entryPointTree *Tree) { } } +// Include see HttpServer.Include +func Include(cList ...ControllerInterface) *HttpServer { + return BeeApp.Include(cList...) +} + // Include will generate router file in the router/xxx.go from the controller's comments // usage: // beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) @@ -366,36 +409,56 @@ func findAndRemoveSingleTree(entryPointTree *Tree) { // the comments @router url methodlist // url support all the function Router's pattern // methodlist [get post head put delete options *] -func Include(cList ...ControllerInterface) *App { - BeeApp.Handlers.Include(cList...) - return BeeApp +func (app *HttpServer) Include(cList ...ControllerInterface) *HttpServer { + app.Handlers.Include(cList...) + return app +} + +// RESTRouter see HttpServer.RESTRouter +func RESTRouter(rootpath string, c ControllerInterface) *HttpServer { + return BeeApp.RESTRouter(rootpath, c) } // RESTRouter adds a restful controller handler to BeeApp. // its' controller implements beego.ControllerInterface and // defines a param "pattern/:objectId" to visit each resource. -func RESTRouter(rootpath string, c ControllerInterface) *App { - Router(rootpath, c) - Router(path.Join(rootpath, ":objectId"), c) - return BeeApp +func (app *HttpServer) RESTRouter(rootpath string, c ControllerInterface) *HttpServer { + app.Router(rootpath, c) + app.Router(path.Join(rootpath, ":objectId"), c) + return app +} + +// AutoRouter see HttpServer.AutoRouter +func AutoRouter(c ControllerInterface) *HttpServer { + return BeeApp.AutoRouter(c) } // AutoRouter adds defined controller handler to BeeApp. -// it's same to App.AutoRouter. +// it's same to HttpServer.AutoRouter. // if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page, // visit the url /main/list to exec List function or /main/page to exec Page function. -func AutoRouter(c ControllerInterface) *App { - BeeApp.Handlers.AddAuto(c) - return BeeApp +func (app *HttpServer) AutoRouter(c ControllerInterface) *HttpServer { + app.Handlers.AddAuto(c) + return app +} + +// AutoPrefix see HttpServer.AutoPrefix +func AutoPrefix(prefix string, c ControllerInterface) *HttpServer { + return BeeApp.AutoPrefix(prefix, c) } // AutoPrefix adds controller handler to BeeApp with prefix. -// it's same to App.AutoRouterWithPrefix. +// it's same to HttpServer.AutoRouterWithPrefix. // if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page, // visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function. -func AutoPrefix(prefix string, c ControllerInterface) *App { - BeeApp.Handlers.AddAutoPrefix(prefix, c) - return BeeApp +func (app *HttpServer) AutoPrefix(prefix string, c ControllerInterface) *HttpServer { + app.Handlers.AddAutoPrefix(prefix, c) + return app +} + +// Get see HttpServer.Get +func Get(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Get(rootpath, f) } // Get used to register router for Get method @@ -403,9 +466,14 @@ func AutoPrefix(prefix string, c ControllerInterface) *App { // beego.Get("/", func(ctx *context.Context){ // ctx.Output.Body("hello world") // }) -func Get(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Get(rootpath, f) - return BeeApp +func (app *HttpServer) Get(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Get(rootpath, f) + return app +} + +// Post see HttpServer.Post +func Post(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Post(rootpath, f) } // Post used to register router for Post method @@ -413,9 +481,14 @@ func Get(rootpath string, f FilterFunc) *App { // beego.Post("/api", func(ctx *context.Context){ // ctx.Output.Body("hello world") // }) -func Post(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Post(rootpath, f) - return BeeApp +func (app *HttpServer) Post(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Post(rootpath, f) + return app +} + +// Delete see HttpServer.Delete +func Delete(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Delete(rootpath, f) } // Delete used to register router for Delete method @@ -423,9 +496,14 @@ func Post(rootpath string, f FilterFunc) *App { // beego.Delete("/api", func(ctx *context.Context){ // ctx.Output.Body("hello world") // }) -func Delete(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Delete(rootpath, f) - return BeeApp +func (app *HttpServer) Delete(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Delete(rootpath, f) + return app +} + +// Put see HttpServer.Put +func Put(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Put(rootpath, f) } // Put used to register router for Put method @@ -433,9 +511,14 @@ func Delete(rootpath string, f FilterFunc) *App { // beego.Put("/api", func(ctx *context.Context){ // ctx.Output.Body("hello world") // }) -func Put(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Put(rootpath, f) - return BeeApp +func (app *HttpServer) Put(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Put(rootpath, f) + return app +} + +// Head see HttpServer.Head +func Head(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Head(rootpath, f) } // Head used to register router for Head method @@ -443,8 +526,14 @@ func Put(rootpath string, f FilterFunc) *App { // beego.Head("/api", func(ctx *context.Context){ // ctx.Output.Body("hello world") // }) -func Head(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Head(rootpath, f) +func (app *HttpServer) Head(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Head(rootpath, f) + return app +} + +// Options see HttpServer.Options +func Options(rootpath string, f FilterFunc) *HttpServer { + BeeApp.Handlers.Options(rootpath, f) return BeeApp } @@ -453,9 +542,14 @@ func Head(rootpath string, f FilterFunc) *App { // beego.Options("/api", func(ctx *context.Context){ // ctx.Output.Body("hello world") // }) -func Options(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Options(rootpath, f) - return BeeApp +func (app *HttpServer) Options(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Options(rootpath, f) + return app +} + +// Patch see HttpServer.Patch +func Patch(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Patch(rootpath, f) } // Patch used to register router for Patch method @@ -463,9 +557,14 @@ func Options(rootpath string, f FilterFunc) *App { // beego.Patch("/api", func(ctx *context.Context){ // ctx.Output.Body("hello world") // }) -func Patch(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Patch(rootpath, f) - return BeeApp +func (app *HttpServer) Patch(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Patch(rootpath, f) + return app +} + +// Any see HttpServer.Any +func Any(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Any(rootpath, f) } // Any used to register router for all methods @@ -473,9 +572,14 @@ func Patch(rootpath string, f FilterFunc) *App { // beego.Any("/api", func(ctx *context.Context){ // ctx.Output.Body("hello world") // }) -func Any(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Any(rootpath, f) - return BeeApp +func (app *HttpServer) Any(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Any(rootpath, f) + return app +} + +// Handler see HttpServer.Handler +func Handler(rootpath string, h http.Handler, options ...interface{}) *HttpServer { + return BeeApp.Handler(rootpath, h, options...) } // Handler used to register a Handler router @@ -483,24 +587,81 @@ func Any(rootpath string, f FilterFunc) *App { // beego.Handler("/api", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { // fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) // })) -func Handler(rootpath string, h http.Handler, options ...interface{}) *App { - BeeApp.Handlers.Handler(rootpath, h, options...) - return BeeApp +func (app *HttpServer) Handler(rootpath string, h http.Handler, options ...interface{}) *HttpServer { + app.Handlers.Handler(rootpath, h, options...) + return app +} + +// InserFilter see HttpServer.InsertFilter +func InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt) *HttpServer { + return BeeApp.InsertFilter(pattern, pos, filter, opts...) } // InsertFilter adds a FilterFunc with pattern condition and action constant. // The pos means action constant including // beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter. // The bool params is for setting the returnOnOutput value (false allows multiple filters to execute) -func InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt) *App { - BeeApp.Handlers.InsertFilter(pattern, pos, filter, opts...) - return BeeApp +func (app *HttpServer) InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt) *HttpServer { + app.Handlers.InsertFilter(pattern, pos, filter, opts...) + return app +} + +// InsertFilterChain see HttpServer.InsertFilterChain +func InsertFilterChain(pattern string, filterChain FilterChain, opts ...FilterOpt) *HttpServer { + return BeeApp.InsertFilterChain(pattern, filterChain, opts...) } // InsertFilterChain adds a FilterFunc built by filterChain. // This filter will be executed before all filters. -// the filter's behavior is like stack -func InsertFilterChain(pattern string, filterChain FilterChain, opts ...FilterOpt) *App { - BeeApp.Handlers.InsertFilterChain(pattern, filterChain, opts...) - return BeeApp +// the filter's behavior like stack's behavior +// and the last filter is serving the http request +func (app *HttpServer) InsertFilterChain(pattern string, filterChain FilterChain, opts ...FilterOpt) *HttpServer { + app.Handlers.InsertFilterChain(pattern, filterChain, opts...) + return app +} + +func (app *HttpServer) initAddr(addr string) { + strs := strings.Split(addr, ":") + if len(strs) > 0 && strs[0] != "" { + app.Cfg.Listen.HTTPAddr = strs[0] + app.Cfg.Listen.Domains = []string{strs[0]} + } + if len(strs) > 1 && strs[1] != "" { + app.Cfg.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) + } +} + +func (app *HttpServer) LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) { + // Skip logging if AccessLogs config is false + if !app.Cfg.Log.AccessLogs { + return + } + // Skip logging static requests unless EnableStaticLogs config is true + if !app.Cfg.Log.EnableStaticLogs && DefaultAccessLogFilter.Filter(ctx) { + return + } + var ( + requestTime time.Time + elapsedTime time.Duration + r = ctx.Request + ) + if startTime != nil { + requestTime = *startTime + elapsedTime = time.Since(*startTime) + } + record := &logs.AccessLogRecord{ + RemoteAddr: ctx.Input.IP(), + RequestTime: requestTime, + RequestMethod: r.Method, + Request: fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto), + ServerProtocol: r.Proto, + Host: r.Host, + Status: statusCode, + ElapsedTime: elapsedTime, + HTTPReferrer: r.Header.Get("Referer"), + HTTPUserAgent: r.Header.Get("User-Agent"), + RemoteUser: r.Header.Get("Remote-User"), + BodyBytesSent: r.ContentLength, + } + logs.AccessLog(record, app.Cfg.Log.AccessLogsFormat) } diff --git a/pkg/server/web/server_test.go b/pkg/server/web/server_test.go new file mode 100644 index 00000000..45ab2d4f --- /dev/null +++ b/pkg/server/web/server_test.go @@ -0,0 +1,31 @@ +// Copyright 2020 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package web + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewHttpServerWithCfg(t *testing.T) { + // we should make sure that update server's config won't change + BConfig.AppName = "Before" + svr := NewHttpServerWithCfg(*BConfig) + svr.Cfg.AppName = "hello" + assert.NotEqual(t, "hello", BConfig.AppName) + assert.Equal(t, "Before", BConfig.AppName) + +} diff --git a/pkg/server/web/template.go b/pkg/server/web/template.go index a4b8db99..1192a3f2 100644 --- a/pkg/server/web/template.go +++ b/pkg/server/web/template.go @@ -368,14 +368,14 @@ func SetTemplateFSFunc(fnt templateFSFunc) { } // SetViewsPath sets view directory path in beego application. -func SetViewsPath(path string) *App { +func SetViewsPath(path string) *HttpServer { BConfig.WebConfig.ViewsPath = path return BeeApp } // SetStaticPath sets static directory path and proper url pattern in beego application. // if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public". -func SetStaticPath(url string, path string) *App { +func SetStaticPath(url string, path string) *HttpServer { if !strings.HasPrefix(url, "/") { url = "/" + url } @@ -387,7 +387,7 @@ func SetStaticPath(url string, path string) *App { } // DelStaticPath removes the static folder setting in this url pattern in beego application. -func DelStaticPath(url string) *App { +func DelStaticPath(url string) *HttpServer { if !strings.HasPrefix(url, "/") { url = "/" + url } @@ -399,7 +399,7 @@ func DelStaticPath(url string) *App { } // AddTemplateEngine add a new templatePreProcessor which support extension -func AddTemplateEngine(extension string, fn templatePreProcessor) *App { +func AddTemplateEngine(extension string, fn templatePreProcessor) *HttpServer { AddTemplateExt(extension) beeTemplateEngines[extension] = fn return BeeApp