1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-23 21:07:03 +00:00

(#1554) Add support for all common compressions (write and read)

- Remove the context.Context interface and export the *context, the iris.Context now points to the pointer\nSupport compression and rate limiting in the FileServer\nBit of code organisation


Former-commit-id: ad1c61bf968059510c6be9e7f2cceec7da70ba17
This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-07-10 23:21:09 +03:00
parent 645da2b2ef
commit 0f113dfcda
112 changed files with 2119 additions and 3390 deletions

View File

@@ -54,9 +54,6 @@
* [Reverse Routing](routing/reverse/main.go)
* [Router Wrapper](routing/custom-wrapper/main.go)
* [Custom Router](routing/custom-router/main.go)
* Custom Context
* [Method Overriding](routing/custom-context/method-overriding/main.go)
* [New Implementation](routing/custom-context/new-implementation/main.go)
* Subdomains
* [Single](routing/subdomains/single/main.go)
* [Multi](routing/subdomains/multi/main.go)
@@ -129,12 +126,10 @@
* [Bind Custom per type](request-body/read-custom-per-type/main.go)
* [Bind Custom via Unmarshaler](request-body/read-custom-via-unmarshaler/main.go)
* [Bind Many times](request-body/read-many/main.go)
* [Read/Bind Gzip compressed data](request-body/read-gzip/main.go)
* Response Writer
* [Content Negotiation](response-writer/content-negotiation)
* [Text, Markdown, YAML, HTML, JSON, JSONP, Msgpack, XML and Binary](response-writer/write-rest/main.go)
* [Protocol Buffers](response-writer/protobuf/main.go)
* [Write Gzip](response-writer/write-gzip/main.go)
* [HTTP/2 Server Push](response-writer/http2push/main.go)
* [Stream Writer](response-writer/stream-writer/main.go)
* [Transactions](response-writer/transactions/main.go)
@@ -143,6 +138,10 @@
* Cache
* [Simple](response-writer/cache/simple/main.go)
* [Client-Side (304)](response-writer/cache/client-side/main.go)
* Compression
* [Server-Side](compression/main.go)
* [Client-Side](compression/client/main.go)
* [Client-Side (using Iris)](compress/client-using-iris/main.go)
* Localization and Internationalization
* [i18n](i18n/main.go)
* Authentication, Authorization & Bot Detection

View File

@@ -0,0 +1,112 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/kataras/iris/v12/context"
)
const baseURL = "http://localhost:8080"
// Available options:
// - "gzip",
// - "deflate",
// - "br" (for brotli),
// - "snappy" and
// - "s2"
const encoding = context.BROTLI
var client = http.DefaultClient
func main() {
fmt.Printf("Running client example on: %s\n", baseURL)
getExample()
postExample()
}
func getExample() {
endpoint := baseURL + "/"
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
panic(err)
}
// Required to receive server's compressed data.
req.Header.Set("Accept-Encoding", encoding)
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// decompress server's compressed reply.
cr, err := context.NewCompressReader(resp.Body, encoding)
if err != nil {
panic(err)
}
defer cr.Close()
body, err := ioutil.ReadAll(cr)
if err != nil {
panic(err)
}
fmt.Printf("Received from server: %s", string(body))
}
type payload struct {
Username string `json:"username"`
}
func postExample() {
buf := new(bytes.Buffer)
// Compress client's data.
cw, err := context.NewCompressWriter(buf, encoding, -1)
if err != nil {
panic(err)
}
json.NewEncoder(cw).Encode(payload{Username: "Edward"})
// `Close` or `Flush` required before `NewRequest` call.
cw.Close()
endpoint := baseURL + "/"
req, err := http.NewRequest(http.MethodPost, endpoint, buf)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
// Required to send gzip compressed data to the server.
req.Header.Set("Content-Encoding", encoding)
// Required to receive server's compressed data.
req.Header.Set("Accept-Encoding", encoding)
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Decompress server's compressed reply.
cr, err := context.NewCompressReader(resp.Body, encoding)
if err != nil {
panic(err)
}
defer cr.Close() // Closes the request body too.
body, err := ioutil.ReadAll(cr)
if err != nil {
panic(err)
}
fmt.Printf("Server replied with: %s", string(body))
}

