Replace response engines with serializers, same job but organized better, read README.md , no front-end changes if you used default engines.

This commit is contained in:
Gerasimos Maropoulos 2016-09-10 07:23:02 +03:00
parent f561b7a90d
commit 97431f2650
6 changed files with 113 additions and 334 deletions

View File

@ -2,6 +2,19 @@
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris`.
## 4.2.0 -> 4.2.1
- **CHANGE**: No front-end changes if you used the default response engines before. Response Engines to Serializers, `iris.ResponseEngine` `serializer.Serializer`, comes from `kataras/go-serializer` which is installed automatically when you upgrade iris with `-u` flag.
- the repo "github.com/iris-contrib/response" is a clone of "github.com/kataras/go-serializer", to keep compatibility state. examples and gitbook updated to work with the last.
- `iris.UseResponse(iris.ResponseEngine, ...string)func (string)` was used to register custom response engines, now you use: `iris.UseSerializer(key string, s serializer.Serializer)`.
- `iris.ResponseString` same defintion but differnet name now: `iris.SerializeToString`
[Serializer examples](https://github.com/iris-contrib/examples/tree/master/serialize_engines) and [Book section](https://kataras.gitbooks.io/iris/content/serialize-engines.html) updated.
## 4.1.7 -> 4.2.0
- **ADDED**: `iris.TemplateSourceString(src string, binding interface{}) string` this will parse the src raw contents to the template engine and return the string result & `context.RenderTemplateSource(status int, src string, binding interface{}, options ...map[string]interface{}) error` this will parse the src raw contents to the template engine and render the result to the client, as requseted [here](https://github.com/kataras/iris/issues/409).

View File

@ -18,7 +18,7 @@
<br/>
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%204.2.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.2.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>
@ -178,7 +178,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
Versioning
------------
Current: **v4.2.0**
Current: **v4.2.1**
> Iris is an active project
@ -221,7 +221,7 @@ License can be found [here](LICENSE).
[Travis]: http://travis-ci.org/kataras/iris
[License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square
[License]: https://github.com/kataras/iris/blob/master/LICENSE
[Release Widget]: https://img.shields.io/badge/release-v4.2.0-blue.svg?style=flat-square
[Release Widget]: https://img.shields.io/badge/release-v4.2.1-blue.svg?style=flat-square
[Release]: https://github.com/kataras/iris/releases
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
[Chat]: https://kataras.rocket.chat/channel/iris

View File

@ -534,6 +534,47 @@ func (ctx *Context) Gzip(b []byte, status int) {
}
}
// renderSerialized renders contents with a serializer with status OK which you can change using RenderWithStatus or ctx.SetStatusCode(iris.StatusCode)
func (ctx *Context) renderSerialized(contentType string, obj interface{}, options ...map[string]interface{}) error {
s := ctx.framework.serializers
finalResult, err := s.Serialize(contentType, obj, options...)
if err != nil {
return err
}
gzipEnabled := ctx.framework.Config.Gzip
charset := ctx.framework.Config.Charset
if len(options) > 0 {
gzipEnabled = getGzipOption(gzipEnabled, options[0]) // located to the template.go below the RenderOptions
charset = getCharsetOption(charset, options[0])
}
ctype := contentType
if ctype == contentMarkdown { // remember the text/markdown is just a custom internal iris content type, which in reallity renders html
ctype = contentHTML
}
if ctype != contentBinary { // set the charset only on non-binary data
ctype += "; charset=" + charset
}
ctx.SetContentType(ctype)
if gzipEnabled && ctx.clientAllowsGzip() {
_, err := fasthttp.WriteGzip(ctx.RequestCtx.Response.BodyWriter(), finalResult)
if err != nil {
return err
}
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
ctx.SetHeader(contentEncodingHeader, "gzip")
} else {
ctx.Response.SetBody(finalResult)
}
ctx.SetStatusCode(StatusOK)
return nil
}
// RenderTemplateSource serves a template source(raw string contents) from the first template engines which supports raw parsing returns its result as string
func (ctx *Context) RenderTemplateSource(status int, src string, binding interface{}, options ...map[string]interface{}) error {
err := ctx.framework.templates.renderSource(ctx, src, binding, options...)
@ -544,13 +585,14 @@ func (ctx *Context) RenderTemplateSource(status int, src string, binding interfa
return err
}
// RenderWithStatus builds up the response from the specified template or a response engine.
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or response engine
// RenderWithStatus builds up the response from the specified template or a serialize engine.
// 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) {
if strings.IndexByte(name, '.') > -1 { //we have template
err = ctx.framework.templates.render(ctx, name, binding, options...)
} else {
err = ctx.renderSerialized(name, binding, options...)
}
err = ctx.framework.responses.getBy(name).render(ctx, binding, options...)
if err == nil {
ctx.SetStatusCode(status)
@ -560,8 +602,8 @@ func (ctx *Context) RenderWithStatus(status int, name string, binding interface{
}
// Render same as .RenderWithStatus but with status to iris.StatusOK (200) if no previous status exists
// builds up the response from the specified template or a response engine.
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or response engine
// builds up the response from the specified template or a serialize engine.
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engine
func (ctx *Context) Render(name string, binding interface{}, options ...map[string]interface{}) error {
errCode := ctx.RequestCtx.Response.StatusCode()
if errCode <= 0 {
@ -571,8 +613,8 @@ func (ctx *Context) Render(name string, binding interface{}, options ...map[stri
}
// MustRender same as .Render but returns 500 internal server http status (error) if rendering fail
// builds up the response from the specified template or a response engine.
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or response engine
// builds up the response from the specified template or a serialize engine.
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engine
func (ctx *Context) MustRender(name string, binding interface{}, options ...map[string]interface{}) {
if err := ctx.Render(name, binding, options...); err != nil {
ctx.Panic()
@ -591,7 +633,7 @@ func (ctx *Context) TemplateString(name string, binding interface{}, options ...
// HTML writes html string with a http status
func (ctx *Context) HTML(status int, htmlContents string) {
if err := ctx.RenderWithStatus(status, contentHTML, htmlContents); err != nil {
// if no response engine found for text/html
// if no serialize engine found for text/html
ctx.SetContentType(contentHTML + "; charset=" + ctx.framework.Config.Charset)
ctx.RequestCtx.SetStatusCode(status)
ctx.RequestCtx.WriteString(htmlContents)
@ -625,7 +667,7 @@ func (ctx *Context) XML(status int, v interface{}) error {
// MarkdownString parses the (dynamic) markdown string and returns the converted html string
func (ctx *Context) MarkdownString(markdownText string) string {
return ctx.framework.ResponseString(contentMarkdown, markdownText)
return ctx.framework.SerializeToString(contentMarkdown, markdownText)
}
// Markdown parses and renders to the client a particular (dynamic) markdown string

95
iris.go
View File

@ -65,14 +65,9 @@ import (
"time"
"github.com/gavv/httpexpect"
"github.com/iris-contrib/response/data"
"github.com/iris-contrib/response/json"
"github.com/iris-contrib/response/jsonp"
"github.com/iris-contrib/response/markdown"
"github.com/iris-contrib/response/text"
"github.com/iris-contrib/response/xml"
"github.com/kataras/go-errors"
"github.com/kataras/go-fs"
"github.com/kataras/go-serializer"
"github.com/kataras/go-sessions"
"github.com/kataras/go-template"
"github.com/kataras/go-template/html"
@ -83,7 +78,7 @@ import (
const (
// Version of the iris
Version = "4.2.0"
Version = "4.2.1"
banner = ` _____ _
|_ _| (_)
@ -152,7 +147,7 @@ type (
Go() error
Close() error
UseSessionDB(sessions.Database)
UseResponse(ResponseEngine, ...string) func(string)
UseSerializer(string, serializer.Serializer)
UseTemplate(template.Engine) *template.Loader
UseGlobal(...Handler)
UseGlobalFunc(...HandlerFunc)
@ -162,7 +157,7 @@ type (
URL(string, ...interface{}) string
TemplateString(string, interface{}, ...map[string]interface{}) string
TemplateSourceString(string, interface{}) string
ResponseString(string, interface{}, ...map[string]interface{}) string
SerializeToString(string, interface{}, ...map[string]interface{}) string
Tester(*testing.T) *httpexpect.Expect
}
@ -174,7 +169,7 @@ type (
contextPool sync.Pool
Config *Configuration
sessions sessions.Sessions
responses *responseEngines
serializers serializer.Serializers
templates *templateEngines
// fields which are useful to the user/dev
// the last added server is the main server
@ -214,7 +209,7 @@ func New(setters ...OptionSetter) *Framework {
// rendering
{
s.responses = newResponseEngines()
s.serializers = serializer.Serializers{}
// set the templates
s.templates = newTemplateEngines(map[string]interface{}{
"url": s.URL,
@ -262,28 +257,8 @@ func (s *Framework) Set(setters ...OptionSetter) {
}
func (s *Framework) initialize() {
// prepare the response engines, if no response engines setted for the default content-types
// then add them
for _, ctype := range defaultResponseKeys {
if rengine := s.responses.getBy(ctype); rengine == nil {
// if not exists
switch ctype {
case contentText:
s.UseResponse(text.New(), ctype)
case contentBinary:
s.UseResponse(data.New(), ctype)
case contentJSON:
s.UseResponse(json.New(), ctype)
case contentJSONP:
s.UseResponse(jsonp.New(), ctype)
case contentXML:
s.UseResponse(xml.New(), ctype)
case contentMarkdown:
s.UseResponse(markdown.New(), ctype)
}
}
}
// prepare the serializers, if not any other serializers setted for the default serializer types(json,jsonp,xml,markdown,text,data) then the defaults are setted:
serializer.RegisterDefaults(s.serializers)
// prepare the templates if enabled
if !s.Config.DisableTemplateEngines {
@ -606,56 +581,38 @@ func (s *Framework) UseSessionDB(db sessions.Database) {
s.sessions.UseDatabase(db)
}
// UseResponse accepts a ResponseEngine and the key or content type on which the developer wants to register this response engine
// UseSerializer accepts a Serializer and the key or content type on which the developer wants to register this serializer
// the gzip and charset are automatically supported by Iris, by passing the iris.RenderOptions{} map on the context.Render
// context.Render renders this response or a template engine if no response engine with the 'key' found
// with these engines you can inject the context.JSON,Text,Data,JSONP,XML also
// to do that just register with UseResponse(myEngine,"application/json") and so on
// look at the https://github.com/iris-contrib/response for examples
// to do that just register with UseSerializer(mySerializer,"application/json") and so on
// look at the https://github.com/kataras/go-serializer for examples
//
// if more than one respone engine with the same key/content type exists then the results will be appended to the final request's body
// if more than one serializer with the same key/content type exists then the results will be appended to the final request's body
// this allows the developer to be able to create 'middleware' responses engines
//
// Note: if you pass an engine which contains a dot('.') as key, then the engine will not be registered.
// you don't have to import and use github.com/iris-contrib/json, jsonp, xml, data, text, markdown
// because iris uses these by default if no other response engine is registered for these content types
//
// Note 2:
// one key has one content type but many response engines ( one to many)
//
// returns a function(string) which you can set the content type, if it's not already declared from the key.
// careful you should call this in the same execution.
// one last thing, you can have unlimited number of response engines for the same key and same content type.
// key and content type may be different, but one key is only for one content type,
// Do not use different content types with more than one response engine on the same key
func UseResponse(e ResponseEngine, forContentTypesOrKeys ...string) func(string) {
return Default.UseResponse(e, forContentTypesOrKeys...)
func UseSerializer(forContentType string, e serializer.Serializer) {
Default.UseSerializer(forContentType, e)
}
// UseResponse accepts a ResponseEngine and the key or content type on which the developer wants to register this response engine
// UseSerializer accepts a Serializer and the key or content type on which the developer wants to register this serializer
// the gzip and charset are automatically supported by Iris, by passing the iris.RenderOptions{} map on the context.Render
// context.Render renders this response or a template engine if no response engine with the 'key' found
// with these engines you can inject the context.JSON,Text,Data,JSONP,XML also
// to do that just register with UseResponse(myEngine,"application/json") and so on
// look at the https://github.com/iris-contrib/response for examples
// to do that just register with UseSerializer(mySerializer,"application/json") and so on
// look at the https://github.com/kataras/go-serializer for examples
//
// if more than one respone engine with the same key/content type exists then the results will be appended to the final request's body
// if more than one serializer with the same key/content type exists then the results will be appended to the final request's body
// this allows the developer to be able to create 'middleware' responses engines
//
// Note: if you pass an engine which contains a dot('.') as key, then the engine will not be registered.
// you don't have to import and use github.com/iris-contrib/json, jsonp, xml, data, text, markdown
// because iris uses these by default if no other response engine is registered for these content types
//
// Note 2:
// one key has one content type but many response engines ( one to many)
//
// returns a function(string) which you can set the content type, if it's not already declared from the key.
// careful you should call this in the same execution.
// one last thing, you can have unlimited number of response engines for the same key and same content type.
// key and content type may be different, but one key is only for one content type,
// Do not use different content types with more than one response engine on the same key
func (s *Framework) UseResponse(e ResponseEngine, forContentTypesOrKeys ...string) func(string) {
return s.responses.add(e, forContentTypesOrKeys...)
func (s *Framework) UseSerializer(forContentType string, e serializer.Serializer) {
s.serializers.For(forContentType, e)
}
// UseTemplate adds a template engine to the iris view system
@ -939,18 +896,18 @@ func (s *Framework) TemplateSourceString(src string, pageContext interface{}) st
return res
}
// ResponseString returns the string of a response engine,
// SerializeToString returns the string of a serializer,
// does not render it to the client
// returns empty string on error
func ResponseString(keyOrContentType string, obj interface{}, options ...map[string]interface{}) string {
return Default.ResponseString(keyOrContentType, obj, options...)
func SerializeToString(keyOrContentType string, obj interface{}, options ...map[string]interface{}) string {
return Default.SerializeToString(keyOrContentType, obj, options...)
}
// ResponseString returns the string of a response engine,
// SerializeToString returns the string of a serializer,
// does not render it to the client
// returns empty string on error
func (s *Framework) ResponseString(keyOrContentType string, obj interface{}, options ...map[string]interface{}) string {
res, err := s.responses.getBy(keyOrContentType).toString(obj, options...)
func (s *Framework) SerializeToString(keyOrContentType string, obj interface{}, options ...map[string]interface{}) string {
res, err := s.serializers.SerializeToString(keyOrContentType, obj, options...)
if err != nil {
return ""
}

View File

@ -1,251 +0,0 @@
package iris
import (
"strings"
"github.com/kataras/go-errors"
"github.com/kataras/go-template"
"github.com/valyala/fasthttp"
)
type (
// notes for me:
// edw an kai kalh idea alla den 9a borw na exw ta defaults mesa sto iris
// kai 9a prepei na to metaferw auto sto context i sto utils kai pragmatika den leei
// na kanoun import auto gia na kanoun to response engine, ara na prospa9isw kapws aliws mesw context.IContext mono
// ektos an metaferw ta defaults mesa sto iris
// alla an to kanw auto 9a prepei na vrw tropo na kanoun configuration ta defaults
// kai to idio prepei na kanw kai sto template engine html tote...
// diladi na kanw prwta to render tou real engine kai meta na parw
// ta contents tou body kai na ta kanw gzip ? na kanw resetbody kai na ta ksanasteilw?
// alla auto einai argh methodos kai gia ton poutso dn m aresei
// kai ola auta gia na mh valw ena property parapanw? 9a valw ...
// 9a einai writer,headers,object,options... anagastika.
/* notes for me:
english 'final' thoughs' results now:
the response engine will be registered with its content type
for example: iris.UseResponse("application/json", the engine or func here, optionalOptions...)
if more than one response engines registered for the same content type
then all the content results will be sent to the client
there will be available a system something like middleware but for the response engines
this will be useful when one response engine's job is only to add more content to the existing/parent response engine's results .
for example when you want to add a standard json object like a last_fetch { "year":...,"month":...,"day":...} for all json responses.
The engine will not have access to context or something like that.
The default engines will registered when no other engine with the same content type
already registered, like I did with template engines, so if someone wants to make a 'middleware' for the default response engines
must register them explicit and after register his/her response engine too, for that reason
the default engines will not be located inside iris but inside iris-contrib (like the default,and others, template engine),
the reason is to be easier to the user/dev to remember what import path should use when he/she wants to edit something,
for templates it's 'iris-contrib/template', for response engines will be 'iris-contrib/response'.
The body content will return as []byte, al/mong with an error if something bad happened.
Now you may ask why not set the header inside from response engine? because to do that we could have one of these four downsides:
1.to have access to context.IContext or *Context(if *Context then default engines should live here in iris repo)
and if we have context.IContext or *Context we will not be able to set a fast built'n gzip option,
because we would copy the contents to the gzip writer, and after copy these contents back to the response body's writer
but with an io.Writer as parameter we can simple change this writer to gzip writer and continue to the response engine after.
2. we could make something like ResponseWriter struct { io.Writer,Header *fasthttp.ResponseHeader}
inside iris repo(then the default response engines should exists in the iris repo and configuration will depends on the iris' configs )
or inside context/ folder inside iris repo, then the user/dev should import this path to
do his/her response engine, and I want simple things as usual, also we would make a pool for this response writer and create new if not available exist,
and this is performarnce downs witch I dissalow on Iris whne no need.
3. to have 4 parameters, the writer, the headers(again the user should import the fasthttp to do his/her response engine and I want simple things, as I told before),
the object and the optional parameters
4. one more function to implement like 'ContentType() string', but if we select this we lose the functionality for ResponseEngine created as simple function,
and the biggest issue will be that one response engine must explicit exists for one content type, the user/dev will not be available (to easly)
to set the content type for the engine.
these were the reasons I decide to set the content type by the frontend iris API itself and not taken by the response engine.
The Response will have two parameters (one required only) interface{], ...options}, and two return values([]byte,error)
The (first) parameter will be an interface{}, for json a json struct, for xml an xml struct, for binary data .([]byte) and so on
There will be available a second optional parameter, map of options, the "gzip" option will be built'n implemented by iris
so the response engines no need to manually add gzip support(same with template engines).
The Charset will be added to the headers automatically, for the previous example of json and the default charset which is UTF-8
the end "Content-Type" header content will be: "application/json; charset=UTF-8"
if the registered content type is not a $content/type then the text/plain will be sent to the client.
OR WAIT, some engines maybe want to set the content type or other headers dynamically or render a response depends on cookies or some other existence headers
on that situtions it will be impossible with this implementation I explained before, so...
access to context.IContext and return the []byte, in order to be able to add the built'n gzip support
the dev/user will have to make this import no no no we stick to the previous though, because
if the user wants to check all that he/she can just use a middleware with .Use/.UseFunc
this is not a middleware implementation, this is a custom content rendering, let's stick to that.
Ok I did that and I realized that template and response engines, final method structure (string,interface{},options...) is the same
so I make the ctx.Render/RenderWithStatus to work with both engines, so the developer can use any type of response engine and render it with ease.
Maybe at the future I could have one file 'render.go' which will contain the template engines and response engines, we will see, these all are unique so excuse me if something goes wrong xD
That's all. Hope some one (other than me) will understand the english here...
*/
// ResponseEngine is the interface which all response engines should implement to send responses
// ResponseEngine(s) can be registered with,for example: iris.UseResponse(json.New(), "application/json")
ResponseEngine interface {
Response(interface{}, ...map[string]interface{}) ([]byte, error)
}
// ResponseEngineFunc is the alternative way to implement a ResponseEngine using a simple function
ResponseEngineFunc func(interface{}, ...map[string]interface{}) ([]byte, error)
// responseEngineMap is a wrapper with key (content type or name) values(engines) for the registered response engine
// it contains all response engines for a specific contentType and two functions, render and toString
// these will be used by the iris' context and iris' ResponseString, yes like TemplateToString
// it's an internal struct, no need to be exported and return that on registration,
// because the two top funcs will be easier to use by the user/dev for multiple engines
responseEngineMap struct {
values []ResponseEngine
// this is used in order to the wrapper to be gettable by the responseEngines iteral,
// if key is not a $content/type and contentType is not changed by the user/dev then the text/plain will be sent to the client
key string
contentType string
}
)
var (
// markdown is custom type, used inside iris to initialize the defaults response engines if no other engine registered with these keys
defaultResponseKeys = [...]string{contentText, contentXML, contentBinary, contentJSON, contentJSONP, contentMarkdown}
)
// Response returns a response to the client(request's body content)
func (r ResponseEngineFunc) Response(obj interface{}, options ...map[string]interface{}) ([]byte, error) {
return r(obj, options...)
}
var errNoResponseEngineFound = errors.New("No response engine found")
// on context: Send(contentType string, obj interface{}, ...options)
func (r *responseEngineMap) add(engine ResponseEngine) {
r.values = append(r.values, engine)
}
// the gzip and charset options are built'n with iris
func (r *responseEngineMap) render(ctx *Context, obj interface{}, options ...map[string]interface{}) error {
if r == nil {
//render, but no response engine registered, this caused by context.RenderWithStatus, and responseEngines. getBy
return errNoResponseEngineFound
}
var finalResult []byte
for i, n := 0, len(r.values); i < n; i++ {
result, err := r.values[i].Response(obj, options...)
if err != nil { // fail on first the first error
return err
}
finalResult = append(finalResult, result...)
}
gzipEnabled := ctx.framework.Config.Gzip
charset := ctx.framework.Config.Charset
if len(options) > 0 {
gzipEnabled = template.GetGzipOption(gzipEnabled, options[0]) // located to the template.go below the RenderOptions
charset = template.GetCharsetOption(charset, options[0])
}
ctype := r.contentType
if r.contentType != contentBinary { // set the charset only on non-binary data
ctype += "; charset=" + charset
}
ctx.SetContentType(ctype)
if gzipEnabled && ctx.clientAllowsGzip() {
_, err := fasthttp.WriteGzip(ctx.RequestCtx.Response.BodyWriter(), finalResult)
if err != nil {
return err
}
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
ctx.SetHeader(contentEncodingHeader, "gzip")
} else {
ctx.Response.SetBody(finalResult)
}
return nil
}
func (r *responseEngineMap) toString(obj interface{}, options ...map[string]interface{}) (string, error) {
if r == nil {
//render, but no response engine registered, this caused by context.RenderWithStatus, and responseEngines. getBy
return "", errNoResponseEngineFound
}
var finalResult []byte
for i, n := 0, len(r.values); i < n; i++ {
result, err := r.values[i].Response(obj, options...)
if err != nil {
return "", err
}
finalResult = append(finalResult, result...)
}
return string(finalResult), nil
}
type responseEngines struct {
engines []*responseEngineMap
}
func newResponseEngines() *responseEngines {
return &responseEngines{}
}
// add accepts a simple response engine with its content type or key, key should not contains a dot('.').
// if key is a content type then it's the content type, but if it not, set the content type from the returned function,
// if it not called/changed then the default content type text/plain will be used.
// different content types for the same key will produce bugs, as it should!
// one key has one content type but many response engines ( one to many)
// note that the func should be used on the same call
func (r *responseEngines) add(engine ResponseEngine, forContentTypesOrKeys ...string) func(string) {
if r.engines == nil {
r.engines = make([]*responseEngineMap, 0)
}
var engineMap *responseEngineMap
for _, key := range forContentTypesOrKeys {
if strings.IndexByte(key, '.') != -1 { // the dot is not allowed as key
continue // skip this engine
}
defaultCtypeAndKey := contentText
if len(key) == 0 {
//if empty key, then set it to text/plain
key = defaultCtypeAndKey
}
engineMap = r.getBy(key)
if engineMap == nil {
ctype := defaultCtypeAndKey
if strings.IndexByte(key, slashByte) != -1 { // pure check, but developer should know the content types at least.
// we have 'valid' content type
ctype = key
}
// the context.Markdown works without it but with .Render we will have problems without this:
if key == contentMarkdown { // remember the text/markdown is just a custom internal iris content type, which in reallity renders html
ctype = contentHTML
}
engineMap = &responseEngineMap{values: make([]ResponseEngine, 0), key: key, contentType: ctype}
r.engines = append(r.engines, engineMap)
}
engineMap.add(engine)
}
return func(theContentType string) {
// and this
if theContentType == contentMarkdown {
theContentType = contentHTML
}
engineMap.contentType = theContentType
}
}
func (r *responseEngines) getBy(key string) *responseEngineMap {
for i, n := 0, len(r.engines); i < n; i++ {
if r.engines[i].key == key {
return r.engines[i]
}
}
return nil
}

View File

@ -33,6 +33,24 @@ func newTemplateEngines(sharedFuncs map[string]interface{}) *templateEngines {
return &templateEngines{Mux: template.NewMux(sharedFuncs)}
}
// getGzipOption receives a default value and the render options map and returns if gzip is enabled for this render action
func getGzipOption(defaultValue bool, options map[string]interface{}) bool {
gzipOpt := options["gzip"] // we only need that, so don't create new map to keep the options.
if b, isBool := gzipOpt.(bool); isBool {
return b
}
return defaultValue
}
// gtCharsetOption receives a default value and the render options map and returns the correct charset for this render action
func getCharsetOption(defaultValue string, options map[string]interface{}) string {
charsetOpt := options["charset"]
if s, isString := charsetOpt.(string); isString {
return s
}
return defaultValue
}
// 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
// an example of this is the "layout"
@ -43,8 +61,8 @@ func (t *templateEngines) render(ctx *Context, filename string, binding interfac
gzipEnabled := ctx.framework.Config.Gzip
charset := ctx.framework.Config.Charset
if len(options) > 0 {
gzipEnabled = template.GetGzipOption(gzipEnabled, options[0])
charset = template.GetCharsetOption(charset, options[0])
gzipEnabled = getGzipOption(gzipEnabled, options[0])
charset = getCharsetOption(charset, options[0])
}
ctxLayout := ctx.GetString(TemplateLayoutContextKey)