1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-17 09:57:01 +00:00

don't fire ErrServerClosed on manually interrupt signals (CTRL/CMD+C)

Former-commit-id: 673c84dd13bb99c0926aa1b4a6b4eff9745403d8
This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-04-28 05:22:58 +03:00
parent 27ca1c93f5
commit b657c5e6af
44 changed files with 101 additions and 110 deletions

View File

@@ -190,6 +190,8 @@ Other Improvements:
![DBUG routes](https://iris-go.com/images/v12.2.0-dbug2.png)
- Server will not return neither log the `ErrServerClosed` if `app.Shutdown` was called manually via interrupt signal(CTRL/CMD+C), note that if the server closed by any other reason the error will be fired as previously (unless `iris.WithoutServerError(iris.ErrServerClosed)`).
- Finally, Log level's and Route debug information colorization is respected across outputs. Previously if the application used more than one output destination (e.g. a file through `app.Logger().AddOutput`) the color support was automatically disabled from all, including the terminal one, this problem is fixed now. Developers can now see colors in their terminals while log files are kept with clear text.
- New `iris.WithLowercaseRouting` option which forces all routes' paths to be lowercase and converts request paths to their lowercase for matching.

View File

@@ -4,5 +4,5 @@ go 1.13
require (
github.com/betacraft/yaag v1.0.1-0.20191027021412-565f65e36090
github.com/kataras/iris/v12 v12.1.5
github.com/kataras/iris/v12 v12.1.8
)

View File

@@ -38,5 +38,5 @@ func main() {
// http://localhost:8080
// http://localhost:8080/ping
// http://localhost:8080/hello
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}

View File

@@ -50,8 +50,6 @@ func main() {
app.Run(
// Start the web server at localhost:8080
iris.Addr("localhost:8080"),
// skip err server closed when CTRL/CMD+C pressed:
iris.WithoutServerError(iris.ErrServerClosed),
// enables faster json serialization and more:
iris.WithOptimizations,
)

View File

@@ -32,8 +32,5 @@ func main() {
// Path: http://localhost:8080
app.Get("/", indexHandler)
app.Run(
iris.Addr(":8080"),
iris.WithoutServerError(iris.ErrServerClosed),
)
app.Listen(":8080")
}

View File

@@ -235,7 +235,7 @@ func main() {
app := iris.New()
iris.RegisterOnInterrupt(func() {
timeout := 5 * time.Second
timeout := 10 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// close all hosts

View File

@@ -33,7 +33,7 @@ func main() {
case <-ch:
println("shutdown...")
timeout := 5 * time.Second
timeout := 10 * time.Second
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
defer cancel()
app.Shutdown(ctx)

View File

@@ -18,7 +18,7 @@ func main() {
app := iris.New()
iris.RegisterOnInterrupt(func() {
timeout := 5 * time.Second
timeout := 10 * time.Second
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
defer cancel()
// close all hosts

View File

@@ -17,7 +17,8 @@ func main() {
}
// same as:
// err := app.Listen(":8080")
// if err != nil && (err != iris.ErrServerClosed || err.Error() != iris.ErrServerClosed.Error()) {
// import "errors"
// if errors.Is(err, iris.ErrServerClosed) {
// [...]
// }
}

View File

@@ -60,9 +60,8 @@ func TestListenAddrWithoutServerErr(t *testing.T) {
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.
// we disable the ErrServerClosed, so the error should be nil when server is closed by `app.Shutdown`
// or by an external issue.
err := app.Listen(":9827", iris.WithoutServerError(iris.ErrServerClosed))
if err != nil {
t.Fatalf("expecting err to be nil but got: %v", err)

View File

@@ -11,14 +11,14 @@ func main() {
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.HTML("<h1>Hello, try to refresh the page after ~10 secs</h1>")
ctx.HTML("<h1>Hello, try to refresh the page after ~5 secs</h1>")
})
app.Logger().Info("Wait 10 seconds and check your terminal again")
app.Logger().Info("Wait 5 seconds and check your terminal again")
// simulate a shutdown action here...
go func() {
<-time.After(10 * time.Second)
timeout := 5 * time.Second
<-time.After(5 * time.Second)
timeout := 10 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// close all hosts, this will notify the callback we had register
@@ -36,21 +36,24 @@ func main() {
// wait 10 seconds and check your terminal.
app.Run(iris.Addr(":8080", configureHost), iris.WithoutServerError(iris.ErrServerClosed))
/*
Or for simple cases you can just use the:
iris.RegisterOnInterrupt for global catch of the CTRL/CMD+C and OS events.
Look at the "graceful-shutdown" example for more.
time.Sleep(500 * time.Millisecond) // give time to the separate go routine(`onServerShutdown`) to finish.
/* See
iris.RegisterOnInterrupt(callback) for global catch of the CTRL/CMD+C and OS events.
Look at the "graceful-shutdown" example for more.
*/
}
func onServerShutdown() {
println("server is closed")
}
func configureHost(su *iris.Supervisor) {
// here we have full access to the host that will be created
// inside the `app.Run` function or `NewHost`.
//
// we're registering a shutdown "event" callback here:
su.RegisterOnShutdown(func() {
println("server is closed")
})
su.RegisterOnShutdown(onServerShutdown)
// su.RegisterOnError
// su.RegisterOnServe
}

View File

@@ -24,5 +24,5 @@ func main() {
// http://localhost:8080?referer=https://twitter.com/Xinterio/status/1023566830974251008
// http://localhost:8080?referer=https://www.google.com/search?q=Top+6+golang+web+frameworks&oq=Top+6+golang+web+frameworks
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}

View File

@@ -19,7 +19,7 @@ func main() {
//
// The response should be:
// Received: main.config{Addr:"localhost:8080", ServerName:"Iris"}
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations)
app.Listen(":8080", iris.WithOptimizations)
}
func newApp() *iris.Application {

View File

@@ -19,7 +19,7 @@ func main() {
//
// The response should be:
// Received: main.config{Addr:"localhost:8080", ServerName:"Iris"}
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations)
app.Listen(":8080", iris.WithOptimizations)
}
func newApp() *iris.Application {

View File

@@ -60,5 +60,5 @@ func main() {
//
// The response should be:
// Received: main.Company{Name:"iris-Go", City:"New York", Other:"Something here"}
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations)
app.Listen(":8080", iris.WithOptimizations)
}

View File

@@ -20,7 +20,7 @@ func main() {
//
// The response should be:
// Received: main.person{XMLName:xml.Name{Space:"", Local:"person"}, Name:"Winston Churchill", Age:90, Description:"Description of this person, the body of this inner element."}
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations)
app.Listen(":8080", iris.WithOptimizations)
}
func newApp() *iris.Application {

View File

@@ -61,5 +61,5 @@ func main() {
// http://localhost:8080/2
// http://lcoalhost:8080/notfoundhere
// see the output on the console.
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}

View File

@@ -82,7 +82,7 @@ func main() {
// http://localhost:8080/1
// http://localhost:8080/2
// http://lcoalhost:8080/notfoundhere
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}
var excludeExtensions = [...]string{

View File

@@ -35,7 +35,7 @@ func main() {
// http://localhost:8080/1
// http://localhost:8080/2
// http://lcoalhost:8080/notfoundhere
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}
// get a filename based on the date, file logs works that way the most times

View File

@@ -18,5 +18,5 @@ func main() {
app := newApp()
// http://localhost:8080
// http://localhost:8080/yourname
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}

View File

@@ -44,7 +44,7 @@ func main() {
})
}() // ...
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}
/* For a golang SSE client you can look at: https://github.com/r3labs/sse#example-client */

View File

@@ -187,5 +187,5 @@ func main() {
// http://localhost:8080
// http://localhost:8080/events
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}

View File

@@ -113,5 +113,5 @@ func main() {
//
// `iris.WithoutServerError` is an optional configurator,
// if passed to the `Run` then it will not print its passed error as an actual server error.
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations)
app.Listen(":8080", iris.WithOptimizations)
}

View File

@@ -42,7 +42,7 @@ func main() {
// Navigate to http://localhost:8080/ping
// and open the ./logs{TODAY}.txt file.
if err := app.Listen(":8080", iris.WithoutBanner, iris.WithoutServerError(iris.ErrServerClosed)); err != nil {
if err := app.Listen(":8080", iris.WithoutBanner); err != nil {
app.Logger().Warn("Shutdown with error: " + err.Error())
}
}

View File

@@ -35,7 +35,7 @@ func main() {
log.Fatal(err)
}
resp, err := client.Post("https://localhost/hello", "application/json", buf)
resp, err := client.Post("https://localhost/helloworld.Greeter/SayHello", "application/json", buf)
if err != nil {
log.Fatal(err)
}

View File

@@ -23,7 +23,7 @@ func main() {
app.Logger().SetLevel("debug")
// The Iris server should ran under TLS (it's a gRPC requirement).
// POST: https://localhost:443/helloworld.greeter/sayhello
// POST: https://localhost:443/helloworld.Greeter/SayHello
// with request data: {"name": "John"}
// and expected output: {"message": "Hello John"}
app.Run(iris.TLS(":443", "server.crt", "server.key"))
@@ -32,7 +32,6 @@ func main() {
func newApp() *iris.Application {
app := iris.New()
// app.Configure(iris.WithLowercaseRouting) // OPTIONAL.
app.Logger().SetLevel("debug")
app.Get("/", func(ctx iris.Context) {
ctx.HTML("<h1>Index Page</h1>")
@@ -55,7 +54,9 @@ func newApp() *iris.Application {
return app
}
type myController struct{}
type myController struct {
// Ctx iris.Context
}
// SayHello implements helloworld.GreeterServer.
func (c *myController) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {

View File

@@ -76,12 +76,8 @@ func main() {
// http://localhost:8080/user/me
// http://localhost:8080/user/logout
// basic auth: "admin", "password", see "./middleware/basicauth.go" source file.
app.Run(
// Starts the web server at localhost:8080
iris.Addr("localhost:8080"),
// Ignores err server closed log when CTRL/CMD+C pressed.
iris.WithoutServerError(iris.ErrServerClosed),
// Enables faster json serialization and more.
iris.WithOptimizations,
)
// Starts the web server at localhost:8080
// Enables faster json serialization and more.
app.Listen(":8080", iris.WithOptimizations)
}

View File

@@ -30,14 +30,7 @@ func main() {
// http://localhost:8080/hello/iris
// http://localhost:8080/movies
// http://localhost:8080/movies/1
app.Run(
// Start the web server at localhost:8080
iris.Addr("localhost:8080"),
// skip err server closed when CTRL/CMD+C pressed:
iris.WithoutServerError(iris.ErrServerClosed),
// enables faster json serialization and more:
iris.WithOptimizations,
)
app.Listen(":8080", iris.WithOptimizations)
}
// note the mvc.Application, it's not iris.Application.

View File

@@ -165,7 +165,8 @@ func main() {
"data": user.Serializer(),
})
})
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}
type patchParam struct {

View File

@@ -70,5 +70,5 @@ func main() {
// http://localhost:8080/insert
// http://localhost:8080/get
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}

View File

@@ -98,5 +98,5 @@ func main() {
myCustomRouter := new(customRouter)
app.BuildRouter(app.ContextPool, myCustomRouter, app.APIBuilder, true)
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}

View File

@@ -38,7 +38,7 @@ func main() {
// this will handle only GET "/other2/static"
app.Get("/other2/static2", staticPathOther2)
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}
func h(ctx iris.Context) {

View File

@@ -104,5 +104,5 @@ func main() {
}
})
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}

View File

@@ -105,5 +105,5 @@ func main() {
}
})
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}

View File

@@ -77,7 +77,7 @@ func main() {
// POST, GET: http://localhost:8080/api/v1/topics
// POST : http://localhost:8080/apiv1/topics/{topic}/produce?key=my-key
// GET : http://localhost:8080/apiv1/topics/{topic}/consume?partition=0&offset=0 (these url query parameters are optional)
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}
// simple use-case, you can use templates and views obviously, see the "_examples/views" examples.

View File

@@ -116,5 +116,5 @@ func main() {
// serves the npm browser websocket client usage example.
app.HandleDir("/browserify", "./browserify")
app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))
app.Listen(":8080")
}

View File

@@ -3,6 +3,6 @@ module github.com/kataras/iris/_examples/websocket/socketio
go 1.13
require (
github.com/googollee/go-socket.io v1.4.3-0.20191109153049-7451e2f8c2e0 // indirect
github.com/kataras/iris/v12 v12.1.5
github.com/googollee/go-socket.io v1.4.3-0.20191109153049-7451e2f8c2e0
github.com/kataras/iris/v12 v12.1.8
)

View File

@@ -47,10 +47,8 @@ func main() {
app.HandleMany("GET POST", "/socket.io/{any:path}", iris.FromStd(server))
app.HandleDir("/", "./asset")
app.Listen(":8000",
iris.WithoutPathCorrection,
iris.WithoutServerError(iris.ErrServerClosed),
)
app.Listen(":8000", iris.WithoutPathCorrection)
}
/*

View File

@@ -185,11 +185,8 @@ var WithGlobalConfiguration = func(app *Application) {
app.Configure(WithConfiguration(YAML(globalConfigurationKeyword)))
}
// 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.
// from the main application's `Run/Listen` function.
//
// Usage:
// err := app.Listen(":8080", iris.WithoutServerError(iris.ErrServerClosed))

View File

@@ -1,6 +1,7 @@
package context
import (
stdContext "context"
"io"
"net/http"
@@ -41,6 +42,10 @@ type Application interface {
// It is ready to use after Build state.
ServeHTTP(w http.ResponseWriter, r *http.Request)
// Shutdown gracefully terminates all the application's server hosts and any tunnels.
// Returns an error on the first failure, otherwise nil.
Shutdown(ctx stdContext.Context) error
// GetRouteReadOnly returns the registered "read-only" route based on its name, otherwise nil.
// One note: "routeName" should be case-sensitive. Used by the context to get the current route.
// It returns an interface instead to reduce wrong usage and to keep the decoupled design between

View File

@@ -27,11 +27,12 @@ type Configurator func(su *Supervisor)
//
// Interfaces are separated to return relative functionality to them.
type Supervisor struct {
Server *http.Server
closedManually int32 // future use, accessed atomically (non-zero means we've called the Shutdown)
manuallyTLS bool // we need that in order to determinate what to output on the console before the server begin.
shouldWait int32 // non-zero means that the host should wait for unblocking
unblockChan chan struct{}
Server *http.Server
closedManually uint32 // future use, accessed atomically (non-zero means we've called the Shutdown)
closedByInterruptHandler uint32 // non-zero means that the end-developer interrupted it by-purpose.
manuallyTLS bool // we need that in order to determinate what to output on the console before the server begin.
shouldWait int32 // non-zero means that the host should wait for unblocking
unblockChan chan struct{}
mu sync.Mutex
@@ -39,14 +40,11 @@ type Supervisor struct {
// 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
@@ -143,6 +141,10 @@ func (su *Supervisor) validateErr(err error) error {
return nil
}
if errors.Is(err, http.ErrServerClosed) && atomic.LoadUint32(&su.closedByInterruptHandler) > 0 {
return nil
}
su.mu.Lock()
defer su.mu.Unlock()
@@ -151,6 +153,7 @@ func (su *Supervisor) validateErr(err error) error {
return nil
}
}
return err
}
@@ -189,6 +192,8 @@ func (su *Supervisor) supervise(blockFunc func() error) error {
host := createTaskHost(su)
su.notifyServe(host)
atomic.StoreUint32(&su.closedByInterruptHandler, 0)
atomic.StoreUint32(&su.closedManually, 0)
err := blockFunc()
su.notifyErr(err)
@@ -319,7 +324,7 @@ func (su *Supervisor) ListenAndServeAutoTLS(domain string, email string, cacheDi
// supervisor in order to close the "secondary redirect server" as well.
su.RegisterOnShutdown(func() {
// give it some time to close itself...
timeout := 5 * time.Second
timeout := 10 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
srv2.Shutdown(ctx)
@@ -346,21 +351,10 @@ func (su *Supervisor) ListenAndServeAutoTLS(domain string, email string, cacheDi
// undergone NPN/ALPN protocol upgrade or that have been hijacked.
// This function should start protocol-specific graceful shutdown,
// but should not wait for shutdown to complete.
//
// Callbacks will run as separate go routines.
func (su *Supervisor) RegisterOnShutdown(cb func()) {
// when go1.9: replace the following lines with su.Server.RegisterOnShutdown(f)
su.mu.Lock()
su.onShutdown = append(su.onShutdown, cb)
su.mu.Unlock()
}
func (su *Supervisor) notifyShutdown() {
// when go1.9: remove the lines below
su.mu.Lock()
for _, f := range su.onShutdown {
go f()
}
su.mu.Unlock()
// end
su.Server.RegisterOnShutdown(cb)
}
// Shutdown gracefully shuts down the server without interrupting any
@@ -375,7 +369,11 @@ func (su *Supervisor) notifyShutdown() {
// separately notify such long-lived connections of shutdown and wait
// for them to close, if desired.
func (su *Supervisor) Shutdown(ctx context.Context) error {
atomic.AddInt32(&su.closedManually, 1) // future-use
su.notifyShutdown()
atomic.StoreUint32(&su.closedManually, 1) // future-use
return su.Server.Shutdown(ctx)
}
func (su *Supervisor) shutdownOnInterrupt(ctx context.Context) {
atomic.StoreUint32(&su.closedByInterruptHandler, 1)
su.Shutdown(ctx)
}

View File

@@ -87,7 +87,7 @@ func ExampleSupervisor_RegisterOnServe() {
logger := log.New(os.Stdout, "Supervisor: ", 0)
mytask := myTestTask{
restartEvery: 6 * time.Second,
restartEvery: 3 * time.Second,
maxRestarts: 2,
logger: logger,
}

View File

@@ -6,6 +6,7 @@ package host
// supervisor.
import (
"context"
"errors"
"fmt"
"io"
"net/http"
@@ -27,8 +28,9 @@ func WriteStartupLogOnServe(w io.Writer) func(TaskHost) {
if runtime.GOOS == "darwin" {
interruptkey = "CMD"
}
_, _ = w.Write([]byte(fmt.Sprintf("Now listening on: %s\nApplication started. Press %s+C to shut down.\n",
listeningURI, interruptkey)))
_, _ = fmt.Fprintf(w, "Now listening on: %s\nApplication started. Press %s+C to shut down.\n",
listeningURI, interruptkey)
}
}
@@ -38,7 +40,7 @@ func ShutdownOnInterrupt(su *Supervisor, shutdownTimeout time.Duration) func() {
return func() {
ctx, cancel := context.WithTimeout(context.TODO(), shutdownTimeout)
defer cancel()
su.Shutdown(ctx)
su.shutdownOnInterrupt(ctx)
su.RestoreFlow()
}
}
@@ -58,9 +60,9 @@ func (h TaskHost) Serve() error {
return err
}
// if http.serverclosed ignroe the error, it will have this error
// if http.serverclosed ignore the error, it will have this error
// from the previous close
if err := h.Supervisor.Server.Serve(l); err != http.ErrServerClosed {
if err := h.Supervisor.Server.Serve(l); !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil

View File

@@ -668,8 +668,8 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
}
if !app.config.DisableInterruptHandler {
// when CTRL+C/CMD+C pressed.
shutdownTimeout := 5 * time.Second
// when CTRL/CMD+C pressed.
shutdownTimeout := 10 * time.Second
host.RegisterOnInterrupt(host.ShutdownOnInterrupt(su, shutdownTimeout))
// app.logger.Debugf("Host: register server shutdown on interrupt(CTRL+C/CMD+C)")
}