diff --git a/HISTORY.md b/HISTORY.md
index fe52f7b6..e1fb2fbc 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -395,6 +395,10 @@ func main() {
}
```
+- `iris.Minify` middleware to minify responses based on their media/content-type.
+
+- `Context.OnCloseErr` and `Context.OnConnectionCloseErr` - to call a function of `func() error` instead of an `iris.Handler` when request is closed or manually canceled.
+
- `Party.UseError(...Handler)` - to register handlers to run before the `OnErrorCode/OnAnyErrorCode` ones.
- `Party.UseRouter(...Handler)` - to register handlers before the main router, useful on handlers that should control whether the router itself should ran or not. Independently of the incoming request's method and path values. These handlers will be executed ALWAYS against ALL incoming matched requests. Example of use-case: CORS.
diff --git a/NOTICE b/NOTICE
index 53742667..716ea617 100644
--- a/NOTICE
+++ b/NOTICE
@@ -6,7 +6,7 @@
The following 3rd-party software packages may be used by or distributed with iris. This document was automatically generated by FOSSA on 2020-5-8; any information relevant to third-party vendors listed below are collected using common, reasonable means.
-Revision ID: 46a3a99adf457d30ea4aeb13ada45e895130d718
+Revision ID: ab226d925aa394ccecf01e515ea8479367e0961c
----------------- ----------------- ------------------------------------------
Package Version Website
@@ -67,7 +67,10 @@ Revision ID: 46a3a99adf457d30ea4aeb13ada45e895130d718
1029f1d962
msgpack 911bfe50493ebbc https://github.com/vmihailenco/msgpack
b6e0af9e6f36451
- 255746ff46
+ 255746ff46
+ minify 119ab8b676c60a6 https://github.com/tdewolff/minify
+ 12b9cc824e6a84a
+ 865191aabb
neffos f1431864185db0b https://github.com/kataras/neffos
334b82e3817eb03
bd957f9fda
diff --git a/_examples/i18n/main.go b/_examples/i18n/main.go
index e0a859b4..b084c3a6 100644
--- a/_examples/i18n/main.go
+++ b/_examples/i18n/main.go
@@ -14,6 +14,7 @@ func newApp() *iris.Application {
if err != nil {
panic(err)
}
+ app.I18n.Subdomain
// app.I18n.LoadAssets for go-bindata.
// Default values:
@@ -29,7 +30,6 @@ func newApp() *iris.Application {
app.Get("/", func(ctx iris.Context) {
hi := ctx.Tr("hi", "iris")
-
locale := ctx.GetLocale()
ctx.Writef("From the language %s translated output: %s", locale.Language(), hi)
diff --git a/_examples/routing/subdomains/http-errors-view/main.go b/_examples/routing/subdomains/http-errors-view/main.go
index 0fe25702..a7a04a3a 100644
--- a/_examples/routing/subdomains/http-errors-view/main.go
+++ b/_examples/routing/subdomains/http-errors-view/main.go
@@ -9,11 +9,18 @@ func main() {
func newApp() *iris.Application {
app := iris.New()
+ // Create the "test.mydomain.com" subdomain.
test := app.Subdomain("test")
+ // Register views for the test subdomain.
test.RegisterView(iris.HTML("./views", ".html").
Layout("layouts/test.layout.html"))
+ // Optionally, to minify the HTML5 error response.
+ // Note that minification might be slower, caching is advised.
+ test.UseError(iris.Minify)
+ // Register error code 404 handler.
test.OnErrorCode(iris.StatusNotFound, handleNotFoundTestSubdomain)
+
test.Get("/", testIndex)
return app
diff --git a/_examples/routing/subdomains/http-errors-view/main_test.go b/_examples/routing/subdomains/http-errors-view/main_test.go
new file mode 100644
index 00000000..7af2fa03
--- /dev/null
+++ b/_examples/routing/subdomains/http-errors-view/main_test.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+ "testing"
+
+ "github.com/kataras/iris/v12/httptest"
+)
+
+func TestSubdomainsHTTPErrorsView(t *testing.T) {
+ app := newApp()
+ // hard coded.
+ expectedHTMLResponse := `
+
+ Test Subdomain
+
+
+
+
+
+
Oups, you've got an error!
+
+
+
+
Not Found
+
+
+
+
+
+
+ `
+
+ e := httptest.New(t, app)
+ got := e.GET("/not_found").WithURL("http://test.mydomain.com").Expect().Status(httptest.StatusNotFound).
+ ContentType("text/html", "utf-8").Body().Raw()
+
+ if expected, _ := app.Minifier().String("text/html", expectedHTMLResponse); expected != got {
+ t.Fatalf("expected:\n'%s'\nbut got:\n'%s'", expected, got)
+ }
+}
diff --git a/aliases.go b/aliases.go
index 77270d9b..b609b024 100644
--- a/aliases.go
+++ b/aliases.go
@@ -244,6 +244,23 @@ var (
ctx.Next()
}
+ // Minify is a middleware which minifies the responses
+ // based on the response content type.
+ // Note that minification might be slower, caching is advised.
+ // Customize the minifier through `Application.Minifier()`.
+ Minify = func(ctx Context) {
+ w := ctx.Application().Minifier().ResponseWriter(ctx.ResponseWriter().Naive(), ctx.Request())
+ // Note(@kataras):
+ // We don't use defer w.Close()
+ // because this response writer holds a sync.WaitGroup under the hoods
+ // and we MUST be sure that its wg.Wait is called on request cancelation
+ // and not in the end of handlers chain execution
+ // (which if running a time-consuming task it will delay its resource release).
+ ctx.OnCloseErr(w.Close)
+ ctx.ResponseWriter().SetWriter(w)
+ ctx.Next()
+ }
+
// MatchImagesAssets is a simple regex expression
// that can be passed to the DirOptions.Cache.CompressIgnore field
// in order to skip compression on already-compressed file types
diff --git a/context/application.go b/context/application.go
index ab524859..b0d89e85 100644
--- a/context/application.go
+++ b/context/application.go
@@ -6,6 +6,7 @@ import (
"net/http"
"github.com/kataras/golog"
+ "github.com/tdewolff/minify/v2"
)
// Application is the context's owner.
@@ -25,6 +26,14 @@ type Application interface {
// the failure reason if not.
Validate(interface{}) error
+ // Minifier returns the minifier instance.
+ // By default it can minifies:
+ // - text/html
+ // - text/css
+ // - image/svg+xml
+ // - application/text(javascript, ecmascript, json, xml).
+ // Use that instance to add custom Minifiers before server ran.
+ Minifier() *minify.M
// View executes and write the result of a template file to the writer.
//
// Use context.View to render templates to the client instead.
diff --git a/context/context.go b/context/context.go
index 1619812e..d2ec69ae 100644
--- a/context/context.go
+++ b/context/context.go
@@ -259,6 +259,35 @@ func (ctx *Context) OnConnectionClose(cb Handler) bool {
return true
}
+// OnConnectionCloseErr same as `OnConnectionClose` but instead it
+// receives a function which returns an error.
+// If error is not nil, it will be logged as a debug message.
+func (ctx *Context) OnConnectionCloseErr(cb func() error) bool {
+ if cb == nil {
+ return false
+ }
+
+ reqCtx := ctx.Request().Context()
+ if reqCtx == nil {
+ return false
+ }
+
+ notifyClose := reqCtx.Done()
+ if notifyClose == nil {
+ return false
+ }
+
+ go func() {
+ <-notifyClose
+ if err := cb(); err != nil {
+ // Can be ignored.
+ ctx.app.Logger().Debugf("OnConnectionCloseErr: received error: %v", err)
+ }
+ }()
+
+ return true
+}
+
// OnClose registers a callback which
// will be fired when the underlying connection has gone away(request canceled)
// on its own goroutine or in the end of the request-response lifecylce
@@ -297,6 +326,36 @@ func (ctx *Context) OnClose(cb Handler) {
ctx.writer.SetBeforeFlush(onFlush)
}
+// OnCloseErr same as `OnClose` but instead it
+// receives a function which returns an error.
+// If error is not nil, it will be logged as a debug message.
+func (ctx *Context) OnCloseErr(cb func() error) {
+ if cb == nil {
+ return
+ }
+
+ var executed uint32
+
+ callback := func() error {
+ if atomic.CompareAndSwapUint32(&executed, 0, 1) {
+ return cb()
+ }
+
+ return nil
+ }
+
+ ctx.OnConnectionCloseErr(callback)
+
+ onFlush := func() {
+ if err := callback(); err != nil {
+ // Can be ignored.
+ ctx.app.Logger().Debugf("OnClose: SetBeforeFlush: received error: %v", err)
+ }
+ }
+
+ ctx.writer.SetBeforeFlush(onFlush)
+}
+
/* Note(@kataras): just leave end-developer decide.
const goroutinesContextKey = "iris.goroutines"
@@ -795,7 +854,7 @@ func GetHost(r *http.Request) string {
// Subdomain returns the subdomain of this request, if any.
// Note that this is a fast method which does not cover all cases.
func (ctx *Context) Subdomain() (subdomain string) {
- host := ctx.request.URL.Host // ctx.Host()
+ host := ctx.Host()
if index := strings.IndexByte(host, '.'); index > 0 {
subdomain = host[0:index]
}
diff --git a/go.mod b/go.mod
index ac30eb3f..fb951704 100644
--- a/go.mod
+++ b/go.mod
@@ -32,6 +32,7 @@ require (
github.com/ryanuber/columnize v2.1.0+incompatible
github.com/schollz/closestmatch v2.1.0+incompatible
github.com/square/go-jose/v3 v3.0.0-20200630053402-0a67ce9b0693
+ github.com/tdewolff/minify/v2 v2.8.0
github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1
github.com/yosssi/ace v0.0.5
go.etcd.io/bbolt v1.3.5
diff --git a/iris.go b/iris.go
index a5f733db..036a96b2 100644
--- a/iris.go
+++ b/iris.go
@@ -9,6 +9,7 @@ import (
"net"
"net/http"
"os"
+ "regexp"
"strings"
"sync"
"time"
@@ -25,6 +26,14 @@ import (
"github.com/kataras/golog"
"github.com/kataras/tunnel"
+
+ "github.com/tdewolff/minify/v2"
+ "github.com/tdewolff/minify/v2/css"
+ "github.com/tdewolff/minify/v2/html"
+ "github.com/tdewolff/minify/v2/js"
+ "github.com/tdewolff/minify/v2/json"
+ "github.com/tdewolff/minify/v2/svg"
+ "github.com/tdewolff/minify/v2/xml"
)
// Version is the current version number of the Iris Web Framework.
@@ -65,6 +74,8 @@ type Application struct {
// Validator is the request body validator, defaults to nil.
Validator context.Validator
+ // Minifier to minify responses.
+ minifier *minify.M
// view engine
view view.View
@@ -92,6 +103,7 @@ func New() *Application {
app := &Application{
config: &config,
logger: golog.Default,
+ minifier: newMinifier(),
I18n: i18n.New(),
APIBuilder: router.NewAPIBuilder(),
Router: router.NewRouter(),
@@ -250,6 +262,28 @@ func (app *Application) Validate(v interface{}) error {
return nil
}
+func newMinifier() *minify.M {
+ m := minify.New()
+ m.AddFunc("text/css", css.Minify)
+ m.AddFunc("text/html", html.Minify)
+ m.AddFunc("image/svg+xml", svg.Minify)
+ m.AddFuncRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), js.Minify)
+ m.AddFuncRegexp(regexp.MustCompile("[/+]json$"), json.Minify)
+ m.AddFuncRegexp(regexp.MustCompile("[/+]xml$"), xml.Minify)
+ return m
+}
+
+// Minifier returns the minifier instance.
+// By default it can minifies:
+// - text/html
+// - text/css
+// - image/svg+xml
+// - application/text(javascript, ecmascript, json, xml).
+// Use that instance to add custom Minifiers before server ran.
+func (app *Application) Minifier() *minify.M {
+ return app.minifier
+}
+
// RegisterView should be used to register view engines mapping to a root directory
// and the template file(s) extension.
func (app *Application) RegisterView(viewEngine view.Engine) {