add iris.Minify middleware and Context.OnCloseErr/OnConnectionCloseErr

This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-08-15 17:21:57 +03:00
parent ab226d925a
commit ef7d365e81
No known key found for this signature in database
GPG Key ID: 5DBE766BD26A54E7
10 changed files with 178 additions and 4 deletions

View File

@ -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.

5
NOTICE
View File

@ -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
@ -68,6 +68,9 @@ Revision ID: 46a3a99adf457d30ea4aeb13ada45e895130d718
msgpack 911bfe50493ebbc https://github.com/vmihailenco/msgpack
b6e0af9e6f36451
255746ff46
minify 119ab8b676c60a6 https://github.com/tdewolff/minify
12b9cc824e6a84a
865191aabb
neffos f1431864185db0b https://github.com/kataras/neffos
334b82e3817eb03
bd957f9fda

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,40 @@
package main
import (
"testing"
"github.com/kataras/iris/v12/httptest"
)
func TestSubdomainsHTTPErrorsView(t *testing.T) {
app := newApp()
// hard coded.
expectedHTMLResponse := `<html>
<head>
<title>Test Subdomain</title>
</head>
<body>
<div style="background-color: black; color: red">
<h1>Oups, you've got an error!</h1>
<div style="background-color: white; color: red">
<h1>Not Found</h1>
</div>
</div>
</body>
</html>`
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)
}
}

View File

@ -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

View File

@ -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.

View File

@ -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]
}

1
go.mod
View File

@ -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

34
iris.go
View File

@ -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) {