diff --git a/HISTORY.md b/HISTORY.md
index c28820c7..ab402039 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -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.
diff --git a/apps/apps.go b/apps/apps.go
index 5088f180..90a07c5d 100644
--- a/apps/apps.go
+++ b/apps/apps.go
@@ -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...)
+}
diff --git a/configuration.go b/configuration.go
index 14d77a36..19adcd8a 100644
--- a/configuration.go
+++ b/configuration.go
@@ -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 = `
TimeoutTimeout
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.`
+
// 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,
diff --git a/context/application.go b/context/application.go
index 81d35413..eddfbff6 100644
--- a/context/application.go
+++ b/context/application.go
@@ -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.
diff --git a/context/configuration.go b/context/configuration.go
index 9cc03d37..67df7e55 100644
--- a/context/configuration.go
+++ b/context/configuration.go
@@ -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.
diff --git a/context/context.go b/context/context.go
index 044f7dc1..4e01695d 100644
--- a/context/context.go
+++ b/context/context.go
@@ -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)
}
diff --git a/core/router/router.go b/core/router/router.go
index 98488135..7c9897c1 100644
--- a/core/router/router.go
+++ b/core/router/router.go
@@ -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.
diff --git a/iris.go b/iris.go
index 976c99f4..c1035858 100644
--- a/iris.go
+++ b/iris.go
@@ -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()
}
diff --git a/middleware/pprof/pprof.go b/middleware/pprof/pprof.go
index f8551f33..ec9b83ee 100644
--- a/middleware/pprof/pprof.go
+++ b/middleware/pprof/pprof.go
@@ -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: