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
Rename and export some types, other minor changes
Matthew Holt committed
May 14, 2019 at 14:14 UTC
f9d93ead4ef6e099ba7e00318dce6509b0f1eda4
7 files changed
+144
-99
modules/caddyhttp/caddyhttp.go
+32
-28
@@ -22,29 +22,31 @@ func init() {
22
23
err := caddy2.RegisterModule(caddy2.Module{
24
Name: "http",
25
- New: func() (interface{}, error) { return new(httpModuleConfig), nil },
25
+ New: func() (interface{}, error) { return new(App), nil },
26
})
27
if err != nil {
28
log.Fatal(err)
29
}
30
}
31
32
-type httpModuleConfig struct {
33
- HTTPPort int `json:"http_port"`
34
- HTTPSPort int `json:"https_port"`
35
- GracePeriod caddy2.Duration `json:"grace_period"`
36
- Servers map[string]*httpServerConfig `json:"servers"`
32
+// App is the HTTP app for Caddy.
33
+type App struct {
34
+ HTTPPort int `json:"http_port"`
35
+ HTTPSPort int `json:"https_port"`
36
+ GracePeriod caddy2.Duration `json:"grace_period"`
37
+ Servers map[string]*Server `json:"servers"`
38
39
servers []*http.Server
40
}
41
41
-func (hc *httpModuleConfig) Provision() error {
42
+// Provision sets up the app.
43
+func (hc *App) Provision() error {
44
for _, srv := range hc.Servers {
43
- err := srv.Routes.setup()
45
+ err := srv.Routes.Provision()
46
if err != nil {
47
return fmt.Errorf("setting up server routes: %v", err)
48
}
47
- err = srv.Errors.Routes.setup()
49
+ err = srv.Errors.Routes.Provision()
50
if err != nil {
51
return fmt.Errorf("setting up server error handling routes: %v", err)
52
}
@@ -53,7 +55,8 @@ func (hc *httpModuleConfig) Provision() error {
55
return nil
56
}
57
56
-func (hc *httpModuleConfig) Validate() error {
58
+// Validate ensures the app's configuration is valid.
59
+func (hc *App) Validate() error {
60
// each server must use distinct listener addresses
61
lnAddrs := make(map[string]string)
62
for srvName, srv := range hc.Servers {
@@ -74,7 +77,8 @@ func (hc *httpModuleConfig) Validate() error {
77
return nil
78
}
79
77
-func (hc *httpModuleConfig) Start(handle caddy2.Handle) error {
80
+// Start runs the app. It sets up automatic HTTPS if enabled.
81
+func (hc *App) Start(handle caddy2.Handle) error {
82
err := hc.automaticHTTPS(handle)
83
if err != nil {
84
return fmt.Errorf("enabling automatic HTTPS: %v", err)
@@ -129,7 +133,7 @@ func (hc *httpModuleConfig) Start(handle caddy2.Handle) error {
133
}
134
135
// Stop gracefully shuts down the HTTP server.
132
-func (hc *httpModuleConfig) Stop() error {
136
+func (hc *App) Stop() error {
137
ctx := context.Background()
138
if hc.GracePeriod > 0 {
139
var cancel context.CancelFunc
@@ -145,7 +149,7 @@ func (hc *httpModuleConfig) Stop() error {
149
return nil
150
}
151
148
-func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
152
+func (hc *App) automaticHTTPS(handle caddy2.Handle) error {
153
tlsAppIface, err := handle.App("tls")
154
if err != nil {
155
return fmt.Errorf("getting tls app: %v", err)
@@ -153,7 +157,7 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
157
tlsApp := tlsAppIface.(*caddytls.TLS)
158
159
lnAddrMap := make(map[string]struct{})
156
- var redirRoutes routeList
160
+ var redirRoutes RouteList
161
162
for srvName, srv := range hc.Servers {
163
srv.tlsApp = tlsApp
@@ -222,7 +226,7 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
226
}
227
redirTo += "{request.uri}"
228
225
- redirRoutes = append(redirRoutes, serverRoute{
229
+ redirRoutes = append(redirRoutes, ServerRoute{
230
matchers: []RouteMatcher{
231
matchProtocol("http"),
232
matchHost(domains),
@@ -255,7 +259,7 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
259
}
260
lnAddrs = append(lnAddrs, addr)
261
}
258
- hc.Servers["auto_https_redirects"] = &httpServerConfig{
262
+ hc.Servers["auto_https_redirects"] = &Server{
263
Listen: lnAddrs,
264
Routes: redirRoutes,
265
DisableAutoHTTPS: true,
@@ -265,7 +269,7 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
269
return nil
270
}
271
268
-func (hc *httpModuleConfig) listenerTaken(network, address string) bool {
272
+func (hc *App) listenerTaken(network, address string) bool {
273
for _, srv := range hc.Servers {
274
for _, addr := range srv.Listen {
275
netw, addrs, err := parseListenAddr(addr)
@@ -284,12 +288,13 @@ func (hc *httpModuleConfig) listenerTaken(network, address string) bool {
288
289
var defaultALPN = []string{"h2", "http/1.1"}
290
287
-type httpServerConfig struct {
291
+// Server is an HTTP server.
292
+type Server struct {
293
Listen []string `json:"listen"`
294
ReadTimeout caddy2.Duration `json:"read_timeout"`
295
ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
296
HiddenFiles []string `json:"hidden_files"` // TODO:... experimenting with shared/common state
292
- Routes routeList `json:"routes"`
297
+ Routes RouteList `json:"routes"`
298
Errors httpErrorConfig `json:"errors"`
299
TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies"`
300
DisableAutoHTTPS bool `json:"disable_auto_https"`
@@ -299,23 +304,24 @@ type httpServerConfig struct {
304
}
305
306
type httpErrorConfig struct {
302
- Routes routeList `json:"routes"`
303
- // TODO: some way to configure the logging of errors, probably? standardize the logging configuration first.
307
+ Routes RouteList `json:"routes"`
308
+ // TODO: some way to configure the logging of errors, probably? standardize
309
+ // the logging configuration first.
310
}
311
312
// ServeHTTP is the entry point for all HTTP requests.
307
-func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
313
+func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
314
if s.tlsApp.HandleHTTPChallenge(w, r) {
315
return
316
}
317
318
// set up the replacer
313
- repl := &Replacer{req: r, resp: w, custom: make(map[string]string)}
319
+ repl := NewReplacer(r, w)
320
ctx := context.WithValue(r.Context(), ReplacerCtxKey, repl)
321
r = r.WithContext(ctx)
322
323
// build and execute the main middleware chain
318
- stack := s.Routes.buildMiddlewareChain(w, r)
324
+ stack := s.Routes.BuildHandlerChain(w, r)
325
err := executeMiddlewareChain(w, r, stack)
326
if err != nil {
327
// add the error value to the request context so
@@ -328,7 +334,7 @@ func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
334
// TODO: implement a default error handler?
335
log.Printf("[ERROR] %s", err)
336
} else {
331
- errStack := s.Errors.Routes.buildMiddlewareChain(w, r)
337
+ errStack := s.Errors.Routes.BuildHandlerChain(w, r)
338
err := executeMiddlewareChain(w, r, errStack)
339
if err != nil {
340
// TODO: what should we do if the error handler has an error?
@@ -411,6 +417,7 @@ func parseListenAddr(a string) (network string, addrs []string, err error) {
417
if err != nil {
418
return
419
}
420
+ host = NewReplacer(nil, nil).Replace(host, "")
421
ports := strings.SplitN(port, "-", 2)
422
if len(ports) == 1 {
423
ports = append(ports, ports[0])
@@ -474,9 +481,6 @@ func (mrw middlewareResponseWriter) Write(b []byte) (int, error) {
481
return mrw.ResponseWriterWrapper.Write(b)
482
}
483
477
-// ReplacerCtxKey is the context key for the request's replacer.
478
-const ReplacerCtxKey caddy2.CtxKey = "replacer"
479
-
484
const (
485
// DefaultHTTPPort is the default port for HTTP.
486
DefaultHTTPPort = 80
modules/caddyhttp/caddyhttp_test.go
+11
@@ -1,6 +1,7 @@
1
package caddyhttp
2
3
import (
4
+ "os"
5
"reflect"
6
"testing"
7
)
@@ -114,6 +115,11 @@ func TestJoinListenerAddr(t *testing.T) {
115
}
116
117
func TestParseListenerAddr(t *testing.T) {
118
+ hostname, err := os.Hostname()
119
+ if err != nil {
120
+ t.Fatalf("Cannot ascertain system hostname: %v", err)
121
+ }
122
+
123
for i, tc := range []struct {
124
input string
125
expectNetwork string
@@ -170,6 +176,11 @@ func TestParseListenerAddr(t *testing.T) {
176
expectNetwork: "tcp",
177
expectAddrs: []string{"localhost:0"},
178
},
179
+ {
180
+ input: "{system.hostname}:0",
181
+ expectNetwork: "tcp",
182
+ expectAddrs: []string{hostname + ":0"},
183
+ },
184
} {
185
actualNetwork, actualAddrs, err := parseListenAddr(tc.input)
186
if tc.expectErr && err == nil {
modules/caddyhttp/matchers_test.go
+2
-2
@@ -205,7 +205,7 @@ func TestPathREMatcher(t *testing.T) {
205
206
// set up the fake request and its Replacer
207
req := &http.Request{URL: &url.URL{Path: tc.input}}
208
- repl := &Replacer{req: req, resp: httptest.NewRecorder(), custom: make(map[string]string)}
208
+ repl := NewReplacer(req, httptest.NewRecorder())
209
ctx := context.WithValue(req.Context(), ReplacerCtxKey, repl)
210
req = req.WithContext(ctx)
211
@@ -322,7 +322,7 @@ func TestHeaderREMatcher(t *testing.T) {
322
323
// set up the fake request and its Replacer
324
req := &http.Request{Header: tc.input, URL: new(url.URL)}
325
- repl := &Replacer{req: req, resp: httptest.NewRecorder(), custom: make(map[string]string)}
325
+ repl := NewReplacer(req, httptest.NewRecorder())
326
ctx := context.WithValue(req.Context(), ReplacerCtxKey, repl)
327
req = req.WithContext(ctx)
328
modules/caddyhttp/replacer.go
+50
-30
@@ -5,18 +5,31 @@ import (
5
"net/http"
6
"os"
7
"strings"
8
+
9
+ "bitbucket.org/lightcodelabs/caddy2"
10
)
11
12
// Replacer can replace values in strings based
13
// on a request and/or response writer. The zero
12
-// Replacer is not valid; it must be initialized
13
-// within this package.
14
+// Replacer is not valid; use NewReplacer() to
15
+// initialize one.
16
type Replacer struct {
17
req *http.Request
18
resp http.ResponseWriter
19
custom map[string]string
20
}
21
22
+// NewReplacer makes a new Replacer, initializing all necessary
23
+// fields. The request and response writer are optional, but
24
+// necessary for most replacements to work.
25
+func NewReplacer(req *http.Request, rw http.ResponseWriter) *Replacer {
26
+ return &Replacer{
27
+ req: req,
28
+ resp: rw,
29
+ custom: make(map[string]string),
30
+ }
31
+}
32
+
33
// Map sets a custom variable mapping to a value.
34
func (r *Replacer) Map(variable, value string) {
35
r.custom[variable] = value
@@ -48,51 +61,58 @@ func (r *Replacer) replaceAll(input, empty string, mapping map[string]string) st
61
62
func (r *Replacer) defaults() map[string]string {
63
m := map[string]string{
51
- "request.host": func() string {
64
+ "system.hostname": func() string {
65
+ // OK if there is an error; just return empty string
66
+ name, _ := os.Hostname()
67
+ return name
68
+ }(),
69
+ }
70
+
71
+ if r.req != nil {
72
+ m["request.host"] = func() string {
73
host, _, err := net.SplitHostPort(r.req.Host)
74
if err != nil {
75
return r.req.Host // OK; there probably was no port
76
}
77
return host
57
- }(),
58
- "request.hostport": r.req.Host, // may include both host and port
59
- "request.method": r.req.Method,
60
- "request.port": func() string {
78
+ }()
79
+ m["request.hostport"] = r.req.Host // may include both host and port
80
+ m["request.method"] = r.req.Method
81
+ m["request.port"] = func() string {
82
// if there is no port, there will be an error; in
83
// that case, port is the empty string anyway
84
_, port, _ := net.SplitHostPort(r.req.Host)
85
return port
65
- }(),
66
- "request.scheme": func() string {
86
+ }()
87
+ m["request.scheme"] = func() string {
88
if r.req.TLS != nil {
89
return "https"
90
}
91
return "http"
71
- }(),
72
- "request.uri": r.req.URL.RequestURI(),
73
- "system.hostname": func() string {
74
- // OK if there is an error; just return empty string
75
- name, _ := os.Hostname()
76
- return name
77
- }(),
78
- }
92
+ }()
93
+ m["request.uri"] = r.req.URL.RequestURI()
94
+ m["request.uri.path"] = r.req.URL.Path
95
80
- // TODO: why should header fields, cookies, and query params get special treatment like this?
81
- // maybe they should be scoped by words like "request.header." just like everything else.
82
- for field, vals := range r.req.Header {
83
- m[">"+strings.ToLower(field)] = strings.Join(vals, ",")
84
- }
85
- for field, vals := range r.resp.Header() {
86
- m["<"+strings.ToLower(field)] = strings.Join(vals, ",")
87
- }
88
- for _, cookie := range r.req.Cookies() {
89
- m["~"+cookie.Name] = cookie.Value
90
- }
91
- for param, vals := range r.req.URL.Query() {
92
- m["?"+param] = strings.Join(vals, ",")
96
+ // TODO: why should header fields, cookies, and query params get special treatment like this?
97
+ // maybe they should be scoped by words like "request.header." just like everything else.
98
+ for field, vals := range r.req.Header {
99
+ m[">"+strings.ToLower(field)] = strings.Join(vals, ",")
100
+ }
101
+ for field, vals := range r.resp.Header() {
102
+ m["<"+strings.ToLower(field)] = strings.Join(vals, ",")
103
+ }
104
+ for _, cookie := range r.req.Cookies() {
105
+ m["~"+cookie.Name] = cookie.Value
106
+ }
107
+ for param, vals := range r.req.URL.Query() {
108
+ m["?"+param] = strings.Join(vals, ",")
109
+ }
110
}
111
112
return m
113
}
114
115
const phOpen, phClose = "{", "}"
116
+
117
+// ReplacerCtxKey is the context key for the request's replacer.
118
+const ReplacerCtxKey caddy2.CtxKey = "replacer"
modules/caddyhttp/routes.go
+46
-38
@@ -8,7 +8,10 @@ import (
8
"bitbucket.org/lightcodelabs/caddy2"
9
)
10
11
-type serverRoute struct {
11
+// ServerRoute represents a set of matching rules,
12
+// middlewares, and a responder for handling HTTP
13
+// requests.
14
+type ServerRoute struct {
15
Matchers map[string]json.RawMessage `json:"match"`
16
Apply []json.RawMessage `json:"apply"`
17
Respond json.RawMessage `json:"respond"`
@@ -21,9 +24,49 @@ type serverRoute struct {
24
responder Handler
25
}
26
24
-type routeList []serverRoute
27
+// RouteList is a list of server routes that can
28
+// create a middleware chain.
29
+type RouteList []ServerRoute
30
26
-func (routes routeList) buildMiddlewareChain(w http.ResponseWriter, r *http.Request) Handler {
31
+// Provision sets up all the routes by loading the modules.
32
+func (routes RouteList) Provision() error {
33
+ for i, route := range routes {
34
+ // matchers
35
+ for modName, rawMsg := range route.Matchers {
36
+ val, err := caddy2.LoadModule("http.matchers."+modName, rawMsg)
37
+ if err != nil {
38
+ return fmt.Errorf("loading matcher module '%s': %v", modName, err)
39
+ }
40
+ routes[i].matchers = append(routes[i].matchers, val.(RouteMatcher))
41
+ }
42
+ routes[i].Matchers = nil // allow GC to deallocate - TODO: Does this help?
43
+
44
+ // middleware
45
+ for j, rawMsg := range route.Apply {
46
+ mid, err := caddy2.LoadModuleInline("middleware", "http.middleware", rawMsg)
47
+ if err != nil {
48
+ return fmt.Errorf("loading middleware module in position %d: %v", j, err)
49
+ }
50
+ routes[i].middleware = append(routes[i].middleware, mid.(MiddlewareHandler))
51
+ }
52
+ routes[i].Apply = nil // allow GC to deallocate - TODO: Does this help?
53
+
54
+ // responder
55
+ if route.Respond != nil {
56
+ resp, err := caddy2.LoadModuleInline("responder", "http.responders", route.Respond)
57
+ if err != nil {
58
+ return fmt.Errorf("loading responder module: %v", err)
59
+ }
60
+ routes[i].responder = resp.(Handler)
61
+ }
62
+ routes[i].Respond = nil // allow GC to deallocate - TODO: Does this help?
63
+ }
64
+ return nil
65
+}
66
+
67
+// BuildHandlerChain creates a chain of handlers by
68
+// applying all the matching routes.
69
+func (routes RouteList) BuildHandlerChain(w http.ResponseWriter, r *http.Request) Handler {
70
if len(routes) == 0 {
71
return emptyHandler
72
}
@@ -68,38 +111,3 @@ routeLoop:
111
112
return stack
113
}
71
-
72
-func (routes routeList) setup() error {
73
- for i, route := range routes {
74
- // matchers
75
- for modName, rawMsg := range route.Matchers {
76
- val, err := caddy2.LoadModule("http.matchers."+modName, rawMsg)
77
- if err != nil {
78
- return fmt.Errorf("loading matcher module '%s': %v", modName, err)
79
- }
80
- routes[i].matchers = append(routes[i].matchers, val.(RouteMatcher))
81
- }
82
- routes[i].Matchers = nil // allow GC to deallocate - TODO: Does this help?
83
-
84
- // middleware
85
- for j, rawMsg := range route.Apply {
86
- mid, err := caddy2.LoadModuleInline("middleware", "http.middleware", rawMsg)
87
- if err != nil {
88
- return fmt.Errorf("loading middleware module in position %d: %v", j, err)
89
- }
90
- routes[i].middleware = append(routes[i].middleware, mid.(MiddlewareHandler))
91
- }
92
- routes[i].Apply = nil // allow GC to deallocate - TODO: Does this help?
93
-
94
- // responder
95
- if route.Respond != nil {
96
- resp, err := caddy2.LoadModuleInline("responder", "http.responders", route.Respond)
97
- if err != nil {
98
- return fmt.Errorf("loading responder module: %v", err)
99
- }
100
- routes[i].responder = resp.(Handler)
101
- }
102
- routes[i].Respond = nil // allow GC to deallocate - TODO: Does this help?
103
- }
104
- return nil
105
-}
modules/caddyhttp/staticresp.go
-1
@@ -15,7 +15,6 @@ func init() {
15
}
16
17
// Static implements a simple responder for static responses.
18
-// It is Caddy's default responder. TODO: Or is it?
18
type Static struct {
19
StatusCode int `json:"status_code"`
20
Headers http.Header `json:"headers"`
modules/caddytls/connpolicy.go
+3
@@ -85,6 +85,9 @@ func (cp *ConnectionPolicy) buildStandardTLSConfig(handle caddy2.Handle) error {
85
NextProtos: cp.ALPN,
86
PreferServerCipherSuites: true,
87
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
88
+ // TODO: Must fix https://github.com/mholt/caddy/issues/2588
89
+ // (allow customizing the selection of a very specific certificate
90
+ // based on the ClientHelloInfo)
91
cfgTpl, err := tlsApp.getConfigForName(hello.ServerName)
92
if err != nil {
93
return nil, fmt.Errorf("getting config for name %s: %v", hello.ServerName, err)