Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
caddyhttp: Add support for listener wrapper modules
Wrapping listeners is useful for composing custom behavior related to accepting, closing, reading/writing connections (etc) below the application layer; for example, the PROXY protocol.
Matthew Holt committed
Mar 15, 2020 at 21:26 UTC
f596fd77bb6880485d2bfc6b18a775b5572da260
3 files changed
+87
listeners.go
+13
@@ -386,6 +386,19 @@ func JoinNetworkAddress(network, host, port string) string {
386
return a
387
}
388
389
+// ListenerWrapper is a type that wraps a listener
390
+// so it can modify the input listener's methods.
391
+// Modules that implement this interface are found
392
+// in the caddy.listeners namespace. Usually, to
393
+// wrap a listener, you will define your own struct
394
+// type that embeds the input listener, then
395
+// implement your own methods that you want to wrap,
396
+// calling the underlying listener's methods where
397
+// appropriate.
398
+type ListenerWrapper interface {
399
+ WrapListener(net.Listener) net.Listener
400
+}
401
+
402
var (
403
listeners = make(map[string]*globalListener)
404
listenersMu sync.Mutex
modules/caddyhttp/caddyhttp.go
+68
@@ -40,6 +40,11 @@ func init() {
40
if err != nil {
41
caddy.Log().Fatal(err.Error())
42
}
43
+
44
+ err = caddy.RegisterModule(tlsPlaceholderWrapper{})
45
+ if err != nil {
46
+ caddy.Log().Fatal(err.Error())
47
+ }
48
}
49
50
// App is a robust, production-ready HTTP server.
@@ -181,6 +186,7 @@ func (app *App) Provision(ctx caddy.Context) error {
186
srv.StrictSNIHost = &trueBool
187
}
188
189
+ // process each listener address
190
for i := range srv.Listen {
191
lnOut, err := repl.ReplaceOrErr(srv.Listen[i], true, true)
192
if err != nil {
@@ -190,6 +196,37 @@ func (app *App) Provision(ctx caddy.Context) error {
196
srv.Listen[i] = lnOut
197
}
198
199
+ // set up each listener modifier
200
+ if srv.ListenerWrappersRaw != nil {
201
+ vals, err := ctx.LoadModule(srv, "ListenerWrappersRaw")
202
+ if err != nil {
203
+ return fmt.Errorf("loading listener wrapper modules: %v", err)
204
+ }
205
+ var hasTLSPlaceholder bool
206
+ for i, val := range vals.([]interface{}) {
207
+ if _, ok := val.(*tlsPlaceholderWrapper); ok {
208
+ if i == 0 {
209
+ // putting the tls placeholder wrapper first is nonsensical because
210
+ // that is the default, implicit setting: without it, all wrappers
211
+ // will go after the TLS listener anyway
212
+ return fmt.Errorf("it is unnecessary to specify the TLS listener wrapper in the first position because that is the default")
213
+ }
214
+ if hasTLSPlaceholder {
215
+ return fmt.Errorf("TLS listener wrapper can only be specified once")
216
+ }
217
+ hasTLSPlaceholder = true
218
+ }
219
+ srv.listenerWrappers = append(srv.listenerWrappers, val.(caddy.ListenerWrapper))
220
+ }
221
+ // if any wrappers were configured but the TLS placeholder wrapper is
222
+ // absent, prepend it so all defined wrappers come after the TLS
223
+ // handshake; this simplifies logic when starting the server, since we
224
+ // can simply assume the TLS placeholder will always be there
225
+ if !hasTLSPlaceholder && len(srv.listenerWrappers) > 0 {
226
+ srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
227
+ }
228
+ }
229
+
230
// pre-compile the primary handler chain, and be sure to wrap it in our
231
// route handler so that important security checks are done, etc.
232
primaryRoute := emptyHandler
@@ -265,12 +302,23 @@ func (app *App) Start() error {
302
return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
303
}
304
for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ {
305
+ // create the listener for this socket
306
hostport := listenAddr.JoinHostPort(portOffset)
307
ln, err := caddy.Listen(listenAddr.Network, hostport)
308
if err != nil {
309
return fmt.Errorf("%s: listening on %s: %v", listenAddr.Network, hostport, err)
310
}
311
312
+ // wrap listener before TLS (up to the TLS placeholder wrapper)
313
+ var lnWrapperIdx int
314
+ for i, lnWrapper := range srv.listenerWrappers {
315
+ if _, ok := lnWrapper.(*tlsPlaceholderWrapper); ok {
316
+ lnWrapperIdx = i + 1 // mark the next wrapper's spot
317
+ break
318
+ }
319
+ ln = lnWrapper.WrapListener(ln)
320
+ }
321
+
322
// enable TLS if there is a policy and if this is not the HTTP port
323
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
324
if useTLS {
@@ -303,6 +351,11 @@ func (app *App) Start() error {
351
/////////
352
}
353
354
+ // finish wrapping listener where we left off before TLS
355
+ for i := lnWrapperIdx; i < len(srv.listenerWrappers); i++ {
356
+ ln = srv.listenerWrappers[i].WrapListener(ln)
357
+ }
358
+
359
app.logger.Debug("starting server loop",
360
zap.String("address", lnAddr),
361
zap.Bool("http3", srv.ExperimentalHTTP3),
@@ -544,6 +597,19 @@ func StatusCodeMatches(actual, configured int) bool {
597
return false
598
}
599
600
+// tlsPlaceholderWrapper is a no-op listener wrapper that marks
601
+// where the TLS listener should be in a chain of listener wrappers.
602
+type tlsPlaceholderWrapper struct{}
603
+
604
+func (tlsPlaceholderWrapper) CaddyModule() caddy.ModuleInfo {
605
+ return caddy.ModuleInfo{
606
+ ID: "caddy.listeners.tls",
607
+ New: func() caddy.Module { return new(tlsPlaceholderWrapper) },
608
+ }
609
+}
610
+
611
+func (tlsPlaceholderWrapper) WrapListener(ln net.Listener) net.Listener { return ln }
612
+
613
const (
614
// DefaultHTTPPort is the default port for HTTP.
615
DefaultHTTPPort = 80
@@ -557,4 +623,6 @@ var (
623
_ caddy.App = (*App)(nil)
624
_ caddy.Provisioner = (*App)(nil)
625
_ caddy.Validator = (*App)(nil)
626
+
627
+ _ caddy.ListenerWrapper = (*tlsPlaceholderWrapper)(nil)
628
)
modules/caddyhttp/server.go
+6
@@ -16,6 +16,7 @@ package caddyhttp
16
17
import (
18
"context"
19
+ "encoding/json"
20
"fmt"
21
"net"
22
"net/http"
@@ -38,6 +39,10 @@ type Server struct {
39
// that may include port ranges.
40
Listen []string `json:"listen,omitempty"`
41
42
+ // A list of listener wrapper modules, which can modify the behavior
43
+ // of the base listener. They are applied in the given order.
44
+ ListenerWrappersRaw []json.RawMessage `json:"listener_wrappers,omitempty" caddy:"namespace=caddy.listeners inline_key=wrapper"`
45
+
46
// How long to allow a read from a client's upload. Setting this
47
// to a short, non-zero value can mitigate slowloris attacks, but
48
// may also affect legitimately slow clients.
@@ -106,6 +111,7 @@ type Server struct {
111
112
primaryHandlerChain Handler
113
errorHandlerChain Handler
114
+ listenerWrappers []caddy.ListenerWrapper
115
116
tlsApp *caddytls.TLS
117
logger *zap.Logger