Skip to content

Commit

Permalink
respect the WithoutBodyConsumptionOnUnmarshal on 'ctx.ReadForm' and '…
Browse files Browse the repository at this point in the history
…ctx.FormValues' and on the new 'ctx.GetBody' method helper as requested at: #1297
  • Loading branch information
kataras committed Jul 12, 2019
1 parent 0d4f247 commit bf3f306
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 7 deletions.
1 change: 1 addition & 0 deletions _examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
60 changes: 60 additions & 0 deletions _examples/http_request/read-many/main.go
Original file line number Diff line number Diff line change
@@ -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()
}
57 changes: 50 additions & 7 deletions context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
//
Expand All @@ -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*
Expand Down

0 comments on commit bf3f306

Please sign in to comment.