From f70f338025826db9cb3fc522ac89c962f255b97b Mon Sep 17 00:00:00 2001 From: astaxie Date: Thu, 10 Dec 2015 21:59:54 +0800 Subject: [PATCH 1/2] use sync.Pool to reuse Context --- context/context.go | 16 +++++++++ context/input.go | 81 ++++++++++++++++++++++++---------------------- context/output.go | 8 ++++- docs.go | 8 +---- router.go | 48 +++++++++++---------------- staticfile.go | 2 +- 6 files changed, 86 insertions(+), 77 deletions(-) diff --git a/context/context.go b/context/context.go index 8f1144f3..a7df6f8d 100644 --- a/context/context.go +++ b/context/context.go @@ -35,6 +35,14 @@ import ( "github.com/astaxie/beego/utils" ) +// NewContext return the Context with Input and Output +func NewContext() *Context { + return &Context{ + Input: NewInput(), + Output: NewOutput(), + } +} + // Context Http request context struct including BeegoInput, BeegoOutput, http.Request and http.ResponseWriter. // BeegoInput and BeegoOutput provides some api to operate request and response more easily. type Context struct { @@ -45,6 +53,14 @@ type Context struct { _xsrfToken string } +// Reset init Context, BeegoInput and BeegoOutput +func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { + ctx.Request = r + ctx.ResponseWriter = rw + ctx.Input.Reset(ctx) + ctx.Output.Reset(ctx) +} + // Redirect does redirection to localurl with http header status code. // It sends http response header directly. func (ctx *Context) Redirect(status int, localurl string) { diff --git a/context/input.go b/context/input.go index 53cc3e88..9649e14f 100644 --- a/context/input.go +++ b/context/input.go @@ -18,7 +18,6 @@ import ( "bytes" "errors" "io/ioutil" - "net/http" "net/url" "reflect" "regexp" @@ -39,37 +38,43 @@ var ( // BeegoInput operates the http request header, data, cookie and body. // it also contains router params and current session. type BeegoInput struct { - CruSession session.Store - Params map[string]string - Data map[interface{}]interface{} // store some values in this context when calling context in filter or controller. - Request *http.Request - RequestBody []byte - RunController reflect.Type - RunMethod string + Context *Context + CruSession session.Store + Params map[string]string + Data map[interface{}]interface{} // store some values in this context when calling context in filter or controller. + RequestBody []byte } -// NewInput return BeegoInput generated by http.Request. -func NewInput(req *http.Request) *BeegoInput { +// NewInput return BeegoInput generated by Context. +func NewInput() *BeegoInput { return &BeegoInput{ - Params: make(map[string]string), - Data: make(map[interface{}]interface{}), - Request: req, + Params: make(map[string]string), + Data: make(map[interface{}]interface{}), } } +// Reset init the BeegoInput +func (input *BeegoInput) Reset(ctx *Context) { + input.Context = ctx + input.CruSession = nil + input.Params = make(map[string]string) + input.Data = make(map[interface{}]interface{}) + input.RequestBody = []byte{} +} + // Protocol returns request protocol name, such as HTTP/1.1 . func (input *BeegoInput) Protocol() string { - return input.Request.Proto + return input.Context.Request.Proto } // URI returns full request url with query string, fragment. func (input *BeegoInput) URI() string { - return input.Request.RequestURI + return input.Context.Request.RequestURI } // URL returns request url path (without query string, fragment). func (input *BeegoInput) URL() string { - return input.Request.URL.Path + return input.Context.Request.URL.Path } // Site returns base site url as scheme://domain type. @@ -79,10 +84,10 @@ func (input *BeegoInput) Site() string { // Scheme returns request scheme as "http" or "https". func (input *BeegoInput) Scheme() string { - if input.Request.URL.Scheme != "" { - return input.Request.URL.Scheme + if input.Context.Request.URL.Scheme != "" { + return input.Context.Request.URL.Scheme } - if input.Request.TLS == nil { + if input.Context.Request.TLS == nil { return "http" } return "https" @@ -97,19 +102,19 @@ func (input *BeegoInput) Domain() string { // Host returns host name. // if no host info in request, return localhost. func (input *BeegoInput) Host() string { - if input.Request.Host != "" { - hostParts := strings.Split(input.Request.Host, ":") + if input.Context.Request.Host != "" { + hostParts := strings.Split(input.Context.Request.Host, ":") if len(hostParts) > 0 { return hostParts[0] } - return input.Request.Host + return input.Context.Request.Host } return "localhost" } // Method returns http request method. func (input *BeegoInput) Method() string { - return input.Request.Method + return input.Context.Request.Method } // Is returns boolean of this request is on given method, such as Is("POST"). @@ -196,7 +201,7 @@ func (input *BeegoInput) IP() string { rip := strings.Split(ips[0], ":") return rip[0] } - ip := strings.Split(input.Request.RemoteAddr, ":") + ip := strings.Split(input.Context.Request.RemoteAddr, ":") if len(ip) > 0 { if ip[0] != "[" { return ip[0] @@ -236,7 +241,7 @@ func (input *BeegoInput) SubDomains() string { // Port returns request client port. // when error or empty, return 80. func (input *BeegoInput) Port() int { - parts := strings.Split(input.Request.Host, ":") + parts := strings.Split(input.Context.Request.Host, ":") if len(parts) == 2 { port, _ := strconv.Atoi(parts[1]) return port @@ -262,22 +267,22 @@ func (input *BeegoInput) Query(key string) string { if val := input.Param(key); val != "" { return val } - if input.Request.Form == nil { - input.Request.ParseForm() + if input.Context.Request.Form == nil { + input.Context.Request.ParseForm() } - return input.Request.Form.Get(key) + return input.Context.Request.Form.Get(key) } // Header returns request header item string by a given string. // if non-existed, return empty string. func (input *BeegoInput) Header(key string) string { - return input.Request.Header.Get(key) + return input.Context.Request.Header.Get(key) } // Cookie returns request cookie item string by a given key. // if non-existed, return empty string. func (input *BeegoInput) Cookie(key string) string { - ck, err := input.Request.Cookie(key) + ck, err := input.Context.Request.Cookie(key) if err != nil { return "" } @@ -292,10 +297,10 @@ func (input *BeegoInput) Session(key interface{}) interface{} { // CopyBody returns the raw request body data as bytes. func (input *BeegoInput) CopyBody() []byte { - requestbody, _ := ioutil.ReadAll(input.Request.Body) - input.Request.Body.Close() + requestbody, _ := ioutil.ReadAll(input.Context.Request.Body) + input.Context.Request.Body.Close() bf := bytes.NewBuffer(requestbody) - input.Request.Body = ioutil.NopCloser(bf) + input.Context.Request.Body = ioutil.NopCloser(bf) input.RequestBody = requestbody return requestbody } @@ -318,10 +323,10 @@ func (input *BeegoInput) SetData(key, val interface{}) { func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error { // Parse the body depending on the content type. if strings.Contains(input.Header("Content-Type"), "multipart/form-data") { - if err := input.Request.ParseMultipartForm(maxMemory); err != nil { + if err := input.Context.Request.ParseMultipartForm(maxMemory); err != nil { return errors.New("Error parsing request body:" + err.Error()) } - } else if err := input.Request.ParseForm(); err != nil { + } else if err := input.Context.Request.ParseForm(); err != nil { return errors.New("Error parsing request body:" + err.Error()) } return nil @@ -386,13 +391,13 @@ func (input *BeegoInput) bind(key string, typ reflect.Type) reflect.Value { } rv = input.bindBool(val, typ) case reflect.Slice: - rv = input.bindSlice(&input.Request.Form, key, typ) + rv = input.bindSlice(&input.Context.Request.Form, key, typ) case reflect.Struct: - rv = input.bindStruct(&input.Request.Form, key, typ) + rv = input.bindStruct(&input.Context.Request.Form, key, typ) case reflect.Ptr: rv = input.bindPoint(key, typ) case reflect.Map: - rv = input.bindMap(&input.Request.Form, key, typ) + rv = input.bindMap(&input.Context.Request.Form, key, typ) } return rv } diff --git a/context/output.go b/context/output.go index f0d66f36..2d756e27 100644 --- a/context/output.go +++ b/context/output.go @@ -43,6 +43,12 @@ func NewOutput() *BeegoOutput { return &BeegoOutput{} } +// Reset init BeegoOutput +func (output *BeegoOutput) Reset(ctx *Context) { + output.Context = ctx + output.Status = 0 +} + // Header sets response header item string via given key. func (output *BeegoOutput) Header(key, val string) { output.Context.ResponseWriter.Header().Set(key, val) @@ -55,7 +61,7 @@ func (output *BeegoOutput) Body(content []byte) { var encoding string var buf = &bytes.Buffer{} if output.EnableGzip { - encoding = ParseEncoding(output.Context.Input.Request) + encoding = ParseEncoding(output.Context.Request) } if b, n, _ := WriteBody(encoding, buf, content); b { output.Header("Content-Encoding", n) diff --git a/docs.go b/docs.go index 5031dc12..0e0f13f7 100644 --- a/docs.go +++ b/docs.go @@ -21,13 +21,7 @@ import ( ) // GlobalDocAPI store the swagger api documents -var GlobalDocAPI map[string]interface{} - -func init() { - if BConfig.WebConfig.EnableDocs { - GlobalDocAPI = make(map[string]interface{}) - } -} +var GlobalDocAPI = make(map[string]interface{}) func serverDocs(ctx *context.Context) { var obj interface{} diff --git a/router.go b/router.go index 28d641d0..a575f9ed 100644 --- a/router.go +++ b/router.go @@ -24,6 +24,7 @@ import ( "runtime" "strconv" "strings" + "sync" "time" beecontext "github.com/astaxie/beego/context" @@ -83,7 +84,7 @@ type logFilter struct { } func (l *logFilter) Filter(ctx *beecontext.Context) bool { - requestPath := path.Clean(ctx.Input.Request.URL.Path) + requestPath := path.Clean(ctx.Request.URL.Path) if requestPath == "/favicon.ico" || requestPath == "/robots.txt" { return true } @@ -114,14 +115,19 @@ type ControllerRegister struct { routers map[string]*Tree enableFilter bool filters map[int][]*FilterRouter + pool sync.Pool } // NewControllerRegister returns a new ControllerRegister. func NewControllerRegister() *ControllerRegister { - return &ControllerRegister{ + cr := &ControllerRegister{ routers: make(map[string]*Tree), filters: make(map[int][]*FilterRouter), } + cr.pool.New = func() interface{} { + return beecontext.NewContext() + } + return cr } // Add controller handler and pattern rules to ControllerRegister. @@ -132,7 +138,7 @@ func NewControllerRegister() *ControllerRegister { // Add("/api/create",&RestController{},"post:CreateFood") // Add("/api/update",&RestController{},"put:UpdateFood") // Add("/api/delete",&RestController{},"delete:DeleteFood") -// Add("/api",&RestController{},"get,post:ApiFunc") +// Add("/api",&RestController{},"get,post:ApiFunc" // Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc") func (p *ControllerRegister) Add(pattern string, c ControllerInterface, mappingMethods ...string) { reflectVal := reflect.ValueOf(c) @@ -573,26 +579,18 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin // Implement http.Handler interface. func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) { starttime := time.Now() - var runrouter reflect.Type - var findrouter bool - var runMethod string - var routerInfo *controllerInfo - - w := &responseWriter{rw, false, 0} - + var ( + runrouter reflect.Type + findrouter bool + runMethod string + routerInfo *controllerInfo + w = &responseWriter{rw, false, 0} + ) if BConfig.RunMode == "dev" { w.Header().Set("Server", BConfig.ServerName) } - - // init context - context := &beecontext.Context{ - ResponseWriter: w, - Request: r, - Input: beecontext.NewInput(r), - Output: beecontext.NewOutput(), - } - context.Output.Context = context - context.Output.EnableGzip = BConfig.EnableGzip + context := p.pool.Get().(*beecontext.Context) + context.Reset(w, r) defer p.recoverPanic(context) @@ -670,23 +668,14 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) goto Admin } - if context.Input.RunController != nil && context.Input.RunMethod != "" { - findrouter = true - runMethod = context.Input.RunMethod - runrouter = context.Input.RunController - } - if !findrouter { httpMethod := r.Method - if httpMethod == "POST" && context.Input.Query("_method") == "PUT" { httpMethod = "PUT" } - if httpMethod == "POST" && context.Input.Query("_method") == "DELETE" { httpMethod = "DELETE" } - if t, ok := p.routers[httpMethod]; ok { runObject, p := t.Match(urlPath) if r, ok := runObject.(*controllerInfo); ok { @@ -897,7 +886,6 @@ type responseWriter struct { status int } - // Write writes the data to the connection as part of an HTTP reply, // and sets `started` to true. // started means the response has sent out. diff --git a/staticfile.go b/staticfile.go index 031667ed..f3ea59a9 100644 --- a/staticfile.go +++ b/staticfile.go @@ -144,7 +144,7 @@ func isStaticCompress(filePath string) bool { // searchFile search the file by url path // if none the static file prefix matches ,return notStaticRequestErr func searchFile(ctx *context.Context) (string, os.FileInfo, error) { - requestPath := filepath.ToSlash(filepath.Clean(ctx.Input.Request.URL.Path)) + requestPath := filepath.ToSlash(filepath.Clean(ctx.Request.URL.Path)) // special processing : favicon.ico/robots.txt can be in any static dir if requestPath == "/favicon.ico" || requestPath == "/robots.txt" { file := path.Join(".", requestPath) From 80bc372f170e14cec0ccf74b9d729b9ba1686071 Mon Sep 17 00:00:00 2001 From: astaxie Date: Fri, 11 Dec 2015 00:20:17 +0800 Subject: [PATCH 2/2] pool.Put --- context/context.go | 28 +++++++++++- router.go | 110 ++++++++++++++++++--------------------------- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/context/context.go b/context/context.go index a7df6f8d..f3dffdbd 100644 --- a/context/context.go +++ b/context/context.go @@ -49,14 +49,14 @@ type Context struct { Input *BeegoInput Output *BeegoOutput Request *http.Request - ResponseWriter http.ResponseWriter + ResponseWriter *Response _xsrfToken string } // Reset init Context, BeegoInput and BeegoOutput func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { ctx.Request = r - ctx.ResponseWriter = rw + ctx.ResponseWriter = &Response{rw, false, 0} ctx.Input.Reset(ctx) ctx.Output.Reset(ctx) } @@ -164,3 +164,27 @@ func (ctx *Context) CheckXSRFCookie() bool { } return true } + +//Response is a wrapper for the http.ResponseWriter +//started set to true if response was written to then don't execute other handler +type Response struct { + http.ResponseWriter + Started bool + Status int +} + +// Write writes the data to the connection as part of an HTTP reply, +// and sets `started` to true. +// started means the response has sent out. +func (w *Response) Write(p []byte) (int, error) { + w.Started = true + return w.ResponseWriter.Write(p) +} + +// WriteHeader sends an HTTP response header with status code, +// and sets `started` to true. +func (w *Response) WriteHeader(code int) { + w.Status = code + w.Started = true + w.ResponseWriter.WriteHeader(code) +} diff --git a/router.go b/router.go index a575f9ed..01dae8aa 100644 --- a/router.go +++ b/router.go @@ -576,6 +576,31 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin return false, "" } +func (p *ControllerRegister) execFilter(context *beecontext.Context, pos int, urlPath string) (started bool) { + if p.enableFilter { + if l, ok := p.filters[pos]; ok { + for _, filterR := range l { + if filterR.returnOnOutput && context.ResponseWriter.Started { + return true + } + if ok, params := filterR.ValidRouter(urlPath); ok { + for k, v := range params { + if context.Input.Params == nil { + context.Input.Params = make(map[string]string) + } + context.Input.Params[k] = v + } + filterR.filterFunc(context) + } + if filterR.returnOnOutput && context.ResponseWriter.Started { + return true + } + } + } + } + return false +} + // Implement http.Handler interface. func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) { starttime := time.Now() @@ -584,61 +609,36 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) findrouter bool runMethod string routerInfo *controllerInfo - w = &responseWriter{rw, false, 0} ) - if BConfig.RunMode == "dev" { - w.Header().Set("Server", BConfig.ServerName) - } context := p.pool.Get().(*beecontext.Context) - context.Reset(w, r) - + context.Reset(rw, r) + defer p.pool.Put(context) defer p.recoverPanic(context) + if BConfig.RunMode == "dev" { + context.Output.Header("Server", BConfig.ServerName) + } + var urlPath string if !BConfig.RouterCaseSensitive { urlPath = strings.ToLower(r.URL.Path) } else { urlPath = r.URL.Path } - // defined filter function - doFilter := func(pos int) (started bool) { - if p.enableFilter { - if l, ok := p.filters[pos]; ok { - for _, filterR := range l { - if filterR.returnOnOutput && w.started { - return true - } - if ok, params := filterR.ValidRouter(urlPath); ok { - for k, v := range params { - if context.Input.Params == nil { - context.Input.Params = make(map[string]string) - } - context.Input.Params[k] = v - } - filterR.filterFunc(context) - } - if filterR.returnOnOutput && w.started { - return true - } - } - } - } - return false - } // filter wrong httpmethod if _, ok := HTTPMETHOD[r.Method]; !ok { - http.Error(w, "Method Not Allowed", 405) + http.Error(rw, "Method Not Allowed", 405) goto Admin } // filter for static file - if doFilter(BeforeStatic) { + if p.execFilter(context, BeforeStatic, urlPath) { goto Admin } serverStaticRouter(context) - if w.started { + if context.ResponseWriter.Started { findrouter = true goto Admin } @@ -646,14 +646,14 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) // session init if BConfig.WebConfig.Session.SessionOn { var err error - context.Input.CruSession, err = GlobalSessions.SessionStart(w, r) + context.Input.CruSession, err = GlobalSessions.SessionStart(rw, r) if err != nil { Error(err) exception("503", context) return } defer func() { - context.Input.CruSession.SessionRelease(w) + context.Input.CruSession.SessionRelease(rw) }() } @@ -664,7 +664,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) context.Input.ParseFormOrMulitForm(BConfig.MaxMemory) } - if doFilter(BeforeRouter) { + if p.execFilter(context, BeforeRouter, urlPath) { goto Admin } @@ -703,7 +703,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) if findrouter { //execute middleware filters - if doFilter(BeforeExec) { + if p.execFilter(context, BeforeExec, urlPath) { goto Admin } isRunnable := false @@ -764,7 +764,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) execController.URLMapping() - if !w.started { + if !context.ResponseWriter.Started { //exec main logic switch runMethod { case "GET": @@ -790,7 +790,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) } //render template - if !w.started && context.Output.Status == 0 { + if !context.ResponseWriter.Started && context.Output.Status == 0 { if BConfig.WebConfig.AutoRender { if err := execController.Render(); err != nil { panic(err) @@ -804,12 +804,12 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) } //execute middleware filters - if doFilter(AfterExec) { + if p.execFilter(context, AfterExec, urlPath) { goto Admin } } - doFilter(FinishRouter) + p.execFilter(context, FinishRouter, urlPath) Admin: timeend := time.Since(starttime) @@ -842,7 +842,7 @@ Admin: // Call WriteHeader if status code has been set changed if context.Output.Status != 0 { - w.WriteHeader(context.Output.Status) + context.ResponseWriter.WriteHeader(context.Output.Status) } } @@ -878,30 +878,6 @@ func (p *ControllerRegister) recoverPanic(context *beecontext.Context) { } } -//responseWriter is a wrapper for the http.ResponseWriter -//started set to true if response was written to then don't execute other handler -type responseWriter struct { - http.ResponseWriter - started bool - status int -} - -// Write writes the data to the connection as part of an HTTP reply, -// and sets `started` to true. -// started means the response has sent out. -func (w *responseWriter) Write(p []byte) (int, error) { - w.started = true - return w.ResponseWriter.Write(p) -} - -// WriteHeader sends an HTTP response header with status code, -// and sets `started` to true. -func (w *responseWriter) WriteHeader(code int) { - w.status = code - w.started = true - w.ResponseWriter.WriteHeader(code) -} - func tourl(params map[string]string) string { if len(params) == 0 { return ""