diff --git a/_examples/README.md b/_examples/README.md index 92fc4810..5d416203 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -121,6 +121,9 @@ Navigate through examples for a better understanding. - [Inject Data Between Handlers](view/context-view-data/main.go) - [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go) + +You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files too, simply by using the `context#ResponseWriter`, take a look at the [http_responsewriter/quicktemplate](http_responsewriter/quicktemplate) example. + ### Authentication - [Basic Authentication](authentication/basicauth/main.go) @@ -148,6 +151,7 @@ Navigate through examples for a better understanding. ### How to Write to `context.ResponseWriter() http.ResponseWriter` +- [Write `valyala/quicktemplate` templates](http_responsewriter/quicktemplate) - [Text, Markdown, HTML, JSON, JSONP, XML, Binary](http_responsewriter/write-rest/main.go) - [Stream Writer](http_responsewriter/stream-writer/main.go) - [Transactions](http_responsewriter/transactions/main.go) diff --git a/_examples/http_responsewriter/quicktemplate/README.md b/_examples/http_responsewriter/quicktemplate/README.md new file mode 100644 index 00000000..3d3a6f9b --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/README.md @@ -0,0 +1,19 @@ +First of all, install [quicktemplate](https://github.com/valyala/quicktemplate) package and [quicktemplate compiler](https://github.com/valyala/quicktemplate/tree/master/qtc) + +```sh +go get -u github.com/valyala/quicktemplate +go get -u github.com/valyala/quicktemplate/qtc +``` + +The example has the Go code compiled already for you, therefore: +```sh +go run main.go # http://localhost:8080 +``` + +However there is an instruction below, full documentation can be found at https://github.com/valyala/quicktemplate. + +Save your template files into `templates` folder under the extension *.qtpl, open your terminal and run `qtc` inside this folder. + +If all went ok, `*.qtpl.go` files must appear in the `templates` folder. These files contain the Go code for all `*.qtpl` files. + +> Remember, each time you change a a `/templates/*.qtpl` file you have to run the `qtc` command and re-build your application. \ No newline at end of file diff --git a/_examples/http_responsewriter/quicktemplate/controllers/execute_template.go b/_examples/http_responsewriter/quicktemplate/controllers/execute_template.go new file mode 100644 index 00000000..fbe39872 --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/controllers/execute_template.go @@ -0,0 +1,14 @@ +package controllers + +import ( + "github.com/kataras/iris/_examples/http_responsewriter/quicktemplate/templates" + + "github.com/kataras/iris/context" +) + +// ExecuteTemplate renders a "tmpl" partial template to the `context#ResponseWriter`. +func ExecuteTemplate(ctx context.Context, tmpl templates.Partial) { + ctx.Gzip(true) + ctx.ContentType("text/html") + templates.WriteTemplate(ctx.ResponseWriter(), tmpl) +} diff --git a/_examples/http_responsewriter/quicktemplate/controllers/hello.go b/_examples/http_responsewriter/quicktemplate/controllers/hello.go new file mode 100644 index 00000000..e95262ec --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/controllers/hello.go @@ -0,0 +1,30 @@ +package controllers + +import ( + "github.com/kataras/iris/_examples/http_responsewriter/quicktemplate/templates" + + "github.com/kataras/iris/context" +) + +// Hello renders our ../templates/hello.qtpl file using the compiled ../templates/hello.qtpl.go file. +func Hello(ctx context.Context) { + // vars := make(map[string]interface{}) + // vars["message"] = "Hello World!" + // vars["name"] = ctx.Params().Get("name") + // [...] + // &templates.Hello{ Vars: vars } + // [...] + + // However, as an alternative, we recommend that you should the `ctx.ViewData(key, value)` + // in order to be able modify the `templates.Hello#Vars` from a middleware(other handlers) as well. + ctx.ViewData("message", "Hello World!") + ctx.ViewData("name", ctx.Params().Get("name")) + + // set view data to the `Vars` template's field + tmpl := &templates.Hello{ + Vars: ctx.GetViewData(), + } + + // render the template + ExecuteTemplate(ctx, tmpl) +} diff --git a/_examples/http_responsewriter/quicktemplate/controllers/index.go b/_examples/http_responsewriter/quicktemplate/controllers/index.go new file mode 100644 index 00000000..2462a8c3 --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/controllers/index.go @@ -0,0 +1,15 @@ +package controllers + +import ( + "github.com/kataras/iris/_examples/http_responsewriter/quicktemplate/templates" + + "github.com/kataras/iris/context" +) + +// Index renders our ../templates/index.qtpl file using the compiled ../templates/index.qtpl.go file. +func Index(ctx context.Context) { + tmpl := &templates.Index{} + + // render the template + ExecuteTemplate(ctx, tmpl) +} diff --git a/_examples/http_responsewriter/quicktemplate/main.go b/_examples/http_responsewriter/quicktemplate/main.go new file mode 100644 index 00000000..e25fab4e --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/kataras/iris/_examples/http_responsewriter/quicktemplate/controllers" + + "github.com/kataras/iris" +) + +func newApp() *iris.Application { + app := iris.New() + app.Get("/", controllers.Index) + app.Get("/{name}", controllers.Hello) + + return app +} + +func main() { + app := newApp() + // http://localhost:8080 + // http://localhost:8080/yourname + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) +} diff --git a/_examples/http_responsewriter/quicktemplate/main_test.go b/_examples/http_responsewriter/quicktemplate/main_test.go new file mode 100644 index 00000000..db5a88fd --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/main_test.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/kataras/iris/httptest" +) + +func TestResponseWriterQuicktemplate(t *testing.T) { + baseRawBody := ` + + + Quicktemplate integration with Iris + + +
+ Header contents here... +
+ +
+ +

