Skip to content

Commit 0761bc3

Browse files
committed
General Improvements (UseRouter per Party, fix AutoTLS). Read HISTORY.md
relative to: #1577 and #1578
1 parent da029d6 commit 0761bc3

File tree

15 files changed

+640
-121
lines changed

15 files changed

+640
-121
lines changed

.travis.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1+
sudo: false
12
language: go
3+
24
os:
35
- linux
46
- osx
57
go:
68
- 1.14.x
9+
- 1.15.x
10+
- master
711
go_import_path: github.com/kataras/iris/v12
812
env:
913
global:
1014
- GO111MODULE=on
1115
install:
1216
- go get ./...
1317
script:
14-
- go test -count=1 -v -cover ./...
18+
- go test -count=1 -v -cover -race ./...
1519
after_script:
1620
# examples
1721
- cd ./_examples
1822
- go get ./...
19-
- go test -count=1 -v -cover ./...
23+
- go test -count=1 -v -cover -race ./...
2024
- cd ../
21-
# make sure that the _benchmarks code is working
22-
- cd ./_benchmarks
23-
- go get ./...
24-
- go test -count=1 -v -cover ./...

HISTORY.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,41 @@ Response:
359359

360360
Other Improvements:
361361

362-
- `Application.UseRouter(...Handler)` - to register handlers before the main router, useful on handlers that should control whether the router itself should ran or not. Independently of the incoming request's method and path values. These handlers will be executed ALWAYS against ALL incoming requests. Example of use-case: CORS.
362+
- Fix `AutoTLS` when used with `iris.TLSNoRedirect` [*](https://github.com/kataras/iris/issues/1577). The `AutoTLS` runner can be customized through the new `iris.AutoTLSNoRedirect` instead, read its go documentation. Example of having both TLS and non-TLS versions of the same application without conflicts with letsencrypt `./well-known` path:
363+
364+
```go
365+
package main
366+
367+
import (
368+
"net/http"
369+
"time"
370+
371+
"github.com/kataras/iris/v12"
372+
)
373+
374+
func main() {
375+
app := iris.New()
376+
app.Logger().SetLevel("debug")
377+
378+
app.Get("/", func(ctx iris.Context) {
379+
ctx.JSON(iris.Map{
380+
"time": time.Now().Unix(),
381+
"tls": ctx.Request().TLS != nil,
382+
})
383+
})
384+
385+
var fallbackServer = func(acme func(http.Handler) http.Handler) *http.Server {
386+
srv := &http.Server{Handler: acme(app)}
387+
go srv.ListenAndServe()
388+
return srv
389+
}
390+
391+
app.Run(iris.AutoTLS(":443", "example.com", "[email protected]",
392+
iris.AutoTLSNoRedirect(fallbackServer)))
393+
}
394+
```
395+
396+
- `Application.UseRouter(...Handler)` - per party to register handlers before the main router, useful on handlers that should control whether the router itself should ran or not. Independently of the incoming request's method and path values. These handlers will be executed ALWAYS against ALL incoming matched requests. Example of use-case: CORS.
363397

364398
- `*versioning.Group` type is a full `Party` now.
365399

@@ -464,7 +498,7 @@ var dirOpts = iris.DirOptions{
464498
- `Context.RemoveCookie` removes also the Request's specific cookie of the same request lifecycle when `iris.CookieAllowReclaim` is set to cookie options, [example](https://github.com/kataras/iris/tree/master/_examples/cookies/options).
465499

466500
- `iris.TLS` can now accept certificates in form of raw `[]byte` contents too.
467-
- `iris.TLS` registers a secondary http server which redirects "http://" to their "https://" equivalent requests, unless the new `iris.TLSNoRedirect` host Configurator is provided on `iris.TLS` (or `iris.AutoTLS`), e.g. `app.Run(iris.TLS("127.0.0.1:443", "mycert.cert", "mykey.key", iris.TLSNoRedirect))`.
501+
- `iris.TLS` registers a secondary http server which redirects "http://" to their "https://" equivalent requests, unless the new `iris.TLSNoRedirect` host Configurator is provided on `iris.TLS`, e.g. `app.Run(iris.TLS("127.0.0.1:443", "mycert.cert", "mykey.key", iris.TLSNoRedirect))`. There is `iris.AutoTLSNoRedirect` option for `AutoTLS` too.
468502

469503
- Fix an [issue](https://github.com/kataras/i18n/issues/1) about i18n loading from path which contains potential language code.
470504

_examples/http-server/listen-letsencrypt/main.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,18 @@ func main() {
2525
// and a non-public e-mail instead or edit your hosts file.
2626
app.Run(iris.AutoTLS(":443", "example.com", "[email protected]"))
2727

28-
// Note: to disable automatic "http://" to "https://" redirections pass the `iris.TLSNoRedirect`
29-
// host configurator to TLS or AutoTLS functions, e.g:
30-
//
31-
// app.Run(iris.AutoTLS(":443", "example.com", "[email protected]", iris.TLSNoRedirect))
28+
// Note: to disable automatic "http://" to "https://" redirections pass
29+
// the `iris.AutoTLSNoRedirect` host configurator to AutoTLS function, example:
30+
/*
31+
var fallbackServer = func(acme func(http.Handler) http.Handler) *http.Server {
32+
// Use any http.Server and Handler, as long as it's wrapped by `acme` one.
33+
// In that case we share the application through non-tls users too:
34+
srv := &http.Server{Handler: acme(app)}
35+
go srv.ListenAndServe()
36+
return srv
37+
}
38+
39+
app.Run(iris.AutoTLS(":443", "example.com", "[email protected]",
40+
iris.AutoTLSNoRedirect(fallbackServer)))
41+
*/
3242
}

_examples/http-server/listen-tls/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func main() {
2121
app.Run(iris.TLS("127.0.0.1:443", "mycert.crt", "mykey.key"))
2222

2323
// Note: to disable automatic "http://" to "https://" redirections pass the `iris.TLSNoRedirect`
24-
// host configurator to TLS or AutoTLS functions, e.g:
24+
// host configurator to TLS function, example:
2525
//
2626
// app.Run(iris.TLS("127.0.0.1:443", "mycert.crt", "mykey.key", iris.TLSNoRedirect))
2727
}

context/context.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -740,7 +740,9 @@ func (ctx *Context) RequestPath(escape bool) string {
740740
// return false
741741
// } no, it will not work because map is a random peek data structure.
742742

743-
// Host returns the host part of the current URI.
743+
// Host returns the host:port part of the request URI, calls the `Request().Host`.
744+
// To get the subdomain part as well use the `Request().URL.Host` method instead.
745+
// To get the subdomain only use the `Subdomain` method instead.
744746
// This method makes use of the `Configuration.HostProxyHeaders` field too.
745747
func (ctx *Context) Host() string {
746748
for header, ok := range ctx.app.ConfigurationReadOnly().GetHostProxyHeaders() {
@@ -762,13 +764,14 @@ func GetHost(r *http.Request) string {
762764
return host
763765
}
764766

767+
// contains subdomain.
765768
return r.URL.Host
766769
}
767770

768771
// Subdomain returns the subdomain of this request, if any.
769772
// Note that this is a fast method which does not cover all cases.
770773
func (ctx *Context) Subdomain() (subdomain string) {
771-
host := ctx.Host()
774+
host := ctx.request.URL.Host // ctx.Host()
772775
if index := strings.IndexByte(host, '.'); index > 0 {
773776
subdomain = host[0:index]
774777
}

context/handler.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,31 @@ func HandlerName(h interface{}) string {
116116
return trimHandlerName(name)
117117
}
118118

119+
// HandlersNames returns a slice of "handlers" names
120+
// separated by commas. Can be used for debugging
121+
// or to determinate if end-developer
122+
// called the same exactly Use/UseRouter/Done... API methods
123+
// so framework can give a warning.
124+
func HandlersNames(handlers ...interface{}) string {
125+
if len(handlers) == 1 {
126+
if hs, ok := handlers[0].(Handlers); ok {
127+
asInterfaces := make([]interface{}, 0, len(hs))
128+
for _, h := range hs {
129+
asInterfaces = append(asInterfaces, h)
130+
}
131+
132+
return HandlersNames(asInterfaces...)
133+
}
134+
}
135+
136+
names := make([]string, 0, len(handlers))
137+
for _, h := range handlers {
138+
names = append(names, HandlerName(h))
139+
}
140+
141+
return strings.Join(names, ",")
142+
}
143+
119144
// HandlerFileLine returns the handler's file and line information.
120145
// See `context.HandlerFileLine` to get the file, line of the current running handler in the chain.
121146
func HandlerFileLine(h interface{}) (file string, line int) {
@@ -304,3 +329,23 @@ func JoinHandlers(h1 Handlers, h2 Handlers) Handlers {
304329
copy(newHandlers[nowLen:], h2)
305330
return newHandlers
306331
}
332+
333+
// UpsertHandlers like `JoinHandlers` but it does
334+
// NOT copies the handlers entries and it does remove duplicates.
335+
func UpsertHandlers(h1 Handlers, h2 Handlers) Handlers {
336+
reg:
337+
for _, handler := range h2 {
338+
name := HandlerName(handler)
339+
for i, registeredHandler := range h1 {
340+
registeredName := HandlerName(registeredHandler)
341+
if name == registeredName {
342+
h1[i] = handler // replace this handler with the new one.
343+
continue reg // break and continue to the next handler.
344+
}
345+
}
346+
347+
h1 = append(h1, handler) // or just insert it.
348+
}
349+
350+
return h1
351+
}

core/host/supervisor.go

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/tls"
66
"errors"
7+
"fmt"
78
"net"
89
"net/http"
910
"net/url"
@@ -30,7 +31,7 @@ type Configurator func(su *Supervisor)
3031
// Interfaces are separated to return relative functionality to them.
3132
type Supervisor struct {
3233
Server *http.Server
33-
disableHTTP1ToHTTP2Redirection bool // if true then no secondary server on `ListenAndServeTLS/AutoTLS` will be registered, exposed through `NoRedirect`.
34+
disableHTTP1ToHTTP2Redirection bool
3435
closedManually uint32 // future use, accessed atomically (non-zero means we've called the Shutdown)
3536
closedByInterruptHandler uint32 // non-zero means that the end-developer interrupted it by-purpose.
3637
manuallyTLS bool // we need that in order to determinate what to output on the console before the server begin.
@@ -49,6 +50,21 @@ type Supervisor struct {
4950
IgnoredErrors []string
5051
onErr []func(error)
5152

53+
// Fallback should return a http.Server, which may already running
54+
// to handle the HTTP/1.1 clients when TLS/AutoTLS.
55+
// On manual TLS the accepted "challengeHandler" just returns the passed handler,
56+
// otherwise it binds to the acme challenge wrapper.
57+
// Example:
58+
// Fallback = func(h func(fallback http.Handler) http.Handler) *http.Server {
59+
// s := &http.Server{
60+
// Handler: h(myServerHandler),
61+
// ...otherOptions
62+
// }
63+
// go s.ListenAndServe()
64+
// return s
65+
// }
66+
Fallback func(challegeHandler func(fallback http.Handler) http.Handler) *http.Server
67+
5268
// See `iris.Configuration.SocketSharding`.
5369
SocketSharding bool
5470
}
@@ -83,7 +99,7 @@ func (su *Supervisor) Configure(configurators ...Configurator) *Supervisor {
8399
return su
84100
}
85101

86-
// NoRedirect should be called before `ListenAndServeTLS/AutoTLS` when
102+
// NoRedirect should be called before `ListenAndServeTLS` when
87103
// secondary http1 to http2 server is not required. This method will disable
88104
// the automatic registration of secondary http.Server
89105
// which would redirect "http://" requests to their "https://" equivalent.
@@ -295,11 +311,8 @@ func (su *Supervisor) ListenAndServeTLS(certFileOrContents string, keyFileOrCont
295311
}
296312
}
297313

298-
target, _ := url.Parse("https://" + netutil.ResolveVHost(su.Server.Addr)) // e.g. https://localhost:443
299-
http1Handler := RedirectHandler(target, http.StatusMovedPermanently)
300-
301314
su.manuallyTLS = true
302-
return su.runTLS(getCertificate, http1Handler)
315+
return su.runTLS(getCertificate, nil)
303316
}
304317

305318
// ListenAndServeAutoTLS acts identically to ListenAndServe, except that it
@@ -346,44 +359,77 @@ func (su *Supervisor) ListenAndServeAutoTLS(domain string, email string, cacheDi
346359
Cache: cache,
347360
}
348361

349-
return su.runTLS(autoTLSManager.GetCertificate, autoTLSManager.HTTPHandler(nil /* nil for redirect */))
362+
return su.runTLS(autoTLSManager.GetCertificate, autoTLSManager.HTTPHandler)
350363
}
351364

352-
func (su *Supervisor) runTLS(getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error), http1Handler http.Handler) error {
353-
if !su.disableHTTP1ToHTTP2Redirection && http1Handler != nil {
354-
// Note: no need to use a function like ping(":http") to see
355-
// if there is another server running, if it is
356-
// then this server will errored and not start at all.
357-
http1RedirectServer := &http.Server{
358-
ReadTimeout: 30 * time.Second,
359-
WriteTimeout: 60 * time.Second,
360-
Addr: ":http",
361-
Handler: http1Handler,
365+
func (su *Supervisor) runTLS(getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error), challengeHandler func(fallback http.Handler) http.Handler) error {
366+
if su.manuallyTLS && !su.disableHTTP1ToHTTP2Redirection {
367+
// If manual TLS and auto-redirection is enabled,
368+
// then create an empty challenge handler so the :80 server starts.
369+
challengeHandler = func(h http.Handler) http.Handler { // it is always nil on manual TLS.
370+
target, _ := url.Parse("https://" + netutil.ResolveVHost(su.Server.Addr)) // e.g. https://localhost:443
371+
http1Handler := RedirectHandler(target, http.StatusMovedPermanently)
372+
return http1Handler
373+
}
374+
}
375+
376+
if challengeHandler != nil {
377+
http1Server := &http.Server{
378+
Addr: ":http",
379+
Handler: challengeHandler(nil), // nil for redirection.
380+
ReadTimeout: su.Server.ReadTimeout,
381+
ReadHeaderTimeout: su.Server.ReadHeaderTimeout,
382+
WriteTimeout: su.Server.WriteTimeout,
383+
IdleTimeout: su.Server.IdleTimeout,
384+
MaxHeaderBytes: su.Server.MaxHeaderBytes,
385+
}
386+
387+
if su.Fallback == nil {
388+
if !su.manuallyTLS && su.disableHTTP1ToHTTP2Redirection {
389+
// automatic redirection was disabled but Fallback was not registered.
390+
return fmt.Errorf("autotls: use iris.AutoTLSNoRedirect instead")
391+
}
392+
go http1Server.ListenAndServe()
393+
} else {
394+
// if it's manual TLS still can have its own Fallback server here,
395+
// the handler will be the redirect one, the difference is that it can run on any port.
396+
srv := su.Fallback(challengeHandler)
397+
if srv == nil {
398+
if !su.manuallyTLS {
399+
return fmt.Errorf("autotls: relies on an HTTP/1.1 server")
400+
}
401+
// for any case the end-developer decided to return nil here,
402+
// we proceed with the automatic redirection.
403+
srv = http1Server
404+
go srv.ListenAndServe()
405+
} else {
406+
if srv.Addr == "" {
407+
srv.Addr = ":http"
408+
} else if !su.manuallyTLS && srv.Addr != ":80" && srv.Addr != ":http" {
409+
return fmt.Errorf("autotls: The HTTP-01 challenge relies on http://%s:80/.well-known/acme-challenge/", netutil.ResolveVHost(su.Server.Addr))
410+
}
411+
412+
if srv.Handler == nil {
413+
// handler was nil, caller wanted to change the server's options like read/write timeout.
414+
srv.Handler = http1Server.Handler
415+
go srv.ListenAndServe() // automatically start it, we assume the above ^
416+
}
417+
http1Server = srv // to register the shutdown event.
418+
}
362419
}
363420

364-
// register a shutdown callback to this
365-
// supervisor in order to close the "secondary redirect server" as well.
366421
su.RegisterOnShutdown(func() {
367-
// give it some time to close itself...
368422
timeout := 10 * time.Second
369423
ctx, cancel := context.WithTimeout(context.Background(), timeout)
370424
defer cancel()
371-
http1RedirectServer.Shutdown(ctx)
425+
http1Server.Shutdown(ctx)
372426
})
373-
374-
ln, err := netutil.TCP(":http", su.SocketSharding)
375-
if err != nil {
376-
return err
377-
}
378-
379-
go http1RedirectServer.Serve(ln)
380427
}
381428

382429
if su.Server.TLSConfig == nil {
383430
// If tls.Config is NOT configured manually through a host configurator,
384431
// then create it.
385432
su.Server.TLSConfig = &tls.Config{
386-
387433
MinVersion: tls.VersionTLS12,
388434
GetCertificate: getCertificate,
389435
PreferServerCipherSuites: true,

0 commit comments

Comments
 (0)