diff --git a/HISTORY.md b/HISTORY.md index 55394b15..95eed0fa 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -21,11 +21,32 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get github.com/kataras/iris@v11.2.0`. + # We, 24 July 2019 | v11.2.1 - https://github.com/kataras/iris/issues/1298 - https://github.com/kataras/iris/issues/1207 +## v11.2.2 + +Sessions as middleware: + +```go +import "github.com/kataras/iris/sessions" +// [...] + +app := iris.New() +sess := sessions.New(sessions.Config{...}) + +app.Get("/path", func(ctx iris.Context){ + session := sessions.Get(ctx) + // [work with session...] +}) +``` + +- Add `Session.Len() int` to return the total number of stored values/entries. +- Make `Context.HTML` and `Context.Text` to accept an optional, variadic, `args ...interface{}` input arg(s) too. + # Tu, 23 July 2019 | v11.2.0 Read about the new release at: https://dev.to/kataras/iris-version-11-2-released-22bc diff --git a/_examples/http_request/read-many/main.go b/_examples/http_request/read-many/main.go index ae02a902..b8d8cbf8 100644 --- a/_examples/http_request/read-many/main.go +++ b/_examples/http_request/read-many/main.go @@ -8,9 +8,11 @@ func main() { app := iris.New() app.Post("/", logAllBody, logJSON, logFormValues, func(ctx iris.Context) { - body, err := ctx.GetBody() + // body, err := ioutil.ReadAll(ctx.Request().Body) once or + body, err := ctx.GetBody() // as many times as you need. if err != nil { - ctx.Writef("error while reading the requested body: %v", err) + ctx.StatusCode(iris.StatusInternalServerError) + ctx.WriteString(err.Error()) return } diff --git a/context/context.go b/context/context.go index 0d8bd445..11eb0364 100644 --- a/context/context.go +++ b/context/context.go @@ -1743,12 +1743,29 @@ func (ctx *context) Header(name string, value string) { ctx.writer.Header().Add(name, value) } +const contentTypeContextKey = "_iris_content_type" + +func (ctx *context) contentTypeOnce(cType string, charset string) { + if charset == "" { + charset = ctx.Application().ConfigurationReadOnly().GetCharset() + } + + cType += "; charset=" + charset + + ctx.Values().Set(contentTypeContextKey, cType) + ctx.writer.Header().Set(ContentTypeHeaderKey, cType) +} + // ContentType sets the response writer's header key "Content-Type" to the 'cType'. func (ctx *context) ContentType(cType string) { if cType == "" { return } + if _, wroteOnce := ctx.Values().GetEntry(contentTypeContextKey); wroteOnce { + return + } + // 1. if it's path or a filename or an extension, // then take the content type from that if strings.Contains(cType, ".") { @@ -2014,20 +2031,19 @@ func (ctx *context) form() (form map[string][]string, found bool) { */ var ( - bodyCopy []byte keepBody = ctx.Application().ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal() + bodyCopy []byte ) 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 { + bodyCopy, _ = ctx.GetBody() + if len(bodyCopy) == 0 { return nil, false } - - bodyCopy = body - } else { // else we don't need this behavior. + // ctx.request.Body = ioutil.NopCloser(io.TeeReader(ctx.request.Body, buf)) + } else { keepBody = false } } @@ -2036,11 +2052,13 @@ func (ctx *context) form() (form map[string][]string, found bool) { // 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()) - + err := ctx.request.ParseMultipartForm(ctx.Application().ConfigurationReadOnly().GetPostMaxMemory()) if keepBody { ctx.request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyCopy)) } + if err != nil && err != http.ErrNotMultipart { + return nil, false + } if form := ctx.request.Form; len(form) > 0 { return form, true @@ -2863,12 +2881,138 @@ const ( ContentTextHeaderValue = "text/plain" // ContentXMLHeaderValue header value for XML data. ContentXMLHeaderValue = "text/xml" + // ContentXMLUnreadableHeaderValue obselete header value for XML. + ContentXMLUnreadableHeaderValue = "application/xml" // ContentMarkdownHeaderValue custom key/content type, the real is the text/html. ContentMarkdownHeaderValue = "text/markdown" // ContentYAMLHeaderValue header value for YAML data. ContentYAMLHeaderValue = "application/x-yaml" + // ContentFormHeaderValue header value for post form data. + ContentFormHeaderValue = "application/x-www-form-urlencoded" + // ContentFormMultipartHeaderValue header value for post multipart form data. + ContentFormMultipartHeaderValue = "multipart/form-data" ) +// TODO: +// const negotitationContextKey = "_iris_accept_negotitation_builder" + +// func (ctx *context) Accept() *Negotitation { +// if n := ctx.Values().Get(negotitationContextKey); n != nil { +// return n.(*Negotitation) +// } + +// n := new(Negotitation) +// n.accept = parseHeader(ctx.GetHeader("Accept")) +// n.charset = parseHeader(ctx.GetHeader("Accept-Charset")) + +// ctx.Values().Set(negotitationContextKey, n) +// return n +// } + +// func parseHeader(headerValue string) []string { +// in := strings.Split(headerValue, ",") +// out := make([]string, 0, len(in)) + +// for _, value := range in { +// // remove any spaces and quality values such as ;q=0.8. +// // */* or * means accept everything. +// v := strings.TrimSpace(strings.Split(value, ";")[0]) +// if v != "" { +// out = append(out, v) +// } +// } + +// return out +// } + +// // Negotitation builds the accepted mime types and charset +// // +// // and "Accept-Charset" headers respectfully. +// // The default values are set by the client side, server can append or override those. +// // The end result will be challenged with runtime preffered set of content types and charsets. +// // +// // See `Negotitate`. +// type Negotitation struct { +// // initialized with "Accept" header values. +// accept []string +// // initialized with "Accept-Charset" and if was empty then the +// // application's default (which defaults to utf-8). +// charset []string + +// // To support override in request life cycle. +// // We need slice when data is the same format +// // for one or more mime types, +// // i.e text/xml and obselete application/xml. +// lastAccept []string +// lastCharset []string +// } + +// func (n *Negotitation) Override() *Negotitation { +// // when called first. +// n.accept = n.accept[0:0] +// n.charset = n.charset[0:0] + +// // when called after. +// if len(n.lastAccept) > 0 { +// n.accept = append(n.accept, n.lastAccept...) +// n.lastAccept = n.lastAccept[0:0] +// } + +// if len(n.lastCharset) > 0 { +// n.charset = append(n.charset, n.lastCharset...) +// n.lastCharset = n.lastCharset[0:0] +// } + +// return n +// } + +// func (n *Negotitation) MIME(mimeType ...string) *Negotitation { +// n.lastAccept = mimeType +// n.accept = append(n.accept, mimeType...) +// return n +// } + +// func (n *Negotitation) JSON() *Negotitation { +// return n.MIME(ContentJSONHeaderValue) +// } + +// func (n *Negotitation) XML() *Negotitation { +// return n.MIME(ContentXMLHeaderValue, ContentXMLUnreadableHeaderValue) +// } + +// func (n *Negotitation) HTML() *Negotitation { +// return n.MIME(ContentHTMLHeaderValue) +// } + +// func (n *Negotitation) Charset(charset ...string) *Negotitation { +// n.lastCharset = charset +// n.charset = append(n.charset, charset...) + +// return n +// } + +// func (n *Negotitation) build(preferences []string) (contentType, charset string) { +// return +// } + +// // https://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html +// // https://developer.mozilla.org/en-US/docs/tag/Content%20Negotiation +// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept +// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values +// func (ctx *context) Negotiate(v interface{}, preferences ...string) (int, error) { +// contentType, charset := ctx.Accept().build(preferences) + +// // // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset +// // If the server cannot serve any matching character set, +// // it can theoretically send back a 406 (Not Acceptable) error code. +// ctx.contentTypeOnce(contentType, charset) + +// switch contentType { + +// } +// return -1, nil +// } + // Binary writes out the raw bytes as binary data. func (ctx *context) Binary(data []byte) (int, error) { ctx.ContentType(ContentBinaryHeaderValue)