View File

@@ -0,0 +1,102 @@
package main
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
var client = http.DefaultClient
const baseURL = "http://localhost:8080"
func main() {
fmt.Printf("Running client example on: %s\n", baseURL)
getExample()
postExample()
}
func getExample() {
endpoint := baseURL + "/"
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
panic(err)
}
// Required to receive server's compressed data.
req.Header.Set("Accept-Encoding", "gzip")
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// decompress server's compressed reply.
r, err := gzip.NewReader(resp.Body)
if err != nil {
panic(err)
}
defer r.Close()
body, err := ioutil.ReadAll(r)
if err != nil {
panic(err)
}
fmt.Printf("Received from server: %s", string(body))
}
type payload struct {
Username string `json:"username"`
}
func postExample() {
buf := new(bytes.Buffer)
// Compress client's data.
w := gzip.NewWriter(buf)
b, err := json.Marshal(payload{Username: "Edward"})
if err != nil {
panic(err)
}
w.Write(b)
w.Close()
endpoint := baseURL + "/"
req, err := http.NewRequest(http.MethodPost, endpoint, buf)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
// Required to send gzip compressed data to the server.
req.Header.Set("Content-Encoding", "gzip")
// Required to receive server's compressed data.
req.Header.Set("Accept-Encoding", "gzip")
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Decompress server's compressed reply.
r, err := gzip.NewReader(resp.Body)
if err != nil {
panic(err)
}
defer r.Close()
body, err := ioutil.ReadAll(r)
if err != nil {
panic(err)
}
fmt.Printf("Server replied with: %s", string(body))
}

View File

@@ -0,0 +1,64 @@
package main
import "github.com/kataras/iris/v12"
func main() {
app := newApp()
app.Logger().SetLevel("debug")
app.Listen(":8080")
}
func newApp() *iris.Application {
app := iris.New()
// HERE and you are ready to GO:
app.Use(iris.Compress, iris.CompressReader)
app.Get("/", send)
app.Post("/", receive)
return app
}
type payload struct {
Username string `json:"username"`
}
func send(ctx iris.Context) {
ctx.JSON(payload{
Username: "Makis",
})
}
func receive(ctx iris.Context) {
var p payload
if err := ctx.ReadJSON(&p); err != nil {
ctx.Application().Logger().Debugf("ReadJSON: %v", err)
}
ctx.WriteString(p.Username)
}
/* Manually:
func enableCompression(ctx iris.Context) {
// Enable writing using compression (deflate, gzip, brotli, snappy, s2):
err := ctx.Compress(true)
if err != nil {
ctx.Application().Logger().Debugf("writer: %v", err)
// if you REQUIRE server to SEND compressed data then `return` here.
// return
}
// Enable reading and binding request's compressed data:
err = ctx.CompressReader(true)
if err != nil &&
// on GET we don't expect writing with gzip from client
ctx.Method() != iris.MethodGet {
ctx.Application().Logger().Debugf("reader: %v", err)
// if you REQUIRE server to RECEIVE only
// compressed data then `return` here.
// return
}
ctx.Next()
}
*/

View File

@@ -0,0 +1,41 @@
package main
import (
"encoding/json"
"reflect"
"strings"
"testing"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/httptest"
)
func TestCompression(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
var expectedReply = payload{Username: "Makis"}
body := e.GET("/").WithHeader(context.AcceptEncodingHeaderKey, context.GZIP).Expect().
Status(httptest.StatusOK).
ContentEncoding(context.GZIP).
ContentType(context.ContentJSONHeaderValue).Body().Raw()
// Note that .Expect() consumes the response body
// and stores it to unexported "contents" field
// therefore, we retrieve it as string and put it to a new buffer.
r := strings.NewReader(body)
cr, err := context.NewCompressReader(r, context.GZIP)
if err != nil {
t.Fatal(err)
}
defer cr.Close()
var got payload
if err = json.NewDecoder(cr).Decode(&got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expectedReply, got) {
t.Fatalf("expected %#+v but got %#+v", expectedReply, got)
}
}

