new Timeout, TimeoutMessage configuration fields and apps.OnApplicationRegistered listener

This commit is contained in:
Gerasimos (Makis) Maropoulos 2021-12-09 14:44:03 +02:00
parent 968a9ec06c
commit d6cfe3fe5b
No known key found for this signature in database
GPG Key ID: ACAB76DFB0DD3F3B
9 changed files with 124 additions and 7 deletions

View File

@ -28,6 +28,10 @@ The codebase for Dependency Injection, Internationalization and localization and
## Fixes and Improvements
- New `Configuration.Timeout` and `Configuration.TimeoutMessage` fields. Use it to set HTTP timeouts. Note that your http server's (`Application.ConfigureHost`) Read/Write timeouts should be a bit higher than the `Configuration.Timeout` in order to give some time to http timeout handler to kick in and be able to send the `Configuration.TimeoutMessage` properly.
- New `apps.OnApplicationRegistered` method which listens on new Iris applications hosted under the same binary. Use it on your `init` functions to configure Iris applications by any spot in your project's files.
- `Context.JSON` respects any object implements the `easyjson.Marshaler` interface and renders the result using the [easyjon](https://github.com/mailru/easyjson)'s writer.
- minor: `Context` structure implements the standard go Context interface now (includes: Deadline, Done, Err and Value methods). Handlers can now just pass the `ctx iris.Context` as a shortcut of `ctx.Request().Context()` when needed.

View File

@ -48,3 +48,15 @@ func GetAll() []*iris.Application {
return apps
}
// OnApplicationRegistered adds a function which fires when a new application
// is registered.
func OnApplicationRegistered(listeners ...func(app *iris.Application)) {
appListeners := make([]func(context.Application), 0, len(listeners))
for i := range listeners {
appListeners = append(appListeners, func(ctxApp context.Application) {
listeners[i](ctxApp.(*iris.Application))
})
}
context.OnApplicationRegistered(appListeners...)
}

View File

@ -208,6 +208,14 @@ func WithKeepAlive(keepAliveDur time.Duration) Configurator {
}
}
// WithTimeout sets the `Configuration.Timeout` field to the given duration.
func WithTimeout(timeoutDur time.Duration, htmlBody string) Configurator {
return func(app *Application) {
app.config.Timeout = timeoutDur
app.config.TimeoutMessage = htmlBody
}
}
// WithoutServerError will cause to ignore the matched "errors"
// from the main application's `Run/Listen` function.
//
@ -498,7 +506,6 @@ func WithSitemap(startURL string) Configurator {
} else {
href = "/" + langPath + loc
}
} else if app.I18n.Subdomain {
// then use the subdomain.
// e.g. http://el.domain.com/path
@ -627,6 +634,17 @@ type Configuration struct {
//
// Defaults to 0.
KeepAlive time.Duration `ini:"keepalive" json:"keepAlive" yaml:"KeepAlive" toml:"KeepAlive" env:"KEEP_ALIVE"`
// Timeout wraps the application's router with an http timeout handler
// if the value is greater than zero.
//
// The underline response writer supports the Pusher interface but does not support
// the Hijacker or Flusher interfaces when Timeout handler is registered.
//
// Read more at: https://pkg.go.dev/net/http#TimeoutHandler.
Timeout time.Duration `ini:"timeout" json:"timeout" yaml:"Timeout" toml:"Timeout"`
// TimeoutMessage specifies the HTML body when a handler hits its life time based
// on the Timeout configuration field.
TimeoutMessage string `ini:"timeout_message" json:"timeoutMessage" yaml:"TimeoutMessage" toml:"TimeoutMessage"`
// Tunneling can be optionally set to enable ngrok http(s) tunneling for this Iris app instance.
// See the `WithTunneling` Configurator too.
Tunneling TunnelingConfiguration `ini:"tunneling" json:"tunneling,omitempty" yaml:"Tunneling" toml:"Tunneling"`
@ -914,6 +932,16 @@ func (c Configuration) GetKeepAlive() time.Duration {
return c.KeepAlive
}
// GetKeepAlive returns the Timeout field.
func (c Configuration) GetTimeout() time.Duration {
return c.Timeout
}
// GetKeepAlive returns the TimeoutMessage field.
func (c Configuration) GetTimeoutMessage() string {
return c.TimeoutMessage
}
// GetDisablePathCorrection returns the DisablePathCorrection field.
func (c Configuration) GetDisablePathCorrection() bool {
return c.DisablePathCorrection
@ -1088,6 +1116,14 @@ func WithConfiguration(c Configuration) Configurator {
main.KeepAlive = v
}
if v := c.Timeout; v > 0 {
main.Timeout = v
}
if v := c.TimeoutMessage; v != "" {
main.TimeoutMessage = v
}
if len(c.Tunneling.Tunnels) > 0 {
main.Tunneling = c.Tunneling
}
@ -1234,12 +1270,18 @@ func WithConfiguration(c Configuration) Configurator {
}
}
// DefaultTimeoutMessage is the default timeout message which is rendered
// on expired handlers when timeout handler is registered (see Timeout configuration field).
var DefaultTimeoutMessage = `<html><head><title>Timeout</title></head><body><h1>Timeout</h1>Looks like the server is taking too long to respond, this can be caused by either poor connectivity or an error with our servers. Please try again in a while.</body></html>`
// DefaultConfiguration returns the default configuration for an iris station, fills the main Configuration
func DefaultConfiguration() Configuration {
return Configuration{
LogLevel: "info",
SocketSharding: false,
KeepAlive: 0,
Timeout: 0,
TimeoutMessage: DefaultTimeoutMessage,
DisableStartupLog: false,
DisableInterruptHandler: false,
DisablePathCorrection: false,

View File

@ -113,8 +113,9 @@ var (
// It's slice instead of map because if IRIS_APP_NAME env var exists,
// by-default all applications running on the same machine
// will have the same name unless `Application.SetName` is called.
registeredApps []Application
mu sync.RWMutex
registeredApps []Application
onApplicationRegisteredListeners []func(Application)
mu sync.RWMutex
)
// RegisterApplication registers an application to the global shared storage.
@ -126,6 +127,20 @@ func RegisterApplication(app Application) {
mu.Lock()
registeredApps = append(registeredApps, app)
mu.Unlock()
mu.RLock()
for _, listener := range onApplicationRegisteredListeners {
listener(app)
}
mu.RUnlock()
}
// OnApplicationRegistered adds a function which fires when a new application
// is registered.
func OnApplicationRegistered(listeners ...func(app Application)) {
mu.Lock()
onApplicationRegisteredListeners = append(onApplicationRegisteredListeners, listeners...)
mu.Unlock()
}
// GetApplications returns a slice of all the registered Applications.

View File

@ -20,6 +20,10 @@ type ConfigurationReadOnly interface {
GetSocketSharding() bool
// GetKeepAlive returns the KeepAlive field.
GetKeepAlive() time.Duration
// GetKeepAlive returns the Timeout field.
GetTimeout() time.Duration
// GetKeepAlive returns the TimeoutMessage field.
GetTimeoutMessage() string
// GetDisablePathCorrection returns the DisablePathCorrection field
GetDisablePathCorrection() bool
// GetDisablePathCorrectionRedirection returns the DisablePathCorrectionRedirection field.

View File

@ -708,7 +708,6 @@ func (ctx *Context) StopExecution() {
// And stop.
ctx.currentHandlerIndex = stopExecutionIndex
}
}
// IsStopped reports whether the current position of the context's handlers is -1,
@ -2697,7 +2696,6 @@ func (ctx *Context) ReadMsgPack(ptr interface{}) error {
// As a special case if the "ptr" was a pointer to string or []byte
// then it will bind it to the request body as it is.
func (ctx *Context) ReadBody(ptr interface{}) error {
// If the ptr is string or byte, read the body as it's.
switch v := ptr.(type) {
case *string:
@ -4871,7 +4869,6 @@ func CookieAllowReclaim(cookieNames ...string) CookieOption {
header.Del("Cookie")
}
}
}
// CookieAllowSubdomains set to the Cookie Options
@ -5888,5 +5885,12 @@ func (ctx *Context) GetID() interface{} {
// It returns the Context's ID given by a `SetID`call,
// followed by the client's IP and the method:uri.
func (ctx *Context) String() string {
return fmt.Sprintf("[%s] %s ▶ %s:%s", ctx.GetID(), ctx.RemoteAddr(), ctx.Method(), ctx.Request().RequestURI)
id := ctx.GetID()
if id != nil {
if stringer, ok := id.(fmt.Stringer); ok {
id = stringer.String()
}
}
return fmt.Sprintf("[%v] %s ▶ %s:%s", id, ctx.RemoteAddr(), ctx.Method(), ctx.Request().RequestURI)
}

View File

@ -6,6 +6,7 @@ import (
"sort"
"strings"
"sync"
"time"
"github.com/kataras/iris/v12/context"
@ -271,6 +272,25 @@ func (router *Router) Downgraded() bool {
return router.mainHandler != nil && router.requestHandler == nil
}
// SetTimeoutHandler overrides the main handler with a timeout handler.
//
// TimeoutHandler supports the Pusher interface but does not support
// the Hijacker or Flusher interfaces.
//
// All previous registered wrappers and middlewares are still executed as expected.
func (router *Router) SetTimeoutHandler(timeout time.Duration, msg string) {
if timeout <= 0 {
return
}
mainHandler := router.mainHandler
h := func(w http.ResponseWriter, r *http.Request) {
mainHandler(w, r)
}
router.mainHandler = http.TimeoutHandler(http.HandlerFunc(h), timeout, msg).ServeHTTP
}
// WrapRouter adds a wrapper on the top of the main router.
// Usually it's useful for third-party middleware
// when need to wrap the entire application with a middleware like CORS.

View File

@ -640,6 +640,11 @@ func (app *Application) Build() error {
return err
}
app.HTTPErrorHandler = routerHandler
if app.config.Timeout > 0 {
app.Router.SetTimeoutHandler(app.config.Timeout, app.config.TimeoutMessage)
}
// re-build of the router from outside can be done with
// app.RefreshRouter()
}

View File

@ -12,6 +12,17 @@ import (
func init() {
context.SetHandlerName("iris/middleware/pprof.*", "iris.profiling")
/* for our readers:
apps.OnApplicationRegistered(func(app *iris.Application) {
app.Any("/debug/pprof/cmdline", iris.FromStd(pprof.Cmdline))
app.Any("/debug/pprof/profile", iris.FromStd(pprof.Profile))
app.Any("/debug/pprof/symbol", iris.FromStd(pprof.Symbol))
app.Any("/debug/pprof/trace", iris.FromStd(pprof.Trace))
app.Any("/debug/pprof /debug/pprof/{action:string}", New())
})
*/
}
// net/http/pprof copy: