mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
Implementation of https://github.com/kataras/iris/issues/412 , as requested. Read HISTORY for a code snippet
This commit is contained in:
parent
4bf1d65877
commit
f4b4dd0275
27
HISTORY.md
27
HISTORY.md
|
@ -2,6 +2,33 @@
|
||||||
|
|
||||||
**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`.
|
**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`.
|
||||||
|
|
||||||
|
## 4.4.0 -> 4.4.1
|
||||||
|
|
||||||
|
- **NEW**: Template PreRenders, as requested [here](https://github.com/kataras/iris/issues/412).
|
||||||
|
|
||||||
|
```go
|
||||||
|
// ...
|
||||||
|
iris.UsePreRender(func(ctx *iris.Context, filename string, binding interface{}, options ...map[string]interface{}) bool {
|
||||||
|
// put the 'Error' binding here, for the shake of the test
|
||||||
|
if b, isMap := binding.(map[string]interface{}); isMap {
|
||||||
|
b["Error"] = "error!"
|
||||||
|
}
|
||||||
|
// true= continue to the next PreRender
|
||||||
|
// false= do not continue to the next PreRender
|
||||||
|
// * but the actual Render will be called at any case *
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
iris.Get("/", func(ctx *Context) {
|
||||||
|
ctx.Render("hi.html", map[string]interface{}{"Username": "anybody"})
|
||||||
|
// hi.html: <h1>HI {{.Username}}. Error: {{.Error}}</h1>
|
||||||
|
})
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 4.3.0 -> 4.4.0
|
## 4.3.0 -> 4.4.0
|
||||||
|
|
||||||
**NOTE**: For normal users this update offers nothing, read that only if you run Iris behind a proxy or balancer like `nginx` or you need to serve using a custom `net.Listener`.
|
**NOTE**: For normal users this update offers nothing, read that only if you run Iris behind a proxy or balancer like `nginx` or you need to serve using a custom `net.Listener`.
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%204.4.0%20-blue.svg?style=flat-square" alt="Releases"></a>
|
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%204.4.1%20-blue.svg?style=flat-square" alt="Releases"></a>
|
||||||
|
|
||||||
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
|
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
|
||||||
|
|
||||||
|
@ -197,7 +197,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
|
||||||
Versioning
|
Versioning
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Current: **v4.4.0**
|
Current: **v4.4.1**
|
||||||
|
|
||||||
> Iris is an active project
|
> Iris is an active project
|
||||||
|
|
||||||
|
@ -213,6 +213,7 @@ Todo
|
||||||
- [x] Implement, even, a better way to manage configuration/options, devs will be able to set their own custom options inside there. ` I'm thinking of something the last days, but it will have breaking changes. `
|
- [x] Implement, even, a better way to manage configuration/options, devs will be able to set their own custom options inside there. ` I'm thinking of something the last days, but it will have breaking changes. `
|
||||||
- [x] Implement an internal updater, as requested [here](https://github.com/kataras/iris/issues/401).
|
- [x] Implement an internal updater, as requested [here](https://github.com/kataras/iris/issues/401).
|
||||||
- [x] Support of using native servers and net.Listener instead of Iris' defined.[*](https://github.com/kataras/iris/issues/438)
|
- [x] Support of using native servers and net.Listener instead of Iris' defined.[*](https://github.com/kataras/iris/issues/438)
|
||||||
|
- [x] Add context processors(PreRenders).[*](https://github.com/kataras/iris/issues/412)
|
||||||
|
|
||||||
Iris is a **Community-Driven** Project, waiting for your suggestions and [feature requests](https://github.com/kataras/iris/issues?utf8=%E2%9C%93&q=label%3A%22feature%20request%22)!
|
Iris is a **Community-Driven** Project, waiting for your suggestions and [feature requests](https://github.com/kataras/iris/issues?utf8=%E2%9C%93&q=label%3A%22feature%20request%22)!
|
||||||
|
|
||||||
|
|
|
@ -596,7 +596,7 @@ func (ctx *Context) RenderTemplateSource(status int, src string, binding interfa
|
||||||
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engines
|
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engines
|
||||||
func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) (err error) {
|
func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) (err error) {
|
||||||
if strings.IndexByte(name, '.') > -1 { //we have template
|
if strings.IndexByte(name, '.') > -1 { //we have template
|
||||||
err = ctx.framework.templates.render(ctx, name, binding, options...)
|
err = ctx.framework.templates.renderFile(ctx, name, binding, options...)
|
||||||
} else {
|
} else {
|
||||||
err = ctx.renderSerialized(name, binding, options...)
|
err = ctx.renderSerialized(name, binding, options...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -657,5 +657,48 @@ func TestContextRenderRest(t *testing.T) {
|
||||||
markdownT := e.GET("/markdown").Expect().Status(StatusOK)
|
markdownT := e.GET("/markdown").Expect().Status(StatusOK)
|
||||||
markdownT.Header("Content-Type").Equal("text/html; charset=UTF-8")
|
markdownT.Header("Content-Type").Equal("text/html; charset=UTF-8")
|
||||||
markdownT.Body().Equal("<h1>" + markdownContents[2:] + "</h1>\n")
|
markdownT.Body().Equal("<h1>" + markdownContents[2:] + "</h1>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextPreRender(t *testing.T) {
|
||||||
|
initDefault()
|
||||||
|
errMsg1 := "thereIsAnError"
|
||||||
|
UsePreRender(func(ctx *Context, src string, binding interface{}, options ...map[string]interface{}) bool {
|
||||||
|
// put the 'Error' binding here, for the shake of the test
|
||||||
|
if b, isMap := binding.(map[string]interface{}); isMap {
|
||||||
|
b["Error"] = errMsg1
|
||||||
|
}
|
||||||
|
// continue to the next prerender
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
errMsg2 := "thereIsASecondError"
|
||||||
|
UsePreRender(func(ctx *Context, src string, binding interface{}, options ...map[string]interface{}) bool {
|
||||||
|
// put the 'Error' binding here, for the shake of the test
|
||||||
|
if b, isMap := binding.(map[string]interface{}); isMap {
|
||||||
|
prev := b["Error"].(string)
|
||||||
|
msg := prev + errMsg2
|
||||||
|
b["Error"] = msg
|
||||||
|
}
|
||||||
|
// DO NOT CONTINUE to the next prerender
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
errMsg3 := "thereisAThirdError"
|
||||||
|
UsePreRender(func(ctx *Context, src string, binding interface{}, options ...map[string]interface{}) bool {
|
||||||
|
// put the 'Error' binding here, for the shake of the test
|
||||||
|
if b, isMap := binding.(map[string]interface{}); isMap {
|
||||||
|
prev := b["Error"].(string)
|
||||||
|
msg := prev + errMsg3
|
||||||
|
b["Error"] = msg
|
||||||
|
}
|
||||||
|
// doesn't matters the return statement, we don't have other prerender
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
Get("/", func(ctx *Context) {
|
||||||
|
ctx.RenderTemplateSource(StatusOK, "<h1>HI {{.Username}}. Error: {{.Error}}</h1>", map[string]interface{}{"Username": "kataras"})
|
||||||
|
})
|
||||||
|
|
||||||
|
e := Tester(t)
|
||||||
|
expected := "<h1>HI kataras. Error: " + errMsg1 + errMsg2 + "</h1>"
|
||||||
|
e.GET("/").Expect().Status(StatusOK).Body().Contains(expected)
|
||||||
}
|
}
|
||||||
|
|
40
iris.go
40
iris.go
|
@ -79,7 +79,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Version is the current version of the Iris web framework
|
// Version is the current version of the Iris web framework
|
||||||
Version = "4.4.0"
|
Version = "4.4.1"
|
||||||
|
|
||||||
banner = ` _____ _
|
banner = ` _____ _
|
||||||
|_ _| (_)
|
|_ _| (_)
|
||||||
|
@ -149,6 +149,7 @@ type (
|
||||||
UseSessionDB(sessions.Database)
|
UseSessionDB(sessions.Database)
|
||||||
UseSerializer(string, serializer.Serializer)
|
UseSerializer(string, serializer.Serializer)
|
||||||
UseTemplate(template.Engine) *template.Loader
|
UseTemplate(template.Engine) *template.Loader
|
||||||
|
UsePreRender(PreRender)
|
||||||
UseGlobal(...Handler)
|
UseGlobal(...Handler)
|
||||||
UseGlobalFunc(...HandlerFunc)
|
UseGlobalFunc(...HandlerFunc)
|
||||||
Lookup(string) Route
|
Lookup(string) Route
|
||||||
|
@ -773,6 +774,28 @@ func (s *Framework) UseSerializer(forContentType string, e serializer.Serializer
|
||||||
s.serializers.For(forContentType, e)
|
s.serializers.For(forContentType, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsePreRender adds a Template's PreRender
|
||||||
|
// PreRender is typeof func(*iris.Context, filenameOrSource string, binding interface{}, options ...map[string]interface{}) bool
|
||||||
|
// PreRenders helps developers to pass middleware between the route Handler and a context.Render call
|
||||||
|
// all parameter receivers can be changed before passing it to the actual context's Render
|
||||||
|
// so, you can change the filenameOrSource, the page binding, the options, and even add cookies, session value or a flash message through ctx
|
||||||
|
// the return value of a PreRender is a boolean, if returns false then the next PreRender will not be executed, keep note
|
||||||
|
// that the actual context's Render will be called at any case.
|
||||||
|
func UsePreRender(pre PreRender) {
|
||||||
|
Default.UsePreRender(pre)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsePreRender adds a Template's PreRender
|
||||||
|
// PreRender is typeof func(*iris.Context, filenameOrSource string, binding interface{}, options ...map[string]interface{}) bool
|
||||||
|
// PreRenders helps developers to pass middleware between the route Handler and a context.Render call
|
||||||
|
// all parameter receivers can be changed before passing it to the actual context's Render
|
||||||
|
// so, you can change the filenameOrSource, the page binding, the options, and even add cookies, session value or a flash message through ctx
|
||||||
|
// the return value of a PreRender is a boolean, if returns false then the next PreRender will not be executed, keep note
|
||||||
|
// that the actual context's Render will be called at any case.
|
||||||
|
func (s *Framework) UsePreRender(pre PreRender) {
|
||||||
|
s.templates.usePreRender(pre)
|
||||||
|
}
|
||||||
|
|
||||||
// UseTemplate adds a template engine to the iris view system
|
// UseTemplate adds a template engine to the iris view system
|
||||||
// it does not build/load them yet
|
// it does not build/load them yet
|
||||||
func UseTemplate(e template.Engine) *template.Loader {
|
func UseTemplate(e template.Engine) *template.Loader {
|
||||||
|
@ -1942,6 +1965,20 @@ func (api *muxAPI) Favicon(favPath string, requestPath ...string) RouteNameFunc
|
||||||
return api.Get(reqPath, h)
|
return api.Get(reqPath, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Layout oerrides the parent template layout with a more specific layout for this Party
|
||||||
|
// returns this Party, to continue as normal
|
||||||
|
// example:
|
||||||
|
// my := iris.Party("/my").Layout("layouts/mylayout.html")
|
||||||
|
// {
|
||||||
|
// my.Get("/", func(ctx *iris.Context) {
|
||||||
|
// ctx.MustRender("page1.html", nil)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func Layout(tmplLayoutFile string) MuxAPI {
|
||||||
|
return Default.Layout(tmplLayoutFile)
|
||||||
|
}
|
||||||
|
|
||||||
// Layout oerrides the parent template layout with a more specific layout for this Party
|
// Layout oerrides the parent template layout with a more specific layout for this Party
|
||||||
// returns this Party, to continue as normal
|
// returns this Party, to continue as normal
|
||||||
// example:
|
// example:
|
||||||
|
@ -1957,6 +1994,7 @@ func (api *muxAPI) Layout(tmplLayoutFile string) MuxAPI {
|
||||||
ctx.Set(TemplateLayoutContextKey, tmplLayoutFile)
|
ctx.Set(TemplateLayoutContextKey, tmplLayoutFile)
|
||||||
ctx.Next()
|
ctx.Next()
|
||||||
})
|
})
|
||||||
|
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
82
template.go
82
template.go
|
@ -22,11 +22,21 @@ type (
|
||||||
// an example of this is the "layout" or "gzip" option
|
// an example of this is the "layout" or "gzip" option
|
||||||
// same as Map but more specific name
|
// same as Map but more specific name
|
||||||
RenderOptions map[string]interface{}
|
RenderOptions map[string]interface{}
|
||||||
|
|
||||||
|
// PreRender is typeof func(*iris.Context, string, interface{},...map[string]interface{}) bool
|
||||||
|
// PreRenders helps developers to pass middleware between
|
||||||
|
// the route Handler and a context.Render (THAT can render 'rest' types also but the PreRender applies ONLY to template rendering(file or source)) call
|
||||||
|
// all parameter receivers can be changed before passing it to the actual context's Render
|
||||||
|
// so, you can change the filenameOrSource, the page binding, the options, and even add cookies, session value or a flash message through ctx
|
||||||
|
// the return value of a PreRender is a boolean, if returns false then the next PreRender will not be executed, keep note
|
||||||
|
// that the actual context's Render will be called at any case.
|
||||||
|
PreRender func(ctx *Context, filenameOrSource string, binding interface{}, options ...map[string]interface{}) bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// templateEngines just a wrapper of template.Mux in order to use it's execute without break the whole of the API
|
// templateEngines just a wrapper of template.Mux in order to use it's execute without break the whole of the API
|
||||||
type templateEngines struct {
|
type templateEngines struct {
|
||||||
*template.Mux
|
*template.Mux
|
||||||
|
prerenders []PreRender
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTemplateEngines(sharedFuncs map[string]interface{}) *templateEngines {
|
func newTemplateEngines(sharedFuncs map[string]interface{}) *templateEngines {
|
||||||
|
@ -51,12 +61,30 @@ func getCharsetOption(defaultValue string, options map[string]interface{}) strin
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *templateEngines) usePreRender(pre PreRender) {
|
||||||
|
if t.prerenders == nil {
|
||||||
|
t.prerenders = make([]PreRender, 0)
|
||||||
|
}
|
||||||
|
t.prerenders = append(t.prerenders, pre)
|
||||||
|
}
|
||||||
|
|
||||||
// render executes a template and write its result to the context's body
|
// render executes a template and write its result to the context's body
|
||||||
// options are the optional runtime options can be passed by user and catched by the template engine when render
|
// options are the optional runtime options can be passed by user and catched by the template engine when render
|
||||||
// an example of this is the "layout"
|
// an example of this is the "layout"
|
||||||
// note that gzip option is an iris dynamic option which exists for all template engines
|
// note that gzip option is an iris dynamic option which exists for all template engines
|
||||||
// the gzip and charset options are built'n with iris
|
// the gzip and charset options are built'n with iris
|
||||||
func (t *templateEngines) render(ctx *Context, filename string, binding interface{}, options ...map[string]interface{}) (err error) {
|
// template is passed as file or souce
|
||||||
|
func (t *templateEngines) render(isFile bool, ctx *Context, filenameOrSource string, binding interface{}, options []map[string]interface{}) error {
|
||||||
|
if len(t.prerenders) > 0 {
|
||||||
|
for i := range t.prerenders {
|
||||||
|
// I'm not making any checks here for performance reasons, means that
|
||||||
|
// if binding is pointer it can be changed, otherwise not.
|
||||||
|
if shouldContinue := t.prerenders[i](ctx, filenameOrSource, binding, options...); !shouldContinue {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// we do all these because we don't want to initialize a new map for each execution...
|
// we do all these because we don't want to initialize a new map for each execution...
|
||||||
gzipEnabled := ctx.framework.Config.Gzip
|
gzipEnabled := ctx.framework.Config.Gzip
|
||||||
charset := ctx.framework.Config.Charset
|
charset := ctx.framework.Config.Charset
|
||||||
|
@ -65,12 +93,14 @@ func (t *templateEngines) render(ctx *Context, filename string, binding interfac
|
||||||
charset = getCharsetOption(charset, options[0])
|
charset = getCharsetOption(charset, options[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxLayout := ctx.GetString(TemplateLayoutContextKey)
|
if isFile {
|
||||||
if ctxLayout != "" {
|
ctxLayout := ctx.GetString(TemplateLayoutContextKey)
|
||||||
if len(options) > 0 {
|
if ctxLayout != "" {
|
||||||
options[0]["layout"] = ctxLayout
|
if len(options) > 0 {
|
||||||
} else {
|
options[0]["layout"] = ctxLayout
|
||||||
options = []map[string]interface{}{map[string]interface{}{"layout": ctxLayout}}
|
} else {
|
||||||
|
options = []map[string]interface{}{map[string]interface{}{"layout": ctxLayout}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,35 +117,17 @@ func (t *templateEngines) render(ctx *Context, filename string, binding interfac
|
||||||
} else {
|
} else {
|
||||||
out = ctx.Response.BodyWriter()
|
out = ctx.Response.BodyWriter()
|
||||||
}
|
}
|
||||||
|
if isFile {
|
||||||
|
return t.ExecuteWriter(out, filenameOrSource, binding, options...)
|
||||||
|
}
|
||||||
|
return t.ExecuteRaw(filenameOrSource, out, binding)
|
||||||
|
|
||||||
err = t.ExecuteWriter(out, filename, binding, options...)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderSource executes a template source raw contents (string) and write its result to the context's body
|
func (t *templateEngines) renderFile(ctx *Context, filename string, binding interface{}, options ...map[string]interface{}) error {
|
||||||
// note that gzip option is an iris dynamic option which exists for all template engines
|
return t.render(true, ctx, filename, binding, options)
|
||||||
// the gzip and charset options are built'n with iris
|
}
|
||||||
func (t *templateEngines) renderSource(ctx *Context, src string, binding interface{}, options ...map[string]interface{}) (err error) {
|
|
||||||
// we do all these because we don't want to initialize a new map for each execution...
|
func (t *templateEngines) renderSource(ctx *Context, src string, binding interface{}, options ...map[string]interface{}) error {
|
||||||
gzipEnabled := ctx.framework.Config.Gzip
|
return t.render(false, ctx, src, binding, options)
|
||||||
charset := ctx.framework.Config.Charset
|
|
||||||
if len(options) > 0 {
|
|
||||||
gzipEnabled = template.GetGzipOption(gzipEnabled, options[0])
|
|
||||||
charset = template.GetCharsetOption(charset, options[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.SetContentType(contentHTML + "; charset=" + charset)
|
|
||||||
|
|
||||||
var out io.Writer
|
|
||||||
if gzipEnabled && ctx.clientAllowsGzip() {
|
|
||||||
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
|
|
||||||
ctx.SetHeader(contentEncodingHeader, "gzip")
|
|
||||||
|
|
||||||
gzipWriter := fs.AcquireGzipWriter(ctx.Response.BodyWriter())
|
|
||||||
defer fs.ReleaseGzipWriter(gzipWriter)
|
|
||||||
out = gzipWriter
|
|
||||||
} else {
|
|
||||||
out = ctx.Response.BodyWriter()
|
|
||||||
}
|
|
||||||
return t.ExecuteRaw(src, out, binding)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user