omit errors received by the server via configuration 🍪 | requested by https://github.com/kataras/iris/issues/668

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


Former-commit-id: 6491abd68b74e18bf4ed0b32406e67597c9b55a9
This commit is contained in:
hiveminded 2017-07-13 16:31:36 +03:00
parent ace439203d
commit 16ccb2edc4
12 changed files with 294 additions and 64 deletions

View File

@ -15,9 +15,83 @@
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready. 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. > 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`. **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).
# 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 # Mo, 10 July 2017 | v8.0.0

View File

@ -43,7 +43,7 @@ These types of projects need heart and sacrifices to continue offer the best dev
</a> </a>
* [Installation](#-installation) * [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) * [Learn](#-learn)
* [HTTP Listening](_examples/#http-listening) * [HTTP Listening](_examples/#http-listening)
* [Configuration](_examples/#configuration) * [Configuration](_examples/#configuration)
@ -215,7 +215,7 @@ _iris_ does not force you to use any specific ORM. With support for the most pop
### 📌 Version ### 📌 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". 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".

View File

@ -16,6 +16,7 @@ It doesn't always contain the "best ways" but it does cover each important featu
### HTTP Listening ### HTTP Listening
- [Common, with address](http-listening/listen-addr/main.go) - [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) - [UNIX socket file](http-listening/listen-unix/main.go)
- [TLS](http-listening/listen-tls/main.go) - [TLS](http-listening/listen-tls/main.go)
- [Letsencrypt (Automatic Certifications)](http-listening/listen-letsencrypt/main.go) - [Letsencrypt (Automatic Certifications)](http-listening/listen-letsencrypt/main.go)

View File

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

View File

@ -12,8 +12,6 @@ func main() {
ctx.HTML("<h1>Hello World!/</h1>") ctx.HTML("<h1>Hello World!/</h1>")
}) })
if err := app.Run(iris.Addr(":8080")); err != nil { // http://localhost:8080
panic(err) app.Run(iris.Addr(":8080"))
}
} }

View File

@ -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()) {
// [...]
// }
}

View File

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

View File

@ -95,6 +95,29 @@ type Configurator func(*Application)
// variables for configurators don't need any receivers, functions // variables for configurators don't need any receivers, functions
// for them that need (helps code editors to recognise as variables without parenthesis completion). // 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. // WithoutStartupLog turns off the information send, once, to the terminal when the main server is open.
var WithoutStartupLog = func(app *Application) { var WithoutStartupLog = func(app *Application) {
app.config.DisableStartupLog = true 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) // It can be retrieved by the context if needed (i.e router for subdomains)
vhost string 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. // DisableStartupLog if setted to true then it turns off the write banner on server startup.
// //
// Defaults to false. // Defaults to false.
@ -455,6 +489,10 @@ func WithConfiguration(c Configuration) Configurator {
return func(app *Application) { return func(app *Application) {
main := app.config main := app.config
if v := c.IgnoreServerErrors; len(v) > 0 {
main.IgnoreServerErrors = append(main.IgnoreServerErrors, v...)
}
if v := c.DisableStartupLog; v { if v := c.DisableStartupLog; v {
main.DisableStartupLog = v main.DisableStartupLog = v
} }

View File

@ -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. // 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" // It will always returns true if the "e2" is a children of "e"
// or the error messages are exactly the same, otherwise false. // 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()) 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. // Panic output the message and after panics.
func (e Error) Panic() { func (e Error) Panic() {
_, fn, line, _ := runtime.Caller(1) _, fn, line, _ := runtime.Caller(1)

View File

@ -106,12 +106,12 @@ func (r *Reporter) Describe(format string, err error) {
// PrintStack prints all the errors to the given "printer". // 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. // 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) return PrintAndReturnErrors(r, printer)
} }
// Stack returns the list of the errors in the stack. // Stack returns the list of the errors in the stack.
func (r Reporter) Stack() []Error { func (r *Reporter) Stack() []Error {
return r.wrapper.Stack return r.wrapper.Stack
} }
@ -127,12 +127,12 @@ func (r *Reporter) addStack(stack []Error) {
} }
// Error implements the error, returns the full error string. // Error implements the error, returns the full error string.
func (r Reporter) Error() string { func (r *Reporter) Error() string {
return r.wrapper.Error() return r.wrapper.Error()
} }
// Return returns nil if the error is empty, otherwise returns the full 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() == "" { if r.Error() == "" {
return nil return nil
} }

View File

@ -26,9 +26,18 @@ type Supervisor struct {
mu sync.Mutex mu sync.Mutex
onServe []func(TaskHost) onServe []func(TaskHost)
onErr []func(error) // IgnoreErrors should contains the errors that should be ignored
onShutdown []func() // 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 // New returns a new host supervisor
@ -103,17 +112,31 @@ func (su *Supervisor) RegisterOnError(cb func(error)) {
su.mu.Unlock() su.mu.Unlock()
} }
func (su *Supervisor) notifyErr(err error) { func (su *Supervisor) validateErr(err error) error {
// if err == http.ErrServerClosed { if err == nil {
// su.notifyShutdown() return nil
// return }
// }
su.mu.Lock() su.mu.Lock()
for _, f := range su.onErr { defer su.mu.Unlock()
go f(err)
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 // 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 // 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 // Serve always returns a non-nil error. After Shutdown or Close, the
// returned error is http.ErrServerClosed. // returned error is http.ErrServerClosed.
func (su *Supervisor) Serve(l net.Listener) error { func (su *Supervisor) Serve(l net.Listener) error {
return su.supervise(func() error { return su.Server.Serve(l) }) return su.supervise(func() error { return su.Server.Serve(l) })
} }

11
iris.go
View File

@ -33,7 +33,7 @@ import (
const ( const (
// Version is the current version number of the Iris Web Framework. // 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. // 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)) host.RegisterOnInterrupt(host.ShutdownOnInterrupt(su, shutdownTimeout))
} }
su.IgnoredErrors = append(su.IgnoredErrors, app.config.IgnoreServerErrors...)
app.Hosts = append(app.Hosts, su) app.Hosts = append(app.Hosts, su)
return su return su
@ -512,7 +514,7 @@ func (app *Application) Build() error {
// ErrServerClosed is returned by the Server's Serve, ServeTLS, ListenAndServe, // ErrServerClosed is returned by the Server's Serve, ServeTLS, ListenAndServe,
// and ListenAndServeTLS methods after a call to Shutdown or Close. // 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 var ErrServerClosed = http.ErrServerClosed
// Run builds the framework and starts the desired `Runner` with or without configuration edits. // 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... // then create a new host and run it manually by `go NewHost(*http.Server).Serve/ListenAndServe` etc...
// or use an already created host: // or use an already created host:
// h := NewHost(*http.Server) // 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 Application can go online with any type of server or iris's host with the help of
// the following runners: // the following runners:
@ -536,7 +540,6 @@ func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error {
} }
app.Configure(withOrWithout...) app.Configure(withOrWithout...)
// this will block until an error(unless supervisor's DeferFlow called from a Task). // this will block until an error(unless supervisor's DeferFlow called from a Task).
err := serve(app) err := serve(app)
if err != nil { if err != nil {