diff --git a/README.md b/README.md index 37ccf7a0..09099e13 100644 --- a/README.md +++ b/README.md @@ -135,11 +135,13 @@ $ go run main.go
Hello World with Go 1.9 -If you've installed Go 1.9 then you can omit the `github.com/kataras/iris/context` package from the imports statement. +Go 1.9 just released. + +Dcumentation and examples will be updated soon to use the already-type aliases inside the framework, such as `iris.Context` instead of the origin pacage. + +If you've installed [Go 1.9](https://golang.org/dl) then you can omit the `github.com/kataras/iris/context` package from the imports statement. ```go -// +build go1.9 - package main import "github.com/kataras/iris" @@ -157,19 +159,6 @@ func main() { } ``` -We expect Go version 1.9 to be released in August, however you can install Go 1.9 RC2 today. - -### Installing Go 1.9rc2 - -1. Go to https://golang.org/dl/#go1.9rc2 -2. Download a compatible, with your OS, archive or executable, i.e `go1.9rc2.windows-amd64.zip` -3. Unzip the contents of `go1.9rc2.windows-amd64.zip` folder to your $GOROOT, i.e `C:\Go` or just execute the executable you've just download -4. Open a terminal and execute `go version`, it should output the go1.9rc2 version, i.e: -```sh -C:\Users\kataras>go version -go version go1.9rc2 windows/amd64 -``` -
diff --git a/core/host/proxy.go b/core/host/proxy.go index abacfa30..11cbdda4 100644 --- a/core/host/proxy.go +++ b/core/host/proxy.go @@ -6,6 +6,7 @@ import ( "net/http/httputil" "net/url" "strings" + "time" "github.com/kataras/iris/core/netutil" ) @@ -59,13 +60,13 @@ func ProxyHandler(target *url.URL) *httputil.ReverseProxy { } // NewProxy returns a new host (server supervisor) which -// redirects all requests to the target. +// proxies all requests to the target. // It uses the httputil.NewSingleHostReverseProxy. // // Usage: // target, _ := url.Parse("https://mydomain.com") // proxy := NewProxy("mydomain.com:80", target) -// proxy.ListenAndServe() // use of proxy.Shutdown to close the proxy server. +// proxy.ListenAndServe() // use of `proxy.Shutdown` to close the proxy server. func NewProxy(hostAddr string, target *url.URL) *Supervisor { proxyHandler := ProxyHandler(target) proxy := New(&http.Server{ @@ -75,3 +76,38 @@ func NewProxy(hostAddr string, target *url.URL) *Supervisor { return proxy } + +// NewRedirection returns a new host (server supervisor) which +// redirects all requests to the target. +// Usage: +// target, _ := url.Parse("https://mydomain.com") +// r := NewRedirection(":80", target, 307) +// r.ListenAndServe() // use of `r.Shutdown` to close this server. +func NewRedirection(hostAddr string, target *url.URL, redirectStatus int) *Supervisor { + targetURI := target.String() + if redirectStatus <= 300 { + // here we should use StatusPermanentRedirect but + // that may result on unexpected behavior + // for end-developers who might change their minds + // after a while, so keep status temporary. + // Note thatwe could also use StatusFound + // as we do on the `Context#Redirect`. + // It will also help us to prevent any post data issues. + redirectStatus = http.StatusTemporaryRedirect + } + + redirectSrv := &http.Server{ + ReadTimeout: 30 * time.Second, + WriteTimeout: 60 * time.Second, + Addr: hostAddr, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + redirectTo := singleJoiningSlash(targetURI, r.URL.Path) + if len(r.URL.RawQuery) > 0 { + redirectTo += "?" + r.URL.RawQuery + } + http.Redirect(w, r, redirectTo, redirectStatus) + }), + } + + return New(redirectSrv) +} diff --git a/core/host/supervisor.go b/core/host/supervisor.go index 688f2f44..bad6599f 100644 --- a/core/host/supervisor.go +++ b/core/host/supervisor.go @@ -5,12 +5,16 @@ import ( "crypto/tls" "net" "net/http" + "net/url" + "strings" "sync" "sync/atomic" + "time" + + "golang.org/x/crypto/acme/autocert" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/netutil" - "golang.org/x/crypto/acme/autocert" ) // Configurator provides an easy way to modify @@ -231,46 +235,119 @@ func (su *Supervisor) ListenAndServe() error { return su.Serve(l) } -func setupHTTP2(cfg *tls.Config) { - cfg.NextProtos = append(cfg.NextProtos, "h2") // HTTP2 -} - // ListenAndServeTLS acts identically to ListenAndServe, except that it // expects HTTPS connections. Additionally, files containing a certificate and // matching private key for the server must be provided. If the certificate // is signed by a certificate authority, the certFile should be the concatenation // of the server's certificate, any intermediates, and the CA's certificate. func (su *Supervisor) ListenAndServeTLS(certFile string, keyFile string) error { - if certFile == "" || keyFile == "" { - return errors.New("certFile or keyFile missing") - } - cfg := new(tls.Config) - var err error - cfg.Certificates = make([]tls.Certificate, 1) - if cfg.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile); err != nil { - return err + su.manuallyTLS = true + + if certFile != "" && keyFile != "" { + cfg := new(tls.Config) + var err error + cfg.Certificates = make([]tls.Certificate, 1) + if cfg.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile); err != nil { + return err + } + + su.Server.TLSConfig = cfg + return su.ListenAndServe() } - setupHTTP2(cfg) - su.Server.TLSConfig = cfg - su.manuallyTLS = true - return su.ListenAndServe() + if su.Server.TLSConfig == nil { + return errors.New("certFile or keyFile missing") + } + + return su.supervise(func() error { return su.Server.ListenAndServeTLS("", "") }) } // ListenAndServeAutoTLS acts identically to ListenAndServe, except that it -// expects HTTPS connections. server's certificates are auto generated from LETSENCRYPT using +// expects HTTPS connections. Server's certificates are auto generated from LETSENCRYPT using // the golang/x/net/autocert package. -func (su *Supervisor) ListenAndServeAutoTLS() error { - autoTLSManager := autocert.Manager{ - Prompt: autocert.AcceptTOS, +// +// The whitelisted domains are separated by whitespace in "domain" argument, i.e "iris-go.com". +// If empty, all hosts are currently allowed. This is not recommended, +// as it opens a potential attack where clients connect to a server +// by IP address and pretend to be asking for an incorrect host name. +// Manager will attempt to obtain a certificate for that host, incorrectly, +// eventually reaching the CA's rate limit for certificate requests +// and making it impossible to obtain actual certificates. +// +// For an "e-mail" use a non-public one, letsencrypt needs that for your own security. +// +// The "cacheDir" is being, optionally, used to provide cache +// stores and retrieves previously-obtained certificates. +// If empty, certs will only be cached for the lifetime of the auto tls manager. +// +// Note: If domain is not empty and the server's port was "443" then +// it will start a new server, automaticall for you, which will redirect all +// http versions to their https as well. +func (su *Supervisor) ListenAndServeAutoTLS(domain string, email string, cacheDir string) error { + var ( + cache autocert.Cache + hostPolicy autocert.HostPolicy + ) + + if cacheDir != "" { + cache = autocert.DirCache(cacheDir) + } + + if domain != "" { + domains := strings.Split(domain, " ") + hostPolicy = autocert.HostWhitelist(domains...) + } + + autoTLSManager := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: hostPolicy, + Email: email, + Cache: cache, + } + + cfg := &tls.Config{ + GetCertificate: autoTLSManager.GetCertificate, + MinVersion: tls.VersionTLS10, + PreferServerCipherSuites: true, + CurvePreferences: []tls.CurveID{ + tls.X25519, + }, } - cfg := new(tls.Config) - cfg.GetCertificate = autoTLSManager.GetCertificate - setupHTTP2(cfg) su.Server.TLSConfig = cfg - su.manuallyTLS = true - return su.ListenAndServe() + + // Redirect all http://$path requests to their + // https://$path versions if a specific domain is passed on + // and the port was 443. + if hostPolicy != nil && netutil.ResolvePort(su.Server.Addr) == 443 { + // find the first domain if more than one. + spaceIdx := strings.IndexByte(domain, ' ') + if spaceIdx != -1 { + domain = domain[0:spaceIdx] + } + // create the url for the secured server. + target, err := url.Parse("https://" + domain) + if err != nil { + return err + } + + // create the redirect server. + redirectSrv := NewRedirection(":80", target, -1) + // register a shutdown callback to this + // 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 + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + redirectSrv.Shutdown(ctx) + }) + // start that redirect server using a different goroutine. + go redirectSrv.ListenAndServe() + } + + return su.ListenAndServeTLS("", "") } // RegisterOnShutdown registers a function to call on Shutdown. diff --git a/iris.go b/iris.go index bfe0e360..d9870401 100644 --- a/iris.go +++ b/iris.go @@ -552,9 +552,24 @@ func TLS(addr string, certFile, keyFile string, hostConfigs ...host.Configurator // certifications created on the fly by the "autocert" golang/x package, // so localhost may not be working, use it at "production" machine. // -// Addr should have the form of [host]:port, i.e mydomain.com:443. +// Addr should have the form of [host]:port, i.e mydomain.com:443 or :443. // -// Second argument is optional, it accepts one or more +// The whitelisted domains are separated by whitespace in "domain" argument, +// i.e "iris-go.com", can be different than "addr". +// If empty, all hosts are currently allowed. This is not recommended, +// as it opens a potential attack where clients connect to a server +// by IP address and pretend to be asking for an incorrect host name. +// Manager will attempt to obtain a certificate for that host, incorrectly, +// eventually reaching the CA's rate limit for certificate requests +// and making it impossible to obtain actual certificates. +// +// For an "e-mail" use a non-public one, letsencrypt needs that for your own security. +// +// Note: If domain is not empty and the server's port was "443" then +// it will start a new server, automaticall for you, which will redirect all +// http versions to their https as well. +// +// Last argument is optional, it accepts one or more // `func(*host.Configurator)` that are being executed // on that specific host that this function will create to start the server. // Via host configurators you can configure the back-end host supervisor, @@ -563,12 +578,18 @@ func TLS(addr string, certFile, keyFile string, hostConfigs ...host.Configurator // https://github.com/kataras/iris/blob/master/_examples/http-listening/notify-on-shutdown/main.go // Look at the `ConfigureHost` too. // -// See `Run` for more. -func AutoTLS(addr string, hostConfigs ...host.Configurator) Runner { +// Usage: +// app.Run(iris.AutoTLS(":443", "example.com", "mail@example.com")) +// +// See `Run` and `core/host/Supervisor#ListenAndServeAutoTLS` for more. +func AutoTLS( + addr string, + domain string, email string, + hostConfigs ...host.Configurator) Runner { return func(app *Application) error { return app.NewHost(&http.Server{Addr: addr}). Configure(hostConfigs...). - ListenAndServeAutoTLS() + ListenAndServeAutoTLS(domain, email, "letscache") } }