diff --git a/caddytest/integration/caddyfile_adapt/global_server_options_single.txt b/caddytest/integration/caddyfile_adapt/global_server_options_single.txt index fb0af2c3..da3e6bc2 100644 --- a/caddytest/integration/caddyfile_adapt/global_server_options_single.txt +++ b/caddytest/integration/caddyfile_adapt/global_server_options_single.txt @@ -1,6 +1,7 @@ { servers { listener_wrappers { + http_redirect tls } timeouts { @@ -32,6 +33,9 @@ foo.com { ":443" ], "listener_wrappers": [ + { + "wrapper": "http_redirect" + }, { "wrapper": "tls" } diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index 67f9d1d6..64cc5401 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -343,11 +343,6 @@ func (app *App) Start() error { // enable TLS if there is a policy and if this is not the HTTP port useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort() if useTLS { - // create HTTP redirect wrapper, which detects if - // the request had HTTP bytes on the HTTPS port, and - // triggers a redirect if so. - ln = &httpRedirectListener{Listener: ln} - // create TLS listener tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx) ln = tls.NewListener(ln, tlsCfg) diff --git a/modules/caddyhttp/httpredirectlistener.go b/modules/caddyhttp/httpredirectlistener.go index 38225a3d..3ff79ff8 100644 --- a/modules/caddyhttp/httpredirectlistener.go +++ b/modules/caddyhttp/httpredirectlistener.go @@ -20,8 +20,45 @@ import ( "net" "net/http" "sync" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" ) +func init() { + caddy.RegisterModule(HTTPRedirectListenerWrapper{}) +} + +// HTTPRedirectListenerWrapper provides HTTP->HTTPS redirects for +// connections that come on the TLS port as an HTTP request, +// by detecting using the first few bytes that it's not a TLS +// handshake, but instead an HTTP request. +// +// This is especially useful when using a non-standard HTTPS port. +// A user may simply type the address in their browser without the +// https:// scheme, which would cause the browser to attempt the +// connection over HTTP, but this would cause a "Client sent an +// HTTP request to an HTTPS server" error response. +// +// This listener wrapper must be placed BEFORE the "tls" listener +// wrapper, for it to work properly. +type HTTPRedirectListenerWrapper struct{} + +func (HTTPRedirectListenerWrapper) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.listeners.http_redirect", + New: func() caddy.Module { return new(HTTPRedirectListenerWrapper) }, + } +} + +func (h *HTTPRedirectListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +func (h *HTTPRedirectListenerWrapper) WrapListener(l net.Listener) net.Listener { + return &httpRedirectListener{l} +} + // httpRedirectListener is listener that checks the first few bytes // of the request when the server is intended to accept HTTPS requests, // to respond to an HTTP request with a redirect. @@ -112,3 +149,8 @@ func firstBytesLookLikeHTTP(hdr []byte) bool { } return false } + +var ( + _ caddy.ListenerWrapper = (*HTTPRedirectListenerWrapper)(nil) + _ caddyfile.Unmarshaler = (*HTTPRedirectListenerWrapper)(nil) +)