%s

+
+ %s +
+ +
+ + + + +` + + expectedIndexRawBody := fmt.Sprintf(baseRawBody, "Index Page", "This is our index page's body.") + name := "yourname" + expectedHelloRawBody := fmt.Sprintf(baseRawBody, "Hello World!", "Hello "+name+"!") + + app := newApp() + + e := httptest.New(t, app) + + e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal(expectedIndexRawBody) + e.GET("/" + name).Expect().Status(httptest.StatusOK).Body().Equal(expectedHelloRawBody) +} diff --git a/_examples/http_responsewriter/quicktemplate/models/.gitkeep b/_examples/http_responsewriter/quicktemplate/models/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/_examples/http_responsewriter/quicktemplate/templates/base.qtpl b/_examples/http_responsewriter/quicktemplate/templates/base.qtpl new file mode 100644 index 00000000..4e3fd923 --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/templates/base.qtpl @@ -0,0 +1,36 @@ +This is our templates' base implementation. + +{% interface +Partial { + Body() +} +%} + + +Template writes a template implementing the Partial interface. +{% func Template(p Partial) %} + + + Quicktemplate integration with Iris + + +
+ Header contents here... +
+ +
+ {%= p.Body() %} +
+ + + + +{% endfunc %} + + +Base template implementation. Other pages may inherit from it if they need +overriding only certain Partial methods. +{% code type Base struct {} %} +{% func (b *Base) Body() %}This is the base body{% endfunc %} diff --git a/_examples/http_responsewriter/quicktemplate/templates/base.qtpl.go b/_examples/http_responsewriter/quicktemplate/templates/base.qtpl.go new file mode 100644 index 00000000..32a5e638 --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/templates/base.qtpl.go @@ -0,0 +1,128 @@ +// This file is automatically generated by qtc from "base.qtpl". +// See https://github.com/valyala/quicktemplate for details. + +// This is our templates' base implementation. +// + +//line base.qtpl:3 +package templates + +//line base.qtpl:3 +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +//line base.qtpl:3 +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +//line base.qtpl:4 +type Partial interface { + //line base.qtpl:4 + Body() string + //line base.qtpl:4 + StreamBody(qw422016 *qt422016.Writer) + //line base.qtpl:4 + WriteBody(qq422016 qtio422016.Writer) +//line base.qtpl:4 +} + +// Template writes a template implementing the Partial interface. + +//line base.qtpl:11 +func StreamTemplate(qw422016 *qt422016.Writer, p Partial) { + //line base.qtpl:11 + qw422016.N().S(` + + + Quicktemplate integration with Iris + + +
+ Header contents here... +
+ +
+ `) + //line base.qtpl:22 + p.StreamBody(qw422016) + //line base.qtpl:22 + qw422016.N().S(` +
+ + + + +`) +//line base.qtpl:30 +} + +//line base.qtpl:30 +func WriteTemplate(qq422016 qtio422016.Writer, p Partial) { + //line base.qtpl:30 + qw422016 := qt422016.AcquireWriter(qq422016) + //line base.qtpl:30 + StreamTemplate(qw422016, p) + //line base.qtpl:30 + qt422016.ReleaseWriter(qw422016) +//line base.qtpl:30 +} + +//line base.qtpl:30 +func Template(p Partial) string { + //line base.qtpl:30 + qb422016 := qt422016.AcquireByteBuffer() + //line base.qtpl:30 + WriteTemplate(qb422016, p) + //line base.qtpl:30 + qs422016 := string(qb422016.B) + //line base.qtpl:30 + qt422016.ReleaseByteBuffer(qb422016) + //line base.qtpl:30 + return qs422016 +//line base.qtpl:30 +} + +// Base template implementation. Other pages may inherit from it if they need +// overriding only certain Partial methods. + +//line base.qtpl:35 +type Base struct{} + +//line base.qtpl:36 +func (b *Base) StreamBody(qw422016 *qt422016.Writer) { +//line base.qtpl:36 +qw422016.N().S(`This is the base body`) } + +//line base.qtpl:36 +//line base.qtpl:36 +func (b *Base) WriteBody(qq422016 qtio422016.Writer) { + //line base.qtpl:36 + qw422016 := qt422016.AcquireWriter(qq422016) + //line base.qtpl:36 + b.StreamBody(qw422016) + //line base.qtpl:36 + qt422016.ReleaseWriter(qw422016) +//line base.qtpl:36 +} + +//line base.qtpl:36 +func (b *Base) Body() string { + //line base.qtpl:36 + qb422016 := qt422016.AcquireByteBuffer() + //line base.qtpl:36 + b.WriteBody(qb422016) + //line base.qtpl:36 + qs422016 := string(qb422016.B) + //line base.qtpl:36 + qt422016.ReleaseByteBuffer(qb422016) + //line base.qtpl:36 + return qs422016 +//line base.qtpl:36 +} diff --git a/_examples/http_responsewriter/quicktemplate/templates/hello.qtpl b/_examples/http_responsewriter/quicktemplate/templates/hello.qtpl new file mode 100644 index 00000000..9752cc8d --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/templates/hello.qtpl @@ -0,0 +1,14 @@ +Hello template, implements the Partial's methods. + +{% code +type Hello struct { + Vars map[string]interface{} +} +%} + +{% func (h *Hello) Body() %} +

{%v h.Vars["message"] %}

+
+ Hello {%v h.Vars["name"] %}! +
+{% endfunc %} diff --git a/_examples/http_responsewriter/quicktemplate/templates/hello.qtpl.go b/_examples/http_responsewriter/quicktemplate/templates/hello.qtpl.go new file mode 100644 index 00000000..5b812bfb --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/templates/hello.qtpl.go @@ -0,0 +1,72 @@ +// This file is automatically generated by qtc from "hello.qtpl". +// See https://github.com/valyala/quicktemplate for details. + +// Hello template, implements the Partial's methods. +// + +//line hello.qtpl:3 +package templates + +//line hello.qtpl:3 +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +//line hello.qtpl:3 +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +//line hello.qtpl:4 +type Hello struct { + Vars map[string]interface{} +} + +//line hello.qtpl:9 +func (h *Hello) StreamBody(qw422016 *qt422016.Writer) { + //line hello.qtpl:9 + qw422016.N().S(` +

