Add the new Go 1.8 Shutdown | Remove DisableBanner, is controlled by LoggerPolicy now.

Former-commit-id: 6ef71a4b9f5a79160a42d1111dd924e244ce1f4e
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-02-17 04:46:33 +02:00
parent 2a4997cadf
commit 21a18d0990
10 changed files with 77 additions and 2104 deletions

2015
HISTORY.md

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@ Iris is an efficient and well-designed toolbox with robust set of features.<br/>
<p>
<h1># Not YAWF<!-- hashtag, no title. --></h1>
<a href="https://github.com/kataras/iris/blob/v6/HISTORY.md"><img src="https://img.shields.io/badge/codename-√Next%20-blue.svg?style=flat-square" alt="CHANGELOG/HISTORY"></a>
<a href="https://github.com/kataras/iris/blob/v6/HISTORY.md"><img src="https://img.shields.io/badge/codename-√Νεxτ%20-blue.svg?style=flat-square" alt="CHANGELOG/HISTORY"></a>
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/examples-%20repository-3362c2.svg?style=flat-square" alt="Examples"></a>
@ -264,7 +264,7 @@ Besides the fact that we have a [community chat][Chat] for questions or reports
Versioning
------------
Current: **v6**, code-named as "√Next"
Current: **v6**, code-named as "√Νεxτ"
v5: https://github.com/kataras/iris/tree/5.0.0

View File

