mirror of
https://github.com/kataras/iris.git
synced 2025-03-13 21:36:28 +01:00
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:
parent
f561b7a90d
commit
97431f2650
13
HISTORY.md
13
HISTORY.md
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
60
context.go
60
context.go
|
@ -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
95
iris.go
|
@ -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 ""
|
||||
}
|
||||
|
|
251
response.go
251
response.go
|
@ -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
|
||||
}
|
22
template.go
22
template.go
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user