From 16ccb2edc4a11899d86baa6871733d8266d447ae Mon Sep 17 00:00:00 2001
From: hiveminded <get-ion@hotmail.com>
Date: Thu, 13 Jul 2017 16:31:36 +0300
Subject: [PATCH] omit errors received by the server via configuration :cookie:
 | requested by https://github.com/kataras/iris/issues/668

relative link: https://github.com/kataras/iris/issues/668


Former-commit-id: 6491abd68b74e18bf4ed0b32406e67597c9b55a9
---
 HISTORY.md                                    | 78 ++++++++++++++++-
 README.md                                     |  4 +-
 _examples/README.md                           |  1 +
 _examples/hello-world/main_go19.go            | 35 --------
 _examples/http-listening/listen-addr/main.go  |  6 +-
 .../listen-addr/omit-server-errors/main.go    | 24 ++++++
 .../omit-server-errors/main_test.go           | 84 +++++++++++++++++++
 configuration.go                              | 38 +++++++++
 core/errors/errors.go                         | 21 +++++
 core/errors/reporter.go                       |  8 +-
 core/host/supervisor.go                       | 48 ++++++++---
 iris.go                                       | 11 ++-
 12 files changed, 294 insertions(+), 64 deletions(-)
 delete mode 100644 _examples/hello-world/main_go19.go
 create mode 100644 _examples/http-listening/listen-addr/omit-server-errors/main.go
 create mode 100644 _examples/http-listening/listen-addr/omit-server-errors/main_test.go

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("<h1>Hello World!/</h1>")
+    })
+
+    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
 </a>
 
 * [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("<b>Hello world!</b>")
-	})
-
-	// 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("<h1>Hello World!/</h1>")
 	})
 
-	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("<h1>Hello World!/</h1>")
+	})
+
+	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 {