@marcel91200 / caddy Mirror · read-only / commits / f9d93ead
Fork 0

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)