`) + //line hello.qtpl:10 + qw422016.E().V(h.Vars["message"]) + //line hello.qtpl:10 + qw422016.N().S(`

+
+ Hello `) + //line hello.qtpl:12 + qw422016.E().V(h.Vars["name"]) + //line hello.qtpl:12 + qw422016.N().S(`! +
+`) +//line hello.qtpl:14 +} + +//line hello.qtpl:14 +func (h *Hello) WriteBody(qq422016 qtio422016.Writer) { + //line hello.qtpl:14 + qw422016 := qt422016.AcquireWriter(qq422016) + //line hello.qtpl:14 + h.StreamBody(qw422016) + //line hello.qtpl:14 + qt422016.ReleaseWriter(qw422016) +//line hello.qtpl:14 +} + +//line hello.qtpl:14 +func (h *Hello) Body() string { + //line hello.qtpl:14 + qb422016 := qt422016.AcquireByteBuffer() + //line hello.qtpl:14 + h.WriteBody(qb422016) + //line hello.qtpl:14 + qs422016 := string(qb422016.B) + //line hello.qtpl:14 + qt422016.ReleaseByteBuffer(qb422016) + //line hello.qtpl:14 + return qs422016 +//line hello.qtpl:14 +} diff --git a/_examples/http_responsewriter/quicktemplate/templates/index.qtpl b/_examples/http_responsewriter/quicktemplate/templates/index.qtpl new file mode 100644 index 00000000..f2551297 --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/templates/index.qtpl @@ -0,0 +1,12 @@ +Index template, implements the Partial's methods. + +{% code +type Index struct {} +%} + +{% func (i *Index) Body() %} +

Index Page

+
+ This is our index page's body. +
+{% endfunc %} diff --git a/_examples/http_responsewriter/quicktemplate/templates/index.qtpl.go b/_examples/http_responsewriter/quicktemplate/templates/index.qtpl.go new file mode 100644 index 00000000..e5297ac4 --- /dev/null +++ b/_examples/http_responsewriter/quicktemplate/templates/index.qtpl.go @@ -0,0 +1,62 @@ +// This file is automatically generated by qtc from "index.qtpl". +// See https://github.com/valyala/quicktemplate for details. + +// Index template, implements the Partial's methods. +// + +//line index.qtpl:3 +package templates + +//line index.qtpl:3 +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +//line index.qtpl:3 +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +//line index.qtpl:4 +type Index struct{} + +//line index.qtpl:7 +func (i *Index) StreamBody(qw422016 *qt422016.Writer) { + //line index.qtpl:7 + qw422016.N().S(` +

Index Page

+
+ This is our index page's body. +
+`) +//line index.qtpl:12 +} + +//line index.qtpl:12 +func (i *Index) WriteBody(qq422016 qtio422016.Writer) { + //line index.qtpl:12 + qw422016 := qt422016.AcquireWriter(qq422016) + //line index.qtpl:12 + i.StreamBody(qw422016) + //line index.qtpl:12 + qt422016.ReleaseWriter(qw422016) +//line index.qtpl:12 +} + +//line index.qtpl:12 +func (i *Index) Body() string { + //line index.qtpl:12 + qb422016 := qt422016.AcquireByteBuffer() + //line index.qtpl:12 + i.WriteBody(qb422016) + //line index.qtpl:12 + qs422016 := string(qb422016.B) + //line index.qtpl:12 + qt422016.ReleaseByteBuffer(qb422016) + //line index.qtpl:12 + return qs422016 +//line index.qtpl:12 +} diff --git a/context/context.go b/context/context.go index 9c786bff..e91111a4 100644 --- a/context/context.go +++ b/context/context.go @@ -22,6 +22,7 @@ import ( "strings" "time" + "github.com/fatih/structs" "github.com/microcosm-cc/bluemonday" "github.com/monoculum/formam" "github.com/russross/blackfriday" @@ -486,6 +487,17 @@ type Context interface { // Example: https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/ ViewData(key string, value interface{}) + // GetViewData returns the values registered by `context#ViewData`. + // The return value is `map[string]interface{}`, this means that + // if a custom struct registered to ViewData then this function + // will try to parse it to map, if failed then the return value is nil + // A check for nil is always a good practise if different + // kind of values or no data are registered via `ViewData`. + // + // Similarly to `viewData := ctx.Values().Get("iris.viewData")` or + // `viewData := ctx.Values().Get(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey())`. + GetViewData() map[string]interface{} + // View renders templates based on the adapted view engines. // First argument accepts the filename, relative to the view engine's Directory, // i.e: if directory is "./templates" and want to render the "./templates/users/index.html" @@ -1626,6 +1638,43 @@ func (ctx *context) ViewData(key string, value interface{}) { } } +// GetViewData returns the values registered by `context#ViewData`. +// The return value is `map[string]interface{}`, this means that +// if a custom struct registered to ViewData then this function +// will try to parse it to map, if failed then the return value is nil +// A check for nil is always a good practise if different +// kind of values or no data are registered via `ViewData`. +// +// Similarly to `viewData := ctx.Values().Get("iris.viewData")` or +// `viewData := ctx.Values().Get(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey())`. +func (ctx *context) GetViewData() map[string]interface{} { + viewDataContextKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey() + v := ctx.Values().Get(viewDataContextKey) + + // if no values found, then return nil + if v == nil { + return nil + } + + // if struct, convert it to map[string]interface{} + if structs.IsStruct(v) { + return structs.Map(v) + } + + // if pure map[string]interface{} + if viewData, ok := v.(map[string]interface{}); ok { + return viewData + } + + // if context#Map + if viewData, ok := v.(Map); ok { + return viewData + } + + // if failure, then return nil + return nil +} + // View renders templates based on the adapted view engines. // First argument accepts the filename, relative to the view engine's Directory, // i.e: if directory is "./templates" and want to render the "./templates/users/index.html" diff --git a/core/router/handler.go b/core/router/handler.go index ab660493..9c9da320 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -210,23 +210,22 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { } if ctx.Application().ConfigurationReadOnly().GetFireMethodNotAllowed() { - var methodAllowed string for i := range h.trees { t := h.trees[i] - methodAllowed = t.Method // keep track of the allowed method of the last checked tree - if ctx.Method() != methodAllowed { - continue + // a bit slower than previous implementation but @kataras let me to apply this change + // because it's more reliable. + // + // if `Configuration#FireMethodNotAllowed` is kept as defaulted(false) then this function will not + // run, therefore performance kept as before. + if t.Nodes.Exists(path) { + // RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + // The response MUST include an Allow header containing a list of valid methods for the requested resource. + ctx.Header("Allow", t.Method) + ctx.StatusCode(http.StatusMethodNotAllowed) + return } } - - if ctx.Method() != methodAllowed { - // RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - // The response MUST include an Allow header containing a list of valid methods for the requested resource. - ctx.Header("Allow", methodAllowed) - ctx.StatusCode(http.StatusMethodNotAllowed) - return - } - } + ctx.StatusCode(http.StatusNotFound) } diff --git a/core/router/node/node.go b/core/router/node/node.go index 25912ba1..d8cb9438 100644 --- a/core/router/node/node.go +++ b/core/router/node/node.go @@ -266,6 +266,15 @@ func (nodes Nodes) Find(path string, params *context.RequestParams) context.Hand return nil } +// Exists returns true if a node with that "path" exists, +// otherise false. +// +// We don't care about parameters here. +func (nodes Nodes) Exists(path string) bool { + n, _ := nodes.findChild(path, nil) + return n != nil && len(n.handlers) > 0 +} + func (nodes Nodes) findChild(path string, params []string) (*node, []string) { // println("request path: " + path) for _, n := range nodes {