From d5a9410e2ae51adb0150828536d4ea8ff1a02730 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Wed, 4 Jan 2017 21:29:58 +0200 Subject: [PATCH] Update to 6.0.3: Add an easy way to set a request body size limit per client or globally for newcomers --- HISTORY.md | 19 +++++++++++++++++++ README.md | 4 ++-- context.go | 19 +++++++++++++++++++ context_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ iris.go | 19 ++++++++++++------- 5 files changed, 93 insertions(+), 9 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index f0f3b702..21c6f621 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,25 @@ **How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`. +## 6.0.2 -> 6.0.3 + +- Give the users an easy to way to set a limit to the body size comes from the client, globally or per-route (useful when you want to disable/enable limit on certain clients). + +```go +// ... +const maxBodySize = 1 << 20 +// ... + +api := iris.New() + +api.Use(iris.LimitRequestBodySize(maxBodySize)) +// or do it manually under certain situations, +// inside the route's handler: +// ctx.SetMaxRequestBodySize(maxBodySize) + +// routes after +``` + ## 6.0.1 -> 6.0.2 - Fix subdomains (silly fix by checking the Request.Host vs Request.URL.Host) and add a more realistic test, as reported [here](https://github.com/kataras/iris/issues/574). diff --git a/README.md b/README.md index 18d0420d..466c7605 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@
-Releases +Releases Examples @@ -823,7 +823,7 @@ I recommend writing your API tests using this new library, [httpexpect](https:// Versioning ------------ -Current: **v6.0.2** +Current: **v6.0.3** Stable: **[v5/fasthttp](https://github.com/kataras/iris/tree/5.0.0)** diff --git a/context.go b/context.go index 7df6d055..dc637357 100644 --- a/context.go +++ b/context.go @@ -390,6 +390,25 @@ func (ctx *Context) FormFile(key string) (multipart.File, *multipart.FileHeader, // ------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------- +// NOTE: No default max body size http package has some built'n protection for DoS attacks +// See iris.Config.MaxBytesReader, https://github.com/golang/go/issues/2093#issuecomment-66057813 +// and https://github.com/golang/go/issues/2093#issuecomment-66057824 + +// LimitRequestBodySize is a middleware which sets a request body size limit for all next handlers +// should be registered before all other handlers +var LimitRequestBodySize = func(maxRequestBodySizeBytes int64) HandlerFunc { + return func(ctx *Context) { + ctx.SetMaxRequestBodySize(maxRequestBodySizeBytes) + ctx.Next() + } +} + +// SetMaxRequestBodySize sets a limit to the request body size +// should be called before reading the request body from the client +func (ctx *Context) SetMaxRequestBodySize(limitOverBytes int64) { + ctx.Request.Body = http.MaxBytesReader(ctx.ResponseWriter, ctx.Request.Body, limitOverBytes) +} + // BodyDecoder is an interface which any struct can implement in order to customize the decode action // from ReadJSON and ReadXML // diff --git a/context_test.go b/context_test.go index c9bf836d..f42a2922 100644 --- a/context_test.go +++ b/context_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "encoding/xml" "fmt" + "io/ioutil" "net/http" "net/url" "strconv" @@ -766,3 +767,43 @@ func TestTransactions(t *testing.T) { Body(). Equal(customErrorTemplateText) } + +func TestLimitRequestBodySize(t *testing.T) { + const maxBodySize = 1 << 20 + + api := iris.New() + + // or inside handler: ctx.SetMaxRequestBodySize(int64(maxBodySize)) + api.Use(iris.LimitRequestBodySize(maxBodySize)) + + api.Post("/", func(ctx *iris.Context) { + b, err := ioutil.ReadAll(ctx.Request.Body) + if len(b) > maxBodySize { + // this is a fatal error it should never happened. + t.Fatalf("body is larger (%d) than maxBodySize (%d) even if we add the LimitRequestBodySize middleware", len(b), maxBodySize) + } + // if is larger then send a bad request status + if err != nil { + ctx.WriteHeader(iris.StatusBadRequest) + ctx.Writef(err.Error()) + return + } + + ctx.Write(b) + }) + + // UseGlobal should be called at the end used to prepend handlers + // api.UseGlobal(iris.LimitRequestBodySize(int64(maxBodySize))) + + e := httptest.New(api, t) + + // test with small body + e.POST("/").WithBytes([]byte("ok")).Expect().Status(iris.StatusOK).Body().Equal("ok") + // test with equal to max body size limit + bsent := make([]byte, maxBodySize, maxBodySize) + e.POST("/").WithBytes(bsent).Expect().Status(iris.StatusOK).Body().Length().Equal(len(bsent)) + // test with larger body sent and wait for the custom response + largerBSent := make([]byte, maxBodySize+1, maxBodySize+1) + e.POST("/").WithBytes(largerBSent).Expect().Status(iris.StatusBadRequest).Body().Equal("http: request body too large") + +} diff --git a/iris.go b/iris.go index db5f8108..9e428a67 100644 --- a/iris.go +++ b/iris.go @@ -81,7 +81,7 @@ const ( // IsLongTermSupport flag is true when the below version number is a long-term-support version IsLongTermSupport = false // Version is the current version number of the Iris web framework - Version = "6.0.2" + Version = "6.0.3" banner = ` _____ _ |_ _| (_) @@ -862,7 +862,7 @@ func (s *Framework) UseTemplate(e template.Engine) *template.Loader { // UseGlobal registers Handler middleware to the beginning, prepends them instead of append // // Use it when you want to add a global middleware to all parties, to all routes in all subdomains -// It can be called after other, (but before .Listen of course) +// It should be called right before Listen functions func UseGlobal(handlers ...Handler) { Default.UseGlobal(handlers...) } @@ -870,7 +870,7 @@ func UseGlobal(handlers ...Handler) { // UseGlobalFunc registers HandlerFunc middleware to the beginning, prepends them instead of append // // Use it when you want to add a global middleware to all parties, to all routes in all subdomains -// It can be called after other, (but before .Listen of course) +// It should be called right before Listen functions func UseGlobalFunc(handlersFn ...HandlerFunc) { Default.UseGlobalFunc(handlersFn...) } @@ -878,17 +878,22 @@ func UseGlobalFunc(handlersFn ...HandlerFunc) { // UseGlobal registers Handler middleware to the beginning, prepends them instead of append // // Use it when you want to add a global middleware to all parties, to all routes in all subdomains -// It can be called after other, (but before .Listen of course) +// It should be called right before Listen functions func (s *Framework) UseGlobal(handlers ...Handler) { - for _, r := range s.mux.lookups { - r.middleware = append(handlers, r.middleware...) + if len(s.mux.lookups) > 0 { + for _, r := range s.mux.lookups { + r.middleware = append(handlers, r.middleware...) + } + return } + + s.Use(handlers...) } // UseGlobalFunc registers HandlerFunc middleware to the beginning, prepends them instead of append // // Use it when you want to add a global middleware to all parties, to all routes in all subdomains -// It can be called after other, (but before .Listen of course) +// It should be called right before Listen functions func (s *Framework) UseGlobalFunc(handlersFn ...HandlerFunc) { s.UseGlobal(convertToHandlers(handlersFn)...) }