From bf3f3065645606c4b51f2382eff0c6dcd334547d Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 12 Jul 2019 20:52:39 +0300 Subject: [PATCH] respect the WithoutBodyConsumptionOnUnmarshal on 'ctx.ReadForm' and 'ctx.FormValues' and on the new 'ctx.GetBody' method helper as requested at: #1297 --- _examples/README.md | 1 + _examples/http_request/read-many/main.go | 60 ++++++++++++++++++++++++ context/context.go | 57 +++++++++++++++++++--- 3 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 _examples/http_request/read-many/main.go diff --git a/_examples/README.md b/_examples/README.md index a5df53b39..06d1d632a 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -387,6 +387,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her - [Read Form](http_request/read-form/main.go) - [Read Custom per type](http_request/read-custom-per-type/main.go) - [Read Custom via Unmarshaler](http_request/read-custom-via-unmarshaler/main.go) +- [Read Many times](http_request/read-many/main.go) - [Upload/Read File](http_request/upload-file/main.go) - [Upload multiple files with an easy way](http_request/upload-files/main.go) - [Extract referrer from "referer" header or URL query parameter](http_request/extract-referer/main.go) diff --git a/_examples/http_request/read-many/main.go b/_examples/http_request/read-many/main.go new file mode 100644 index 000000000..ae02a9027 --- /dev/null +++ b/_examples/http_request/read-many/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "github.com/kataras/iris" +) + +func main() { + app := iris.New() + + app.Post("/", logAllBody, logJSON, logFormValues, func(ctx iris.Context) { + body, err := ctx.GetBody() + if err != nil { + ctx.Writef("error while reading the requested body: %v", err) + return + } + + if len(body) == 0 { + ctx.WriteString(`The body was empty +or iris.WithoutBodyConsumptionOnUnmarshal option is missing from app.Run. +Check the terminal window for any queries logs.`) + + } else { + ctx.WriteString("OK body is still:\n") + ctx.Write(body) + } + }) + + // With ctx.UnmarshalBody, ctx.ReadJSON, ctx.ReadXML, ctx.ReadForm, ctx.FormValues + // and ctx.GetBody methods the default golang and net/http behavior + // is to consume the readen data - they are not available on any next handlers in the chain - + // to change that behavior just pass the `WithoutBodyConsumptionOnUnmarshal` option. + app.Run(iris.Addr(":8080"), iris.WithoutBodyConsumptionOnUnmarshal) +} + +func logAllBody(ctx iris.Context) { + body, err := ctx.GetBody() + if err == nil && len(body) > 0 { + ctx.Application().Logger().Infof("logAllBody: %s", string(body)) + } + + ctx.Next() +} + +func logJSON(ctx iris.Context) { + var p interface{} + if err := ctx.ReadJSON(&p); err == nil { + ctx.Application().Logger().Infof("logJSON: %#+v", p) + } + + ctx.Next() +} + +func logFormValues(ctx iris.Context) { + values := ctx.FormValues() + if values != nil { + ctx.Application().Logger().Infof("logFormValues: %v", values) + } + + ctx.Next() +} diff --git a/context/context.go b/context/context.go index a7ff58ce7..bab70562e 100644 --- a/context/context.go +++ b/context/context.go @@ -563,6 +563,12 @@ type Context interface { // should be called before reading the request body from the client. SetMaxRequestBodySize(limitOverBytes int64) + // GetBody reads and returns the request body. + // The default behavior for the http request reader is to consume the data readen + // but you can change that behavior by passing the `WithoutBodyConsumptionOnUnmarshal` iris option. + // + // However, whenever you can use the `ctx.Request().Body` instead. + GetBody() ([]byte, error) // UnmarshalBody reads the request's body and binds it to a value or pointer of any type. // Examples of usage: context.ReadJSON, context.ReadXML. // @@ -2010,12 +2016,35 @@ func (ctx *context) form() (form map[string][]string, found bool) { } */ + var ( + bodyCopy []byte + keepBody = ctx.Application().ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal() + ) + + if keepBody { + // on POST, PUT and PATCH it will read the form values from request body otherwise from URL queries. + if m := ctx.Method(); m == "POST" || m == "PUT" || m == "PATCH" { + body, err := ioutil.ReadAll(ctx.request.Body) + if err != nil { + return nil, false + } + + bodyCopy = body + } else { // else we don't need this behavior. + keepBody = false + } + } + // ParseMultipartForm calls `request.ParseForm` automatically // therefore we don't need to call it here, although it doesn't hurt. // After one call to ParseMultipartForm or ParseForm, // subsequent calls have no effect, are idempotent. ctx.request.ParseMultipartForm(ctx.Application().ConfigurationReadOnly().GetPostMaxMemory()) + if keepBody { + ctx.request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyCopy)) + } + if form := ctx.request.Form; len(form) > 0 { return form, true } @@ -2288,6 +2317,26 @@ func (ctx *context) SetMaxRequestBodySize(limitOverBytes int64) { ctx.request.Body = http.MaxBytesReader(ctx.writer, ctx.request.Body, limitOverBytes) } +// GetBody reads and returns the request body. +// The default behavior for the http request reader is to consume the data readen +// but you can change that behavior by passing the `WithoutBodyConsumptionOnUnmarshal` iris option. +// +// However, whenever you can use the `ctx.Request().Body` instead. +func (ctx *context) GetBody() ([]byte, error) { + data, err := ioutil.ReadAll(ctx.request.Body) + if err != nil { + return nil, err + } + + if ctx.Application().ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal() { + // * remember, Request.Body has no Bytes(), we have to consume them first + // and after re-set them to the body, this is the only solution. + ctx.request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + } + + return data, nil +} + // UnmarshalBody reads the request's body and binds it to a value or pointer of any type // Examples of usage: context.ReadJSON, context.ReadXML. // @@ -2301,17 +2350,11 @@ func (ctx *context) UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) e return errors.New("unmarshal: empty body") } - rawData, err := ioutil.ReadAll(ctx.request.Body) + rawData, err := ctx.GetBody() if err != nil { return err } - if ctx.Application().ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal() { - // * remember, Request.Body has no Bytes(), we have to consume them first - // and after re-set them to the body, this is the only solution. - ctx.request.Body = ioutil.NopCloser(bytes.NewBuffer(rawData)) - } - // check if the v contains its own decode // in this case the v should be a pointer also, // but this is up to the user's custom Decode implementation*