View File

@@ -39,6 +39,3 @@ func negronilikeTestMiddleware(w http.ResponseWriter, r *http.Request, next http
w.WriteHeader(iris.StatusBadRequest)
w.Write([]byte("Bad request"))
}
// Look "routing/custom-context" if you want to convert a custom handler with a custom Context
// to a context.Handler.

View File

@@ -29,6 +29,3 @@ func main() {
func nativeTestMiddleware(w http.ResponseWriter, r *http.Request) {
println("Request path: " + r.URL.Path)
}
// Look "routing/custom-context" if you want to convert a custom handler with a custom Context
// to a context.Handler.

View File

@@ -23,7 +23,7 @@ func newApp() *iris.Application {
// if end developer does not managed to handle it by hand.
IndexName: "/index.html",
// When files should served under compression.
Gzip: false,
Compress: false,
// List the files inside the current requested directory if `IndexName` not found.
ShowList: false,
// If `ShowList` is true then this function will be used instead of the default one to show the list of files of a current requested directory(dir).

View File

@@ -54,7 +54,7 @@ func main() {
filesRouter := app.Party("/files")
{
filesRouter.HandleDir("/", uploadDir, iris.DirOptions{
Gzip: false,
Compress: true,
ShowList: true,
// Optionally, force-send files to the client inside of showing to the browser.

View File

@@ -83,7 +83,11 @@
{{ range $idx, $file := .Files }}
<tr>
<td>{{ $idx }}</td>
{{ if $file.Download }}
<td><a href="{{ $file.Path }}" title="{{ $file.ModTime }}" download>{{ $file.Name }}</a></td>
{{ else }}
<td><a href="{{ $file.Path }}" title="{{ $file.ModTime }}">{{ $file.Name }}</a></td>
{{ end }}
{{ if $file.Info.IsDir }}
<td>Dir</td>
{{ else }}

View File

@@ -1,44 +0,0 @@
package main
import (
"github.com/kataras/iris/v12"
)
func main() {
app := newApp()
app.Logger().SetLevel("debug")
app.Listen(":8080")
}
type payload struct {
Message string `json:"message"`
}
func newApp() *iris.Application {
app := iris.New()
// GzipReader is a middleware which enables gzip decompression,
// when client sends gzip compressed data.
//
// A shortcut of:
// func(ctx iris.Context) {
// ctx.GzipReader(true)
// ctx.Next()
// }
app.Use(iris.GzipReader)
app.Post("/", func(ctx iris.Context) {
// Bind incoming gzip compressed JSON to "p".
var p payload
if err := ctx.ReadJSON(&p); err != nil {
ctx.StopWithError(iris.StatusBadRequest, err)
return
}
// Send back the message as plain text.
ctx.WriteString(p.Message)
})
return app
}

View File

@@ -1,38 +0,0 @@
package main
import (
"bytes"
"compress/gzip"
"encoding/json"
"testing"
"github.com/kataras/iris/v12/httptest"
)
func TestGzipReader(t *testing.T) {
app := newApp()
expected := payload{Message: "test"}
b, err := json.Marshal(expected)
if err != nil {
t.Fatal(err)
}
buf := new(bytes.Buffer)
w := gzip.NewWriter(buf)
_, err = w.Write(b)
if err != nil {
t.Fatal(err)
}
err = w.Close()
if err != nil {
t.Fatal(err)
}
e := httptest.New(t, app)
// send gzip compressed.
e.POST("/").WithHeader("Content-Encoding", "gzip").WithHeader("Content-Type", "application/json").
WithBytes(buf.Bytes()).Expect().Status(httptest.StatusOK).Body().Equal(expected.Message)
// raw.
e.POST("/").WithJSON(expected).Expect().Status(httptest.StatusOK).Body().Equal(expected.Message)
}

View File

@@ -1,24 +0,0 @@
package main
import "github.com/kataras/iris/v12"
func main() {
app := iris.New()
// app.Use(iris.Gzip)
// func(ctx iris.Context) { ctx.Gzip(true/false)}
// OR:
app.Get("/", func(ctx iris.Context) {
ctx.WriteGzip([]byte("Hello World!"))
ctx.Header("X-Custom",
"Headers can be set here after WriteGzip as well, because the data are kept before sent to the client when using the context's GzipResponseWriter and ResponseRecorder.")
})
app.Get("/2", func(ctx iris.Context) {
// same as the `WriteGzip`.
// However GzipResponseWriter gives you more options, like
// reset data, disable and more, look its methods.
ctx.GzipResponseWriter().WriteString("Hello World!")
})
app.Listen(":8080")
}

View File

@@ -1,90 +0,0 @@
package main
// In this package I'll show you how to override the existing Context's functions and methods.
// You can easly navigate to the custom-context example to see how you can add new functions
// to your own context (need a custom handler).
//
// This way is far easier to understand and it's faster when you want to override existing methods:
import (
"reflect"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
)
// Create your own custom Context, put any fields you wanna need.
type MyContext struct {
// Optional Part 1: embed (optional but required if you don't want to override all context's methods)
iris.Context
}
var _ iris.Context = &MyContext{} // optionally: validate on compile-time if MyContext implements context.Context.
// The only one important if you will override the Context
// with an embedded context.Context inside it.
// Required in order to run the handlers via this "*MyContext".
func (ctx *MyContext) Do(handlers context.Handlers) {
context.Do(ctx, handlers)
}
// The second one important if you will override the Context
// with an embedded context.Context inside it.
// Required in order to run the chain of handlers via this "*MyContext".
func (ctx *MyContext) Next() {
context.Next(ctx)
}
// Override any context's method you want...
// [...]
func (ctx *MyContext) HTML(format string, args ...interface{}) (int, error) {
ctx.Application().Logger().Infof("Executing .HTML function from MyContext")
ctx.ContentType("text/html")
return ctx.Writef(format, args...)
}
func main() {
app := iris.New()
// app.Logger().SetLevel("debug")
// The only one Required:
// here is how you define how your own context will
// be created and acquired from the iris' generic context pool.
app.ContextPool.Attach(func() iris.Context {
return &MyContext{
// Optional Part 3:
Context: context.NewContext(app),
}
})
// Register a view engine on .html files inside the ./view/** directory.
app.RegisterView(iris.HTML("./view", ".html"))
// register your route, as you normally do
app.Handle("GET", "/", recordWhichContextJustForProofOfConcept, func(ctx iris.Context) {
// use the context's overridden HTML method.
ctx.HTML("<h1> Hello from my custom context's HTML! </h1>")
})
// this will be executed by the MyContext.Context
// if MyContext is not directly define the View function by itself.
app.Handle("GET", "/hi/{firstname:alphabetical}", recordWhichContextJustForProofOfConcept, func(ctx iris.Context) {
firstname := ctx.Params().Get("firstname")
ctx.ViewData("firstname", firstname)
ctx.Gzip(true)
ctx.View("hi.html")
})
app.Listen(":8080")
}
// should always print "($PATH) Handler is executing from 'MyContext'"
func recordWhichContextJustForProofOfConcept(ctx iris.Context) {
ctx.Application().Logger().Infof("(%s) Handler is executing from: '%s'", ctx.Path(), reflect.TypeOf(ctx).Elem().Name())
ctx.Next()
}
// Look "new-implementation" to see how you can create an entirely new Context with new functions.

View File

@@ -1 +0,0 @@
<h1> Hi {{.firstname}} </h1>

View File

@@ -1,103 +0,0 @@
package main
import (
"sync"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/sessions"
)
// Owner is our application structure, it contains the methods or fields we need,
// think it as the owner of our *Context.
type Owner struct {
// define here the fields that are global
// and shared to all clients.
sessionsManager *sessions.Sessions
}
// this package-level variable "application" will be used inside context to communicate with our global Application.
var owner = &Owner{
sessionsManager: sessions.New(sessions.Config{Cookie: "mysessioncookie"}),
}
// Context is our custom context.
// Let's implement a context which will give us access
// to the client's Session with a trivial `ctx.Session()` call.
type Context struct {
iris.Context
session *sessions.Session
}
// Session returns the current client's session.
func (ctx *Context) Session() *sessions.Session {
// this help us if we call `Session()` multiple times in the same handler
if ctx.session == nil {
// start a new session if not created before.
ctx.session = owner.sessionsManager.Start(ctx.Context)
}
return ctx.session
}
// Bold will send a bold text to the client.
func (ctx *Context) Bold(text string) {
ctx.HTML("<b>" + text + "</b>")
}
var contextPool = sync.Pool{New: func() interface{} {
return &Context{}
}}
func acquire(original iris.Context) *Context {
ctx := contextPool.Get().(*Context)
ctx.Context = original // set the context to the original one in order to have access to iris's implementation.
ctx.session = nil // reset the session
return ctx
}
func release(ctx *Context) {
contextPool.Put(ctx)
}
// Handler will convert our handler of func(*Context) to an iris Handler,
// in order to be compatible with the HTTP API.
func Handler(h func(*Context)) iris.Handler {
return func(original iris.Context) {
ctx := acquire(original)
h(ctx)
release(ctx)
}
}
func newApp() *iris.Application {
app := iris.New()
// Work as you did before, the only difference
// is that the original context.Handler should be wrapped with our custom
// `Handler` function.
app.Get("/", Handler(func(ctx *Context) {
ctx.Bold("Hello from our *Context")
}))
app.Post("/set", Handler(func(ctx *Context) {
nameFieldValue := ctx.FormValue("name")
ctx.Session().Set("name", nameFieldValue)
ctx.Writef("set session = " + nameFieldValue)
}))
app.Get("/get", Handler(func(ctx *Context) {
name := ctx.Session().GetString("name")
ctx.Writef(name)
}))
return app
}
func main() {
app := newApp()
// GET: http://localhost:8080
// POST: http://localhost:8080/set
// GET: http://localhost:8080/get
app.Listen(":8080")
}

View File

@@ -1,26 +0,0 @@
package main
import (
"testing"
"github.com/kataras/iris/v12/httptest"
)
func TestCustomContextNewImpl(t *testing.T) {
app := newApp()
e := httptest.New(t, app, httptest.URL("http://localhost:8080"))
e.GET("/").Expect().
Status(httptest.StatusOK).
ContentType("text/html").
Body().Equal("<b>Hello from our *Context</b>")
expectedName := "iris"
e.POST("/set").WithFormField("name", expectedName).Expect().
Status(httptest.StatusOK).
Body().Equal("set session = " + expectedName)
e.GET("/get").Expect().
Status(httptest.StatusOK).
Body().Equal(expectedName)
}

View File

@@ -14,7 +14,7 @@ import (
Build(provider router.RoutesProvider) error
- RouteExists reports whether a particular route exists.
RouteExists(ctx iris.Context, method, path string) bool
- FireErrorCode(ctx context.Context) should handle the given ctx.GetStatusCode().
- FireErrorCode(ctx iris.Context) should handle the given ctx.GetStatusCode().
For a more detailed, complete and useful example
you can take a look at the iris' router itself which is located at:

View File

@@ -179,7 +179,7 @@ func main() {
app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) {
id := ctx.Params().GetUint64Default("id", 0)
friendid := ctx.Params().GetUint64Default("friendid", 0)
ctx.Writef("Hello id: %d looking for friend id: ", id, friendid)
ctx.Writef("Hello id: %d looking for friend id: %d", id, friendid)
}) // this will throw e 504 error code instead of 404 if all route's macros not passed.
// :uint8 0 to 255.

View File

@@ -17,7 +17,7 @@ func main() {
app := iris.New()
app.Get("/users", func(ctx iris.Context) {
ctx.Gzip(true)
ctx.Compress(true)
ctx.ContentType("text/html")
userList := []string{
@@ -35,11 +35,9 @@ func main() {
// template.UserList(userList, buffer)
// ctx.Write(buffer.Bytes())
// using an io.Writer for automatic buffer management (i.e. hero built-in buffer pool),
// iris context implements the io.Writer by its ResponseWriter
// which is an enhanced version of the standard http.ResponseWriter
// but still 100% compatible, GzipResponseWriter too:
// _, err := template.UserListToWriter(userList, ctx.GzipResponseWriter())
// iris context implements the io.Writer:
// _, err := template.UserListToWriter(userList, ctx)
// OR:
buffer := new(bytes.Buffer)
template.UserList(userList, buffer)

View File

@@ -14,9 +14,9 @@ func main() {
// - {{ current }}
app.RegisterView(iris.HTML("./templates", ".html"))
app.Get("/", func(ctx iris.Context) {
ctx.ViewData("Name", "iris") // the .Name inside the ./templates/hi.html
ctx.Gzip(true) // enable gzip for big files
ctx.View("hi.html") // render the template with the file name relative to the './templates'
ctx.Compress(true) // enable compression based on Accept-Encoding (e.g. "gzip").
ctx.ViewData("Name", "iris") // the .Name inside the ./templates/hi.html.
ctx.View("hi.html") // render the template with the file name relative to the './templates'.
})
// http://localhost:8080/

View File

@@ -6,9 +6,9 @@ import (
"github.com/kataras/iris/v12"
)
// ExecuteTemplate renders a "tmpl" partial template to the `context#ResponseWriter`.
// ExecuteTemplate renders a "tmpl" partial template to the `Context.ResponseWriter`.
func ExecuteTemplate(ctx iris.Context, tmpl templates.Partial) {
ctx.Gzip(true)
ctx.Compress(true)
ctx.ContentType("text/html")
templates.WriteTemplate(ctx.ResponseWriter(), tmpl)
templates.WriteTemplate(ctx, tmpl)
}

View File

@@ -16,7 +16,7 @@ func main() {
// TIP: append .Reload(true) to reload the templates on each request.
app.Get("/", func(ctx iris.Context) {
ctx.Gzip(true)
ctx.Compress(true)
ctx.ViewData("", mypage{"My Page title", "Hello world!"})
ctx.View("mypage.html")
// Note that: you can pass "layout" : "otherLayout.html" to bypass the config's Layout property

View File

@@ -201,9 +201,9 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"views/includes/_partial.jet": viewsIncludes_partialJet,
"views/includes/blocks.jet": viewsIncludesBlocksJet,
"views/index.jet": viewsIndexJet,
"views/includes/_partial.jet": viewsIncludes_partialJet,
"views/includes/blocks.jet": viewsIncludesBlocksJet,
"views/index.jet": viewsIndexJet,
"views/layouts/application.jet": viewsLayoutsApplicationJet,
}
@@ -246,15 +246,16 @@ type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"views": &bintree{nil, map[string]*bintree{
"includes": &bintree{nil, map[string]*bintree{
"_partial.jet": &bintree{viewsIncludes_partialJet, map[string]*bintree{}},
"blocks.jet": &bintree{viewsIncludesBlocksJet, map[string]*bintree{}},
"views": {nil, map[string]*bintree{
"includes": {nil, map[string]*bintree{
"_partial.jet": {viewsIncludes_partialJet, map[string]*bintree{}},
"blocks.jet": {viewsIncludesBlocksJet, map[string]*bintree{}},
}},
"index.jet": &bintree{viewsIndexJet, map[string]*bintree{}},
"layouts": &bintree{nil, map[string]*bintree{
"application.jet": &bintree{viewsLayoutsApplicationJet, map[string]*bintree{}},
"index.jet": {viewsIndexJet, map[string]*bintree{}},
"layouts": {nil, map[string]*bintree{
"application.jet": {viewsLayoutsApplicationJet, map[string]*bintree{}},
}},
}},
}}
@@ -305,4 +306,3 @@ func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

View File

@@ -17,7 +17,7 @@ func main() {
app.HandleDir("/", "./client")
app.Get("/", func(ctx iris.Context) {
// ctx.Gzip(true)
// ctx.Compress(true)
ctx.ServeFile("./client/hello.html")
})