@ -1,6 +1,7 @@
package iris
import (
"context"
"crypto/tls"
"net"
"net/http"
@ -282,13 +283,12 @@ var ProxyHandler = func(redirectSchemeAndHost string) http.HandlerFunc {
// nothing special, use it only when you want to start a secondary server which its only work is to redirect from one requested path to another
//
// returns a close function
func Proxy(proxyAddr string, redirectSchemeAndHost string) func() error {
func Proxy(proxyAddr string, redirectSchemeAndHost string) func(context.Context) error {
proxyAddr = ParseHost(proxyAddr)
// override the handler and redirect all requests to this addr
h := ProxyHandler(redirectSchemeAndHost)
prx := New(OptionDisableBanner(true))
prx.Adapt(DevLogger())
prx := New()
prx.Adapt(RouterBuilderPolicy(func(RouteRepository, ContextPool) http.Handler {
return h
@ -297,5 +297,5 @@ func Proxy(proxyAddr string, redirectSchemeAndHost string) func() error {
go prx.Listen(proxyAddr)
time.Sleep(150 * time.Millisecond)
return func() error { return prx.Close() }
return prx.Shutdown
}

View File

@ -2,6 +2,7 @@
package iris_test
import (
"context"
"io/ioutil"
"math/rand"
"net/http"
@ -152,8 +153,6 @@ func getRandomNumber(min int, max int) int {
// works as
// defer listenTLS(iris.Default, hostTLS)()
func listenTLS(app *iris.Framework, hostTLS string) func() {
app.Close() // close any prev listener
app.Config.DisableBanner = true
// create the key and cert files on the fly, and delete them when this test finished
certFile, ferr := ioutil.TempFile("", "cert")
@ -173,6 +172,8 @@ func listenTLS(app *iris.Framework, hostTLS string) func() {
time.Sleep(200 * time.Millisecond)
return func() {
app.Shutdown(context.Background())
certFile.Close()
time.Sleep(50 * time.Millisecond)
os.Remove(certFile.Name())

View File

@ -158,11 +158,6 @@ type Configuration struct {
// Defaults to false.
FireMethodNotAllowed bool `yaml:"FireMethodNotAllowed"`
// DisableBanner outputs the iris banner at startup
//
// Defaults to false.
DisableBanner bool `yaml:"DisableBanner"`
// DisableBodyConsumptionOnUnmarshal manages the reading behavior of the context's body readers/binders.
// If setted to true then it
// disables the body consumption by the `context.UnmarshalBody/ReadJSON/ReadXML`.
@ -315,15 +310,6 @@ var (
}
}
// OptionDisableBanner outputs the iris banner at startup.
//
// Defaults to false.
OptionDisableBanner = func(val bool) OptionSet {
return func(c *Configuration) {
c.DisableBanner = val
}
}
// OptionDisableBodyConsumptionOnUnmarshal manages the reading behavior of the context's body readers/binders.
// If setted to true then it
// disables the body consumption by the `context.UnmarshalBody/ReadJSON/ReadXML`.
@ -416,7 +402,6 @@ func DefaultConfiguration() Configuration {
DisablePathCorrection: DefaultDisablePathCorrection,
EnablePathEscape: DefaultEnablePathEscape,
FireMethodNotAllowed: false,
DisableBanner: false,
DisableBodyConsumptionOnUnmarshal: false,
TimeFormat: DefaultTimeFormat,
Charset: DefaultCharset,

View File

@ -29,12 +29,12 @@ func TestConfigurationStatic(t *testing.T) {
t.Fatalf("Configuration should be not equal, got: %#v", afterNew)
}
app = New(Configuration{DisableBanner: true})
app = New(Configuration{DisableBodyConsumptionOnUnmarshal: true})
afterNew = *app.Config
if app.Config.DisableBanner == false {
t.Fatalf("Passing a Configuration field as Option fails, expected DisableBanner to be true but was false")
if app.Config.DisableBodyConsumptionOnUnmarshal == false {
t.Fatalf("Passing a Configuration field as Option fails, expected DisableBodyConsumptionOnUnmarshal to be true but was false")
}
app = New() // empty , means defaults so
@ -47,21 +47,21 @@ func TestConfigurationOptions(t *testing.T) {
charset := "MYCHARSET"
disableBanner := true
app := New(OptionCharset(charset), OptionDisableBanner(disableBanner))
app := New(OptionCharset(charset), OptionDisableBodyConsumptionOnUnmarshal(disableBanner))
if got := app.Config.Charset; got != charset {
t.Fatalf("Expected configuration Charset to be: %s but got: %s", charset, got)
}
if got := app.Config.DisableBanner; got != disableBanner {
t.Fatalf("Expected configuration DisableBanner to be: %#v but got: %#v", disableBanner, got)
if got := app.Config.DisableBodyConsumptionOnUnmarshal; got != disableBanner {
t.Fatalf("Expected configuration DisableBodyConsumptionOnUnmarshal to be: %#v but got: %#v", disableBanner, got)
}
// now check if other default values are setted (should be setted automatically)
expected := DefaultConfiguration()
expected.Charset = charset
expected.DisableBanner = disableBanner
expected.DisableBodyConsumptionOnUnmarshal = disableBanner
has := *app.Config
if !reflect.DeepEqual(has, expected) {
@ -74,11 +74,11 @@ func TestConfigurationOptionsDeep(t *testing.T) {
disableBanner := true
vhost := "mydomain.com"
// first charset,disableBanner and profilepath, no canonical order.
app := New(OptionCharset(charset), OptionDisableBanner(disableBanner), OptionVHost(vhost))
app := New(OptionCharset(charset), OptionDisableBodyConsumptionOnUnmarshal(disableBanner), OptionVHost(vhost))
expected := DefaultConfiguration()
expected.Charset = charset
expected.DisableBanner = disableBanner
expected.DisableBodyConsumptionOnUnmarshal = disableBanner
expected.VHost = vhost
has := *app.Config
@ -112,7 +112,7 @@ func TestConfigurationYAML(t *testing.T) {
DisablePathCorrection: false
EnablePathEscape: false
FireMethodNotAllowed: true
DisableBanner: true
DisableBodyConsumptionOnUnmarshal: true
DisableBodyConsumptionOnUnmarshal: true
TimeFormat: Mon, 01 Jan 2006 15:04:05 GMT
Charset: UTF-8
@ -161,8 +161,8 @@ func TestConfigurationYAML(t *testing.T) {
t.Fatalf("error on TestConfigurationYAML: Expected FireMethodNotAllowed %v but got %v", expected, c.FireMethodNotAllowed)
}
if expected := true; c.DisableBanner != expected {
t.Fatalf("error on TestConfigurationYAML: Expected DisableBanner %v but got %v", expected, c.DisableBanner)
if expected := true; c.DisableBodyConsumptionOnUnmarshal != expected {
t.Fatalf("error on TestConfigurationYAML: Expected DisableBodyConsumptionOnUnmarshal %v but got %v", expected, c.DisableBodyConsumptionOnUnmarshal)
}
if expected := true; c.DisableBodyConsumptionOnUnmarshal != expected {

View File

@ -78,8 +78,6 @@ func New(app *iris.Framework, t *testing.T, setters ...OptionSetter) *httpexpect
setter.Set(conf)
}
app.Set(iris.OptionDisableBanner(true))
app.Adapt(iris.DevLogger())
baseURL := ""
app.Boot()

100
iris.go
View File

@ -9,6 +9,7 @@
package iris
import (
"context"
"crypto/tls"
"fmt"
"io"
@ -33,12 +34,14 @@ const (
// Version is the current version number of the Iris web framework
Version = "6.2.0"
codeName = `Νεxτ`
banner = ` _____ _
|_ _| (_)
| | ____ _ ___
| | | __|| |/ __|
_| |_| | | |\__ \
|_____|_| |_||___/ ` + Version + ` `
|_____|_| |_||___/ ` + codeName
)
// Default is the field which keeps an empty `Framework`
@ -114,12 +117,25 @@ type Framework struct {
// ConnState type and associated constants for details.
ConnState func(net.Conn, http.ConnState) // same as http.Server.ConnState
closedManually bool // true if closed via .Close, used to not throw an error when closing the app's server
// Shutdown gracefully shuts down the server without interrupting any
// active connections. Shutdown works by first closing all open
// listeners, then closing all idle connections, and then waiting
// indefinitely for connections to return to idle and then shut down.
// If the provided context expires before the shutdown is complete,
// then the context's error is returned.
//
// Shutdown does not attempt to close nor wait for hijacked
// connections such as WebSockets. The caller of Shutdown should
// separately notify such long-lived connections of shutdown and wait
// for them to close, if desired.
Shutdown func(context.Context) error
closedManually bool // true if closed via .Shutdown, used to not throw a panic on s.handlePanic when closing the app's server
once sync.Once // used to 'Boot' once
}
var defaultGlobalLoggerOuput = log.New(os.Stdout, "[iris] ", log.LstdFlags)
var defaultGlobalLoggerOuput = log.New(os.Stdout, "[Iris] ", log.LstdFlags)
// DevLogger returns a new Logger which prints both ProdMode and DevMode messages
// to the default global logger printer.
@ -377,6 +393,17 @@ func (s *Framework) Must(err error) {
}
func (s *Framework) handlePanic(err error) {
// if x, ok := err.(*net.OpError); ok && x.Op == "accept" {
// return
// }
if err.Error() == http.ErrServerClosed.Error() && s.closedManually {
//.Shutdown was called, log to dev not in prod (prod is only for critical errors.)
// also do not try to recover from this error, remember, Shutdown was called manually here.
s.Log(DevMode, "HTTP Server closed manually")
return
}
if recoveryHandler := s.policies.EventPolicy.Recover; recoveryHandler != nil {
recoveryHandler(s, err)
return
@ -413,8 +440,8 @@ func (s *Framework) Boot() (firstTime bool) {
//
// Serve blocks until the given listener returns permanent error.
func (s *Framework) Serve(ln net.Listener) error {
if s.isRunning() {
return errors.New("Server is already started and listening")
if s.ln != nil {
return errors.New("server is already started and listening")
}
// maybe a 'race' here but user should not call .Serve more than one time especially in more than one go routines...
s.ln = ln
@ -423,18 +450,6 @@ func (s *Framework) Serve(ln net.Listener) error {
// post any panics to the user defined logger.
defer func() {
if rerr := recover(); rerr != nil {
if x, ok := rerr.(*net.OpError); ok && x.Op == "accept" && s.closedManually {
///TODO:
// here we don't report it back because the user called .Close manually.
// NOTES:
//
// I know that the best option to actual Close a server is
// by using a custom net.Listener and do it via channels on its Accept.
// BUT I am not doing this right now because as I'm learning the new go v1.8 will have a shutdown
// options by-default and we will use that instead.
// println("iris.go:355:recover but closed manually so we don't run the handler")
return
}
if err, ok := rerr.(error); ok {
s.handlePanic(err)
}
@ -451,6 +466,15 @@ func (s *Framework) Serve(ln net.Listener) error {
ErrorLog: s.policies.LoggerPolicy.ToLogger(log.LstdFlags),
Handler: s.Router,
}
// Set the grace shutdown, it's just a func no need to make things complicated
// all are managed by net/http now.
s.Shutdown = func(ctx context.Context) error {
// order matters, look s.handlePanic
s.closedManually = true
err := srv.Shutdown(ctx)
s.ln = nil
return err
}
// print the banner and wait for system channel interrupt
go s.postServe()
@ -460,14 +484,8 @@ func (s *Framework) Serve(ln net.Listener) error {
}
func (s *Framework) postServe() {
if !s.Config.DisableBanner {
bannerMessage := fmt.Sprintf("%s: Running at %s", time.Now().Format(s.Config.TimeFormat), s.Config.VHost)
// we don't print it via Logger because:
// 1. The banner is only 'useful' when the developer logs to terminal and no file
// 2. Prefix & LstdFlags options of the default s.Logger
fmt.Printf("%s\n\n%s\n", banner, bannerMessage)
}
bannerMessage := fmt.Sprintf("| Running at %s\n\n%s\n", s.Config.VHost, banner)
s.Log(DevMode, bannerMessage)
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
@ -476,8 +494,8 @@ func (s *Framework) postServe() {
// fire any custom interrupted events and at the end close and exit
// if the custom event blocks then it decides what to do next.
s.policies.Fire(s.policies.Interrupted, s)
// .Close doesn't really closes but it releases the ip:port, wait for go1.8 and see comments on IsRunning
s.Close()
s.Shutdown(context.Background())
os.Exit(1)
}
@ -576,34 +594,6 @@ func (s *Framework) ListenUNIX(addr string, mode os.FileMode) {
s.Must(s.Serve(ln))
}
// IsRunning returns true if server is running
func (s *Framework) isRunning() bool {
///TODO: this will change on gov1.8,
// Reseve or Restart and Close will be re-added again when 1.8 final release.
return s != nil && s.ln != nil && s.ln.Addr() != nil && s.ln.Addr().String() != ""
}
// Close is not working propetly but it releases the host:port.
func (s *Framework) Close() error {
if s.isRunning() {
s.closedManually = true
///TODO:
// This code below doesn't works without custom net listener which will work in a stop channel which will cost us performance.
// This will work on go v1.8 BUT FOR NOW make unexported reserve/reboot/restart in order to be non confusual for the user.
// Close need to be exported because whitebox tests are using this method to release the port.
return s.ln.Close()
}
return nil
}
// restart re-starts the server using the last .Serve's listener
// func (s *Framework) restart() error {
// ///TODO: See .close() notes
// return s.Serve(s.ln)
// }
func (s *Framework) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.Router.ServeHTTP(w, r)
}

View File

@ -143,12 +143,10 @@ func (l LoggerPolicy) Adapt(frame *Policies) {
//
// Implementations must not retain p.
//
// Note: this Write writes as the Production Env, so the default logger should be able to log this messages
// coming from internal http.Server (mostly)
// you can change this behavior too.
// Note: this Write writes as the DevMode.
func (l LoggerPolicy) Write(p []byte) (n int, err error) {
log := string(p)
l(ProdMode, log)
l(DevMode, log)
return len(log), nil
}

View File

@ -132,7 +132,7 @@ func TestResponseRecorderStatusCodeContentTypeBody(t *testing.T) {
}
func ExampleResponseWriter_WriteHeader() {
app := iris.New(iris.OptionDisableBanner(true))
app := iris.New()
app.Adapt(newTestNativeRouter())
expectedOutput := "Hey"