mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
Better gzip managment, align with https://github.com/kataras/iris/issues/361 . OnError on Parties: https://github.com/kataras/iris/issues/35
This commit is contained in:
parent
56bd511427
commit
c6f5406c3b
65
context.go
65
context.go
|
@ -37,6 +37,12 @@ const (
|
|||
contentType = "Content-Type"
|
||||
// ContentLength represents the header["Content-Length"]
|
||||
contentLength = "Content-Length"
|
||||
// contentEncodingHeader represents the header["Content-Encoding"]
|
||||
contentEncodingHeader = "Content-Encoding"
|
||||
// varyHeader represents the header "Vary"
|
||||
varyHeader = "Vary"
|
||||
// acceptEncodingHeader represents the header key & value "Accept-Encoding"
|
||||
acceptEncodingHeader = "Accept-Encoding"
|
||||
// ContentHTML is the string of text/html response headers
|
||||
contentHTML = "text/html"
|
||||
// ContentBinary header value for binary data.
|
||||
|
@ -111,36 +117,6 @@ func (ctx *Context) GetRequestCtx() *fasthttp.RequestCtx {
|
|||
return ctx.RequestCtx
|
||||
}
|
||||
|
||||
// Reset resets the Context with a given domain.Response and domain.Request
|
||||
// the context is ready-to-use after that, just like a new Context
|
||||
// I use it for zero rellocation memory
|
||||
func (ctx *Context) Reset(reqCtx *fasthttp.RequestCtx) {
|
||||
ctx.Params = ctx.Params[0:0]
|
||||
ctx.session = nil
|
||||
ctx.middleware = nil
|
||||
ctx.RequestCtx = reqCtx
|
||||
}
|
||||
|
||||
// Clone use that method if you want to use the context inside a goroutine
|
||||
func (ctx *Context) Clone() context.IContext {
|
||||
var cloneContext = *ctx
|
||||
cloneContext.pos = 0
|
||||
|
||||
//copy params
|
||||
p := ctx.Params
|
||||
cpP := make(PathParameters, len(p))
|
||||
copy(cpP, p)
|
||||
cloneContext.Params = cpP
|
||||
//copy middleware
|
||||
m := ctx.middleware
|
||||
cpM := make(Middleware, len(m))
|
||||
copy(cpM, m)
|
||||
cloneContext.middleware = cpM
|
||||
|
||||
// we don't copy the sessionStore for more than one reasons...
|
||||
return &cloneContext
|
||||
}
|
||||
|
||||
// Do calls the first handler only, it's like Next with negative pos, used only on Router&MemoryRouter
|
||||
func (ctx *Context) Do() {
|
||||
ctx.pos = 0
|
||||
|
@ -521,11 +497,27 @@ func (ctx *Context) Write(format string, a ...interface{}) {
|
|||
ctx.RequestCtx.WriteString(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (ctx *Context) clientAllowsGzip() bool {
|
||||
if h := ctx.RequestHeader(acceptEncodingHeader); h != "" {
|
||||
for _, v := range strings.Split(h, ";") {
|
||||
if strings.Contains(v, "gzip") { // we do Contains because sometimes browsers has the q=, we don't use it atm. || strings.Contains(v,"deflate"){
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Gzip accepts bytes, which are compressed to gzip format and sent to the client
|
||||
func (ctx *Context) Gzip(b []byte, status int) {
|
||||
_, err := fasthttp.WriteGzip(ctx.RequestCtx.Response.BodyWriter(), b)
|
||||
if err == nil {
|
||||
ctx.RequestCtx.Response.Header.Add("Content-Encoding", "gzip")
|
||||
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
|
||||
|
||||
if ctx.clientAllowsGzip() {
|
||||
_, err := fasthttp.WriteGzip(ctx.RequestCtx.Response.BodyWriter(), b)
|
||||
if err == nil {
|
||||
ctx.SetHeader(contentEncodingHeader, "gzip")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -630,8 +622,9 @@ func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime
|
|||
ctx.RequestCtx.Response.Header.Set(lastModified, modtime.UTC().Format(config.TimeFormat))
|
||||
ctx.RequestCtx.SetStatusCode(StatusOK)
|
||||
var out io.Writer
|
||||
if gzipCompression {
|
||||
ctx.RequestCtx.Response.Header.Add("Content-Encoding", "gzip")
|
||||
if gzipCompression && ctx.clientAllowsGzip() {
|
||||
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
|
||||
ctx.SetHeader(contentEncodingHeader, "gzip")
|
||||
gzipWriter := gzipWriterPool.Get().(*gzip.Writer)
|
||||
gzipWriter.Reset(ctx.RequestCtx.Response.BodyWriter())
|
||||
defer gzipWriter.Close()
|
||||
|
@ -651,6 +644,7 @@ func (ctx *Context) ServeContent(content io.ReadSeeker, filename string, modtime
|
|||
// gzipCompression (bool)
|
||||
//
|
||||
// You can define your own "Content-Type" header also, after this function call
|
||||
// This function doesn't implement resuming, use ctx.RequestCtx.SendFile/fasthttp.ServeFileUncompressed(ctx.RequestCtx,path)/ServeFile(ctx.RequestCtx,path) instead
|
||||
func (ctx *Context) ServeFile(filename string, gzipCompression bool) error {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
|
@ -673,6 +667,7 @@ func (ctx *Context) ServeFile(filename string, gzipCompression bool) error {
|
|||
//
|
||||
// You can define your own "Content-Type" header also, after this function call
|
||||
// for example: ctx.Response.Header.Set("Content-Type","thecontent/type")
|
||||
// This function doesn't implement resuming, use ctx.RequestCtx.SendFile instead
|
||||
func (ctx *Context) SendFile(filename string, destinationName string) error {
|
||||
err := ctx.ServeFile(filename, false)
|
||||
if err != nil {
|
||||
|
|
|
@ -90,9 +90,7 @@ type (
|
|||
Session() Session
|
||||
SessionDestroy()
|
||||
Log(string, ...interface{})
|
||||
Reset(*fasthttp.RequestCtx)
|
||||
GetRequestCtx() *fasthttp.RequestCtx
|
||||
Clone() IContext
|
||||
Do()
|
||||
Next()
|
||||
StopExecution()
|
||||
|
|
|
@ -21,30 +21,6 @@ import (
|
|||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
func TestContextReset(t *testing.T) {
|
||||
var context Context
|
||||
context.Params = PathParameters{PathParameter{Key: "testkey", Value: "testvalue"}}
|
||||
context.Reset(nil)
|
||||
if len(context.Params) > 0 {
|
||||
t.Fatalf("Expecting to have %d params but got: %d", 0, len(context.Params))
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextClone(t *testing.T) {
|
||||
var context Context
|
||||
context.Params = PathParameters{
|
||||
PathParameter{Key: "testkey", Value: "testvalue"},
|
||||
PathParameter{Key: "testkey2", Value: "testvalue2"},
|
||||
}
|
||||
c := context.Clone()
|
||||
if v := c.Param("testkey"); v != context.Param("testkey") {
|
||||
t.Fatalf("Expecting to have parameter value: %s but got: %s", context.Param("testkey"), v)
|
||||
}
|
||||
if v := c.Param("testkey2"); v != context.Param("testkey2") {
|
||||
t.Fatalf("Expecting to have parameter value: %s but got: %s", context.Param("testkey2"), v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextDoNextStop(t *testing.T) {
|
||||
var context Context
|
||||
ok := false
|
||||
|
|
35
http.go
35
http.go
|
@ -311,9 +311,8 @@ func (s *Server) Port() int {
|
|||
return 443
|
||||
}
|
||||
return 80
|
||||
} else {
|
||||
return p
|
||||
}
|
||||
return p
|
||||
}
|
||||
if s.Config.AutoTLS {
|
||||
return 443
|
||||
|
@ -559,12 +558,10 @@ func (s *ServerList) CloseAll() (err error) {
|
|||
// OpenAll starts all servers
|
||||
// returns the first error happens to one of these servers
|
||||
// if one server gets error it closes the previous servers and exits from this process
|
||||
func (s *ServerList) OpenAll() error {
|
||||
func (s *ServerList) OpenAll(reqHandler fasthttp.RequestHandler) error {
|
||||
l := len(s.servers) - 1
|
||||
h := s.mux.ServeRequest()
|
||||
for i := range s.servers {
|
||||
|
||||
if err := s.servers[i].Open(h); err != nil {
|
||||
if err := s.servers[i].Open(reqHandler); err != nil {
|
||||
time.Sleep(2 * time.Second)
|
||||
// for any case,
|
||||
// we don't care about performance on initialization,
|
||||
|
@ -1332,7 +1329,6 @@ type (
|
|||
}
|
||||
|
||||
serveMux struct {
|
||||
cPool *sync.Pool
|
||||
tree *muxTree
|
||||
lookups []*route
|
||||
|
||||
|
@ -1355,9 +1351,8 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
func newServeMux(contextPool sync.Pool, logger *logger.Logger) *serveMux {
|
||||
func newServeMux(logger *logger.Logger) *serveMux {
|
||||
mux := &serveMux{
|
||||
cPool: &contextPool,
|
||||
lookups: make([]*route, 0),
|
||||
errorHandlers: make(map[int]Handler, 0),
|
||||
hostname: config.DefaultServerHostname, // these are changing when the server is up
|
||||
|
@ -1485,16 +1480,16 @@ func (mux *serveMux) lookup(routeName string) *route {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (mux *serveMux) ServeRequest() fasthttp.RequestHandler {
|
||||
func (mux *serveMux) Handler() HandlerFunc {
|
||||
|
||||
// initialize the router once
|
||||
mux.build()
|
||||
// optimize this once once, we could do that: context.RequestPath(mux.escapePath), but we lose some nanoseconds on if :)
|
||||
getRequestPath := func(reqCtx *fasthttp.RequestCtx) string {
|
||||
return utils.BytesToString(reqCtx.Path())
|
||||
getRequestPath := func(ctx *Context) string {
|
||||
return utils.BytesToString(ctx.Path()) //string(ctx.Path()[:]) // a little bit of memory allocation, old method used: BytesToString, If I see the benchmarks get low I will change it back to old, but this way is safer.
|
||||
}
|
||||
if !mux.escapePath {
|
||||
getRequestPath = func(reqCtx *fasthttp.RequestCtx) string { return utils.BytesToString(reqCtx.RequestURI()) }
|
||||
getRequestPath = func(ctx *Context) string { return utils.BytesToString(ctx.RequestCtx.RequestURI()) }
|
||||
}
|
||||
|
||||
methodEqual := func(treeMethod []byte, reqMethod []byte) bool {
|
||||
|
@ -1511,14 +1506,11 @@ func (mux *serveMux) ServeRequest() fasthttp.RequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
return func(reqCtx *fasthttp.RequestCtx) {
|
||||
context := mux.cPool.Get().(*Context)
|
||||
context.Reset(reqCtx)
|
||||
|
||||
routePath := getRequestPath(reqCtx)
|
||||
return func(context *Context) {
|
||||
routePath := getRequestPath(context)
|
||||
tree := mux.tree
|
||||
for tree != nil {
|
||||
if !methodEqual(tree.method, reqCtx.Method()) {
|
||||
if !methodEqual(tree.method, context.Method()) {
|
||||
// we break any CORS OPTIONS method
|
||||
// but for performance reasons if user wants http method OPTIONS to be served
|
||||
// then must register it with .Options(...)
|
||||
|
@ -1555,9 +1547,8 @@ func (mux *serveMux) ServeRequest() fasthttp.RequestHandler {
|
|||
context.middleware = middleware
|
||||
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
|
||||
context.Do()
|
||||
mux.cPool.Put(context)
|
||||
return
|
||||
} else if mustRedirect && mux.correctPath && !bytes.Equal(reqCtx.Method(), methodConnectBytes) {
|
||||
} else if mustRedirect && mux.correctPath && !bytes.Equal(context.Method(), methodConnectBytes) {
|
||||
|
||||
reqPath := routePath
|
||||
pathLen := len(reqPath)
|
||||
|
@ -1582,7 +1573,6 @@ func (mux *serveMux) ServeRequest() fasthttp.RequestHandler {
|
|||
note := "<a href=\"" + utils.HTMLEscape(urlToRedirect) + "\">Moved Permanently</a>.\n"
|
||||
context.Write(note)
|
||||
}
|
||||
mux.cPool.Put(context)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -1590,6 +1580,5 @@ func (mux *serveMux) ServeRequest() fasthttp.RequestHandler {
|
|||
break
|
||||
}
|
||||
mux.fireError(StatusNotFound, context)
|
||||
mux.cPool.Put(context)
|
||||
}
|
||||
}
|
||||
|
|
141
iris.go
141
iris.go
|
@ -153,8 +153,6 @@ type (
|
|||
UseTemplate(TemplateEngine) *TemplateEngineLocation
|
||||
UseGlobal(...Handler)
|
||||
UseGlobalFunc(...HandlerFunc)
|
||||
OnError(int, HandlerFunc)
|
||||
EmitError(int, *Context)
|
||||
Lookup(string) Route
|
||||
Lookups() []Route
|
||||
Path(string, ...interface{}) string
|
||||
|
@ -169,6 +167,7 @@ type (
|
|||
// Implements the FrameworkAPI
|
||||
Framework struct {
|
||||
*muxAPI
|
||||
contextPool sync.Pool
|
||||
Config *config.Iris
|
||||
gzipWriterPool sync.Pool // used for several methods, usually inside context
|
||||
sessions *sessionsManager
|
||||
|
@ -225,7 +224,7 @@ func New(cfg ...config.Iris) *Framework {
|
|||
// set the websocket server
|
||||
s.Websocket = NewWebsocketServer(s.Config.Websocket)
|
||||
// set the servemux, which will provide us the public API also, with its context pool
|
||||
mux := newServeMux(sync.Pool{New: func() interface{} { return &Context{framework: s} }}, s.Logger)
|
||||
mux := newServeMux(s.Logger)
|
||||
mux.onLookup = s.Plugins.DoPreLookup
|
||||
// set the public router API (and party)
|
||||
s.muxAPI = &muxAPI{mux: mux, relativePath: "/"}
|
||||
|
@ -286,6 +285,29 @@ func (s *Framework) initialize() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Framework) acquireCtx(reqCtx *fasthttp.RequestCtx) *Context {
|
||||
v := s.contextPool.Get()
|
||||
var ctx *Context
|
||||
if v == nil {
|
||||
ctx = &Context{
|
||||
RequestCtx: reqCtx,
|
||||
framework: s,
|
||||
}
|
||||
} else {
|
||||
ctx = v.(*Context)
|
||||
ctx.Params = ctx.Params[0:0]
|
||||
ctx.RequestCtx = reqCtx
|
||||
ctx.middleware = nil
|
||||
ctx.session = nil
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (s *Framework) releaseCtx(ctx *Context) {
|
||||
s.contextPool.Put(ctx)
|
||||
}
|
||||
|
||||
// Go starts the iris station, listens to all registered servers, and prepare only if Virtual
|
||||
func Go() error {
|
||||
return Default.Go()
|
||||
|
@ -295,8 +317,14 @@ func Go() error {
|
|||
func (s *Framework) Go() error {
|
||||
s.initialize()
|
||||
s.Plugins.DoPreListen(s)
|
||||
|
||||
if firstErr := s.Servers.OpenAll(); firstErr != nil {
|
||||
// build the fasthttp handler to bind it to the servers
|
||||
h := s.mux.Handler()
|
||||
reqHandler := func(reqCtx *fasthttp.RequestCtx) {
|
||||
ctx := s.acquireCtx(reqCtx)
|
||||
h(ctx)
|
||||
s.releaseCtx(ctx)
|
||||
}
|
||||
if firstErr := s.Servers.OpenAll(reqHandler); firstErr != nil {
|
||||
return firstErr
|
||||
}
|
||||
|
||||
|
@ -665,30 +693,6 @@ func (s *Framework) UseGlobalFunc(handlersFn ...HandlerFunc) {
|
|||
s.UseGlobal(convertToHandlers(handlersFn)...)
|
||||
}
|
||||
|
||||
// OnError registers a custom http error handler
|
||||
func OnError(statusCode int, handlerFn HandlerFunc) {
|
||||
Default.OnError(statusCode, handlerFn)
|
||||
}
|
||||
|
||||
// EmitError fires a custom http error handler to the client
|
||||
//
|
||||
// if no custom error defined with this statuscode, then iris creates one, and once at runtime
|
||||
func EmitError(statusCode int, ctx *Context) {
|
||||
Default.EmitError(statusCode, ctx)
|
||||
}
|
||||
|
||||
// OnError registers a custom http error handler
|
||||
func (s *Framework) OnError(statusCode int, handlerFn HandlerFunc) {
|
||||
s.mux.registerError(statusCode, handlerFn)
|
||||
}
|
||||
|
||||
// EmitError fires a custom http error handler to the client
|
||||
//
|
||||
// if no custom error defined with this statuscode, then iris creates one, and once at runtime
|
||||
func (s *Framework) EmitError(statusCode int, ctx *Context) {
|
||||
s.mux.fireError(statusCode, ctx)
|
||||
}
|
||||
|
||||
// Lookup returns a registed route by its name
|
||||
func Lookup(routeName string) Route {
|
||||
return Default.Lookup(routeName)
|
||||
|
@ -1051,6 +1055,10 @@ type (
|
|||
|
||||
// templates
|
||||
Layout(string) MuxAPI // returns itself
|
||||
|
||||
// errors
|
||||
OnError(int, HandlerFunc)
|
||||
EmitError(int, *Context)
|
||||
}
|
||||
|
||||
muxAPI struct {
|
||||
|
@ -1839,3 +1847,78 @@ func (api *muxAPI) Layout(tmplLayoutFile string) MuxAPI {
|
|||
})
|
||||
return api
|
||||
}
|
||||
|
||||
// OnError registers a custom http error handler
|
||||
func OnError(statusCode int, handlerFn HandlerFunc) {
|
||||
Default.OnError(statusCode, handlerFn)
|
||||
}
|
||||
|
||||
// EmitError fires a custom http error handler to the client
|
||||
//
|
||||
// if no custom error defined with this statuscode, then iris creates one, and once at runtime
|
||||
func EmitError(statusCode int, ctx *Context) {
|
||||
Default.EmitError(statusCode, ctx)
|
||||
}
|
||||
|
||||
// OnError registers a custom http error handler
|
||||
func (api *muxAPI) OnError(statusCode int, handlerFn HandlerFunc) {
|
||||
|
||||
path := strings.Replace(api.relativePath, "//", "/", -1) // fix the path if double //
|
||||
staticPath := path
|
||||
// find the static path (on Party the path should be ALWAYS a static path, as we all know,
|
||||
// but do this check for any case)
|
||||
dynamicPathIdx := strings.IndexByte(path, parameterStartByte) // check for /mypath/:param
|
||||
|
||||
if dynamicPathIdx == -1 {
|
||||
dynamicPathIdx = strings.IndexByte(path, matchEverythingByte) // check for /mypath/*param
|
||||
}
|
||||
|
||||
if dynamicPathIdx > 1 { //yes after / and one character more ( /*param or /:param will break the root path, and this is not allowed even on error handlers).
|
||||
staticPath = api.relativePath[0:dynamicPathIdx]
|
||||
}
|
||||
|
||||
if staticPath == "/" {
|
||||
api.mux.registerError(statusCode, handlerFn) // register the user-specific error message, as the global error handler, for now.
|
||||
return
|
||||
}
|
||||
|
||||
//after this, we have more than one error handler for one status code, and that's dangerous some times, but use it for non-globals error catching by your own risk
|
||||
// NOTES:
|
||||
// subdomains error will not work if same path of a non-subdomain (maybe a TODO for later)
|
||||
// errors for parties should be registered from the biggest path length to the smaller.
|
||||
|
||||
// get the previous
|
||||
prevErrHandler := api.mux.errorHandlers[statusCode]
|
||||
if prevErrHandler == nil {
|
||||
/*
|
||||
make a new one with the standard error message,
|
||||
this will be used as the last handler if no other error handler catches the error (by prefix(?))
|
||||
*/
|
||||
prevErrHandler = HandlerFunc(func(ctx *Context) {
|
||||
ctx.ResetBody()
|
||||
ctx.SetStatusCode(statusCode)
|
||||
ctx.SetBodyString(statusText[statusCode])
|
||||
})
|
||||
}
|
||||
|
||||
func(statusCode int, staticPath string, prevErrHandler Handler, newHandler Handler) { // to separate the logic
|
||||
errHandler := HandlerFunc(func(ctx *Context) {
|
||||
if strings.HasPrefix(ctx.PathString(), staticPath) { // yes the user should use OnError from longest to lower static path's length in order this to work, so we can find another way, like a builder on the end.
|
||||
newHandler.Serve(ctx)
|
||||
return
|
||||
}
|
||||
// serve with the user-specific global ("/") pure iris.OnError receiver Handler or the standar handler if OnError called only from inside a no-relative Party.
|
||||
prevErrHandler.Serve(ctx)
|
||||
})
|
||||
|
||||
api.mux.registerError(statusCode, errHandler)
|
||||
}(statusCode, staticPath, prevErrHandler, handlerFn)
|
||||
|
||||
}
|
||||
|
||||
// EmitError fires a custom http error handler to the client
|
||||
//
|
||||
// if no custom error defined with this statuscode, then iris creates one, and once at runtime
|
||||
func (api *muxAPI) EmitError(statusCode int, ctx *Context) {
|
||||
api.mux.fireError(statusCode, ctx)
|
||||
}
|
||||
|
|
|
@ -151,12 +151,13 @@ func (r *responseEngineMap) render(ctx *Context, obj interface{}, options ...map
|
|||
}
|
||||
ctx.SetContentType(ctype)
|
||||
|
||||
if gzipEnabled {
|
||||
if gzipEnabled && ctx.clientAllowsGzip() {
|
||||
_, err := fasthttp.WriteGzip(ctx.RequestCtx.Response.BodyWriter(), finalResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Response.Header.Add("Content-Encoding", "gzip")
|
||||
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
|
||||
ctx.SetHeader(contentEncodingHeader, "gzip")
|
||||
} else {
|
||||
ctx.Response.SetBody(finalResult)
|
||||
}
|
||||
|
|
|
@ -201,8 +201,9 @@ func (t *templateEngineWrapper) execute(ctx *Context, filename string, binding i
|
|||
ctx.SetContentType(contentHTML + "; charset=" + charset)
|
||||
|
||||
var out io.Writer
|
||||
if gzipEnabled {
|
||||
ctx.Response.Header.Add("Content-Encoding", "gzip")
|
||||
if gzipEnabled && ctx.clientAllowsGzip() {
|
||||
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
|
||||
ctx.SetHeader(contentEncodingHeader, "gzip")
|
||||
|
||||
gzipWriter := ctx.framework.AcquireGzip(ctx.Response.BodyWriter())
|
||||
defer ctx.framework.ReleaseGzip(gzipWriter)
|
||||
|
|
Loading…
Reference in New Issue
Block a user