diff --git a/HISTORY.md b/HISTORY.md
index 9e57c7db..030f78ce 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -15,9 +15,83 @@
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
> Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes.
-**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
-For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework).
+**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
+# Th, 13 July 2017 | v8.0.1
+
+Nothing tremendous at this minor version.
+
+We've just added a configuration field in order to ignore errors received by the `Run` function, see below.
+
+[Configuration#IgnoreServerErrors](https://github.com/kataras/iris/blob/master/configuration.go#L255)
+```go
+type Configuration struct {
+ // [...]
+
+ // IgnoreServerErrors will cause to ignore the matched "errors"
+ // from the main application's `Run` function.
+ // This is a slice of string, not a slice of error
+ // users can register these errors using yaml or toml configuration file
+ // like the rest of the configuration fields.
+ //
+ // See `WithoutServerError(...)` function too.
+ //
+ // Defaults to an empty slice.
+ IgnoreServerErrors []string `yaml:"IgnoreServerErrors" toml:"IgnoreServerErrors"`
+
+ // [...]
+}
+```
+[Configuration#WithoutServerError](https://github.com/kataras/iris/blob/master/configuration.go#L106)
+```go
+// WithoutServerError will cause to ignore the matched "errors"
+// from the main application's `Run` function.
+//
+// Usage:
+// err := app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
+// will return `nil` if the server's error was `http/iris#ErrServerClosed`.
+//
+// See `Configuration#IgnoreServerErrors []string` too.
+WithoutServerError(errors ...error) Configurator
+```
+
+By default no error is being ignored, of course.
+
+Example code:
+[_examples/http-listening/omit-server-errors](https://github.com/kataras/iris/tree/master/_examples/http-listening/omit-server-errors)
+```go
+package main
+
+import (
+ "github.com/kataras/iris"
+ "github.com/kataras/iris/context"
+)
+
+func main() {
+ app := iris.New()
+
+ app.Get("/", func(ctx context.Context) {
+ ctx.HTML("
Hello World!/
")
+ })
+
+ err := app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
+ if err != nil {
+ // do something
+ }
+ // same as:
+ // err := app.Run(iris.Addr(":8080"))
+ // if err != nil && (err != iris.ErrServerClosed || err.Error() != iris.ErrServerClosed.Error()) {
+ // [...]
+ // }
+}
+```
+
+At first we didn't want to implement something like that because it's ridiculous easy to do it manually but a second thought came to us,
+that many applications are based on configuration, therefore it would be nice to have something to ignore errors
+by simply string values that can be passed to the application's configuration via `toml` or `yaml` files too.
+
+This feature has been implemented after a request of ignoring the `iris/http#ErrServerClosed` from the `Run` function:
+https://github.com/kataras/iris/issues/668
# Mo, 10 July 2017 | v8.0.0
diff --git a/README.md b/README.md
index 213d0465..a19c9793 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ These types of projects need heart and sacrifices to continue offer the best dev
* [Installation](#-installation)
-* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#mo-10-july-2017--v800)
+* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#th-13-july-2017--v801)
* [Learn](#-learn)
* [HTTP Listening](_examples/#http-listening)
* [Configuration](_examples/#configuration)
@@ -215,7 +215,7 @@ _iris_ does not force you to use any specific ORM. With support for the most pop
### 📌 Version
-Current: **8.0.0**
+Current: **8.0.1**
Each new release is pushed to the master. It stays there until the next version. When a next version is released then the previous version goes to its own branch with `gopkg.in` as its import path (and its own vendor folder), in order to keep it working "for-ever".
diff --git a/_examples/README.md b/_examples/README.md
index b9ab6a34..63d5633d 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -16,6 +16,7 @@ It doesn't always contain the "best ways" but it does cover each important featu
### HTTP Listening
- [Common, with address](http-listening/listen-addr/main.go)
+ * [omit server errors](http-listening/listen-addr/omit-server-errors)
- [UNIX socket file](http-listening/listen-unix/main.go)
- [TLS](http-listening/listen-tls/main.go)
- [Letsencrypt (Automatic Certifications)](http-listening/listen-letsencrypt/main.go)
diff --git a/_examples/hello-world/main_go19.go b/_examples/hello-world/main_go19.go
deleted file mode 100644
index 6a35f5cd..00000000
--- a/_examples/hello-world/main_go19.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// +build go1.9
-
-package main
-
-import (
- "github.com/kataras/iris"
-)
-
-func main() {
- app := iris.Default()
-
- // Method: GET
- // Resource: http://localhost:8080/
- app.Handle("GET", "/", func(ctx iris.Context) {
- ctx.HTML("Hello world!")
- })
-
- // same as app.Handle("GET", "/ping", [...])
- // Method: GET
- // Resource: http://localhost:8080/ping
- app.Get("/ping", func(ctx iris.Context) {
- ctx.WriteString("pong")
- })
-
- // Method: GET
- // Resource: http://localhost:8080/hello
- app.Get("/hello", func(ctx iris.Context) {
- ctx.JSON(iris.Map{"message": "Hello iris web framework."})
- })
-
- // http://localhost:8080
- // http://localhost:8080/ping
- // http://localhost:8080/hello
- app.Run(iris.Addr(":8080"))
-}
diff --git a/_examples/http-listening/listen-addr/main.go b/_examples/http-listening/listen-addr/main.go
index 673057ca..d89c7a3f 100644
--- a/_examples/http-listening/listen-addr/main.go
+++ b/_examples/http-listening/listen-addr/main.go
@@ -12,8 +12,6 @@ func main() {
ctx.HTML("Hello World!/
")
})
- if err := app.Run(iris.Addr(":8080")); err != nil {
- panic(err)
- }
-
+ // http://localhost:8080
+ app.Run(iris.Addr(":8080"))
}
diff --git a/_examples/http-listening/listen-addr/omit-server-errors/main.go b/_examples/http-listening/listen-addr/omit-server-errors/main.go
new file mode 100644
index 00000000..7b7a5c53
--- /dev/null
+++ b/_examples/http-listening/listen-addr/omit-server-errors/main.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+ "github.com/kataras/iris"
+ "github.com/kataras/iris/context"
+)
+
+func main() {
+ app := iris.New()
+
+ app.Get("/", func(ctx context.Context) {
+ ctx.HTML("Hello World!/
")
+ })
+
+ err := app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
+ if err != nil {
+ // do something
+ }
+ // same as:
+ // err := app.Run(iris.Addr(":8080"))
+ // if err != nil && (err != iris.ErrServerClosed || err.Error() != iris.ErrServerClosed.Error()) {
+ // [...]
+ // }
+}
diff --git a/_examples/http-listening/listen-addr/omit-server-errors/main_test.go b/_examples/http-listening/listen-addr/omit-server-errors/main_test.go
new file mode 100644
index 00000000..3413efec
--- /dev/null
+++ b/_examples/http-listening/listen-addr/omit-server-errors/main_test.go
@@ -0,0 +1,84 @@
+package main
+
+import (
+ "bytes"
+ stdContext "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/kataras/iris"
+ "github.com/sirupsen/logrus"
+)
+
+func logger(app *iris.Application) *bytes.Buffer {
+ buf := &bytes.Buffer{}
+ app.Logger().Formatter = &logrus.TextFormatter{
+ DisableColors: true,
+ DisableSorting: true,
+ DisableTimestamp: true,
+ }
+
+ app.Logger().Out = buf
+
+ // disable the "Now running at...." in order to have a clean log of the error.
+ // we could attach that on `Run` but better to keep things simple here.
+ app.Configure(iris.WithoutStartupLog)
+ return buf
+}
+
+func TestListenAddr(t *testing.T) {
+ app := iris.New()
+ // we keep the logger running as well but in a controlled way.
+ log := logger(app)
+
+ // close the server at 3-6 seconds
+ go func() {
+ time.Sleep(3 * time.Second)
+ ctx, cancel := stdContext.WithTimeout(stdContext.TODO(), 3*time.Second)
+ defer cancel()
+ app.Shutdown(ctx)
+ }()
+
+ err := app.Run(iris.Addr(":9829"))
+ // in this case the error should be logged and return as well.
+ if err != iris.ErrServerClosed {
+ t.Fatalf("expecting err to be `iris.ErrServerClosed` but got: %v", err)
+ }
+
+ // println(log.Bytes())
+ // println(len(log.Bytes()))
+ expected := fmt.Sprintln("level=error msg=\"" + iris.ErrServerClosed.Error() + "\" ")
+ // println([]byte(expected))
+ // println(len([]byte(expected)))
+
+ if got := log.String(); expected != got {
+ t.Fatalf("expecting to log the:\n'%s'\ninstead of:\n'%s'", expected, got)
+ }
+}
+
+func TestListenAddrWithoutServerErr(t *testing.T) {
+ app := iris.New()
+ // we keep the logger running as well but in a controlled way.
+ log := logger(app)
+
+ // close the server at 3-6 seconds
+ go func() {
+ time.Sleep(3 * time.Second)
+ ctx, cancel := stdContext.WithTimeout(stdContext.TODO(), 3*time.Second)
+ defer cancel()
+ app.Shutdown(ctx)
+ }()
+
+ // we disable the ErrServerClosed, so the error should be nil when server is closed by `app.Shutdown`.
+
+ // so in this case the iris/http.ErrServerClosed should be NOT logged and NOT return.
+ err := app.Run(iris.Addr(":9827"), iris.WithoutServerError(iris.ErrServerClosed))
+ if err != nil {
+ t.Fatalf("expecting err to be nil but got: %v", err)
+ }
+
+ if got := log.String(); got != "" {
+ t.Fatalf("expecting to log nothing but logged: '%s'", got)
+ }
+}
diff --git a/configuration.go b/configuration.go
index b87990b7..a69d2c0f 100644
--- a/configuration.go
+++ b/configuration.go
@@ -95,6 +95,29 @@ type Configurator func(*Application)
// variables for configurators don't need any receivers, functions
// for them that need (helps code editors to recognise as variables without parenthesis completion).
+// WithoutServerError will cause to ignore the matched "errors"
+// from the main application's `Run` function.
+//
+// Usage:
+// err := app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
+// will return `nil` if the server's error was `http/iris#ErrServerClosed`.
+//
+// See `Configuration#IgnoreServerErrors []string` too.
+func WithoutServerError(errors ...error) Configurator {
+ return func(app *Application) {
+ if len(errors) == 0 {
+ return
+ }
+
+ errorsAsString := make([]string, len(errors), len(errors))
+ for i, e := range errors {
+ errorsAsString[i] = e.Error()
+ }
+
+ app.config.IgnoreServerErrors = append(app.config.IgnoreServerErrors, errorsAsString...)
+ }
+}
+
// WithoutStartupLog turns off the information send, once, to the terminal when the main server is open.
var WithoutStartupLog = func(app *Application) {
app.config.DisableStartupLog = true
@@ -220,6 +243,17 @@ type Configuration struct {
// It can be retrieved by the context if needed (i.e router for subdomains)
vhost string
+ // IgnoreServerErrors will cause to ignore the matched "errors"
+ // from the main application's `Run` function.
+ // This is a slice of string, not a slice of error
+ // users can register these errors using yaml or toml configuration file
+ // like the rest of the configuration fields.
+ //
+ // See `WithoutServerError(...)` function too.
+ //
+ // Defaults to an empty slice.
+ IgnoreServerErrors []string `yaml:"IgnoreServerErrors" toml:"IgnoreServerErrors"`
+
// DisableStartupLog if setted to true then it turns off the write banner on server startup.
//
// Defaults to false.
@@ -455,6 +489,10 @@ func WithConfiguration(c Configuration) Configurator {
return func(app *Application) {
main := app.config
+ if v := c.IgnoreServerErrors; len(v) > 0 {
+ main.IgnoreServerErrors = append(main.IgnoreServerErrors, v...)
+ }
+
if v := c.DisableStartupLog; v {
main.DisableStartupLog = v
}
diff --git a/core/errors/errors.go b/core/errors/errors.go
index d96f277a..56611fde 100644
--- a/core/errors/errors.go
+++ b/core/errors/errors.go
@@ -37,6 +37,16 @@ func New(errMsg string) Error {
}
}
+// NewFromErr same as `New` but pointer for nil checks without the need of the `Return()` function.
+func NewFromErr(err error) *Error {
+ if err == nil {
+ return nil
+ }
+
+ errp := New(err.Error())
+ return &errp
+}
+
// Equal returns true if "e" and "e2" are matched, by their IDs.
// It will always returns true if the "e2" is a children of "e"
// or the error messages are exactly the same, otherwise false.
@@ -123,6 +133,17 @@ func (e Error) With(err error) error {
return e.Format(err.Error())
}
+// Ignore will ignore the "err" and return nil.
+func (e Error) Ignore(err error) error {
+ if err == nil {
+ return e
+ }
+ if e.Error() == err.Error() {
+ return nil
+ }
+ return e
+}
+
// Panic output the message and after panics.
func (e Error) Panic() {
_, fn, line, _ := runtime.Caller(1)
diff --git a/core/errors/reporter.go b/core/errors/reporter.go
index 8da3f59a..859993a2 100644
--- a/core/errors/reporter.go
+++ b/core/errors/reporter.go
@@ -106,12 +106,12 @@ func (r *Reporter) Describe(format string, err error) {
// PrintStack prints all the errors to the given "printer".
// Returns itself in order to be used as printer and return the full error in the same time.
-func (r Reporter) PrintStack(printer func(string, ...interface{})) error {
+func (r *Reporter) PrintStack(printer func(string, ...interface{})) error {
return PrintAndReturnErrors(r, printer)
}
// Stack returns the list of the errors in the stack.
-func (r Reporter) Stack() []Error {
+func (r *Reporter) Stack() []Error {
return r.wrapper.Stack
}
@@ -127,12 +127,12 @@ func (r *Reporter) addStack(stack []Error) {
}
// Error implements the error, returns the full error string.
-func (r Reporter) Error() string {
+func (r *Reporter) Error() string {
return r.wrapper.Error()
}
// Return returns nil if the error is empty, otherwise returns the full error.
-func (r Reporter) Return() error {
+func (r *Reporter) Return() error {
if r.Error() == "" {
return nil
}
diff --git a/core/host/supervisor.go b/core/host/supervisor.go
index 7d98f4ba..cddf2ff7 100644
--- a/core/host/supervisor.go
+++ b/core/host/supervisor.go
@@ -26,9 +26,18 @@ type Supervisor struct {
mu sync.Mutex
- onServe []func(TaskHost)
- onErr []func(error)
- onShutdown []func()
+ onServe []func(TaskHost)
+ // IgnoreErrors should contains the errors that should be ignored
+ // on both serve functions return statements and error handlers.
+ //
+ // i.e: http.ErrServerClosed.Error().
+ //
+ // Note that this will match the string value instead of the equality of the type's variables.
+ //
+ // Defaults to empty.
+ IgnoredErrors []string
+ onErr []func(error)
+ onShutdown []func()
}
// New returns a new host supervisor
@@ -103,17 +112,31 @@ func (su *Supervisor) RegisterOnError(cb func(error)) {
su.mu.Unlock()
}
-func (su *Supervisor) notifyErr(err error) {
- // if err == http.ErrServerClosed {
- // su.notifyShutdown()
- // return
- // }
+func (su *Supervisor) validateErr(err error) error {
+ if err == nil {
+ return nil
+ }
su.mu.Lock()
- for _, f := range su.onErr {
- go f(err)
+ defer su.mu.Unlock()
+
+ for _, e := range su.IgnoredErrors {
+ if err.Error() == e {
+ return nil
+ }
+ }
+ return err
+}
+
+func (su *Supervisor) notifyErr(err error) {
+ err = su.validateErr(err)
+ if err != nil {
+ su.mu.Lock()
+ for _, f := range su.onErr {
+ go f(err)
+ }
+ su.mu.Unlock()
}
- su.mu.Unlock()
}
// RegisterOnServe registers a function to call on
@@ -156,7 +179,7 @@ func (su *Supervisor) supervise(blockFunc func() error) error {
}
}
- return err // start the server
+ return su.validateErr(err)
}
// Serve accepts incoming connections on the Listener l, creating a
@@ -171,7 +194,6 @@ func (su *Supervisor) supervise(blockFunc func() error) error {
// Serve always returns a non-nil error. After Shutdown or Close, the
// returned error is http.ErrServerClosed.
func (su *Supervisor) Serve(l net.Listener) error {
-
return su.supervise(func() error { return su.Server.Serve(l) })
}
diff --git a/iris.go b/iris.go
index 1eb298c3..987bfe93 100644
--- a/iris.go
+++ b/iris.go
@@ -33,7 +33,7 @@ import (
const (
// Version is the current version number of the Iris Web Framework.
- Version = "8.0.0"
+ Version = "8.0.1"
)
// HTTP status codes as registered with IANA.
@@ -361,6 +361,8 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
host.RegisterOnInterrupt(host.ShutdownOnInterrupt(su, shutdownTimeout))
}
+ su.IgnoredErrors = append(su.IgnoredErrors, app.config.IgnoreServerErrors...)
+
app.Hosts = append(app.Hosts, su)
return su
@@ -512,7 +514,7 @@ func (app *Application) Build() error {
// ErrServerClosed is returned by the Server's Serve, ServeTLS, ListenAndServe,
// and ListenAndServeTLS methods after a call to Shutdown or Close.
//
-// Conversion for the http.ErrServerClosed.
+// A shortcut for the `http#ErrServerClosed`.
var ErrServerClosed = http.ErrServerClosed
// Run builds the framework and starts the desired `Runner` with or without configuration edits.
@@ -523,7 +525,9 @@ var ErrServerClosed = http.ErrServerClosed
// then create a new host and run it manually by `go NewHost(*http.Server).Serve/ListenAndServe` etc...
// or use an already created host:
// h := NewHost(*http.Server)
-// Run(Raw(h.ListenAndServe), WithCharset("UTF-8"), WithRemoteAddrHeader("CF-Connecting-IP"))
+// Run(Raw(h.ListenAndServe), WithCharset("UTF-8"),
+// WithRemoteAddrHeader("CF-Connecting-IP"),
+// WithoutServerError(iris.ErrServerClosed))
//
// The Application can go online with any type of server or iris's host with the help of
// the following runners:
@@ -536,7 +540,6 @@ func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error {
}
app.Configure(withOrWithout...)
-
// this will block until an error(unless supervisor's DeferFlow called from a Task).
err := serve(app)
if err != nil {