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
Begin implementing HTTP replacer and static responder
Matthew Holt committed
May 4, 2019 at 13:21 UTC
2eb35933271d5b2ef1ca29aaa8f00f6748c56424
6 files changed
+161
-30
admin_test.go
+1
-1
@@ -9,7 +9,7 @@ func BenchmarkLoad(b *testing.B) {
9
for i := 0; i < b.N; i++ {
10
r := strings.NewReader(`{
11
"testval": "Yippee!",
12
- "modules": {
12
+ "apps": {
13
"http": {
14
"servers": {
15
"myserver": {
cmd/caddy2/main.go
+4
-14
@@ -1,28 +1,18 @@
1
package main
2
3
import (
4
- "log"
5
-
6
- _ "net/http/pprof"
7
-
8
- "bitbucket.org/lightcodelabs/caddy2"
4
+ caddycmd "bitbucket.org/lightcodelabs/caddy2/cmd"
5
6
// this is where modules get plugged in
7
8
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
9
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/caddylog"
10
+ _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/reverseproxy"
11
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticfiles"
12
+ _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticresp"
13
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddytls"
16
- _ "bitbucket.org/lightcodelabs/dynamicconfig"
17
- _ "bitbucket.org/lightcodelabs/proxy"
14
)
15
16
func main() {
21
- err := caddy2.StartAdmin("127.0.0.1:1234")
22
- if err != nil {
23
- log.Fatal(err)
24
- }
25
- defer caddy2.StopAdmin()
26
-
27
- select {}
17
+ caddycmd.Main()
18
}
modules/caddyhttp/caddyhttp.go
+17
-8
@@ -167,14 +167,15 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
167
var defaultALPN = []string{"h2", "http/1.1"}
168
169
type httpServerConfig struct {
170
- Listen []string `json:"listen"`
171
- ReadTimeout caddy2.Duration `json:"read_timeout"`
172
- ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
173
- HiddenFiles []string `json:"hidden_files"` // TODO:... experimenting with shared/common state
174
- Routes routeList `json:"routes"`
175
- Errors httpErrorConfig `json:"errors"`
176
- TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies"`
177
- DisableAutoHTTPS bool `json:"disable_auto_https"`
170
+ Listen []string `json:"listen"`
171
+ ReadTimeout caddy2.Duration `json:"read_timeout"`
172
+ ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
173
+ HiddenFiles []string `json:"hidden_files"` // TODO:... experimenting with shared/common state
174
+ Routes routeList `json:"routes"`
175
+ Errors httpErrorConfig `json:"errors"`
176
+ TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies"`
177
+ DisableAutoHTTPS bool `json:"disable_auto_https"`
178
+ DisableAutoHTTPSRedir bool `json:"disable_auto_https_redir"`
179
180
tlsApp *caddytls.TLS
181
}
@@ -190,6 +191,12 @@ func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
191
return
192
}
193
194
+ // set up the replacer
195
+ repl := &Replacer{req: r, resp: w, custom: make(map[string]string)}
196
+ ctx := context.WithValue(r.Context(), ReplacerCtxKey, repl)
197
+ r = r.WithContext(ctx)
198
+
199
+ // build and execute the main middleware chain
200
stack := s.Routes.buildMiddlewareChain(w, r)
201
err := executeMiddlewareChain(w, r, stack)
202
if err != nil {
@@ -329,5 +336,7 @@ func (mrw middlewareResponseWriter) Write(b []byte) (int, error) {
336
return mrw.ResponseWriterWrapper.Write(b)
337
}
338
339
+const ReplacerCtxKey caddy2.CtxKey = "replacer"
340
+
341
// Interface guards
342
var _ HTTPInterfaces = middlewareResponseWriter{}
modules/caddyhttp/matchers.go
+7
-7
@@ -139,11 +139,11 @@ func (m matchHeader) Match(r *http.Request) bool {
139
140
// Interface guards
141
var (
142
- _ RouteMatcher = matchHost{}
143
- _ RouteMatcher = matchPath{}
144
- _ RouteMatcher = matchMethod{}
145
- _ RouteMatcher = matchQuery{}
146
- _ RouteMatcher = matchHeader{}
147
- _ RouteMatcher = new(matchProtocol)
148
- _ RouteMatcher = new(matchScript)
142
+ _ RouteMatcher = (*matchHost)(nil)
143
+ _ RouteMatcher = (*matchPath)(nil)
144
+ _ RouteMatcher = (*matchMethod)(nil)
145
+ _ RouteMatcher = (*matchQuery)(nil)
146
+ _ RouteMatcher = (*matchHeader)(nil)
147
+ _ RouteMatcher = (*matchProtocol)(nil)
148
+ _ RouteMatcher = (*matchScript)(nil)
149
)
modules/caddyhttp/replacer.go
new
+75
@@ -0,0 +1,75 @@
1
+package caddyhttp
2
+
3
+import (
4
+ "net/http"
5
+ "strings"
6
+)
7
+
8
+type Replacer struct {
9
+ req *http.Request
10
+ resp http.ResponseWriter
11
+ custom map[string]string
12
+}
13
+
14
+// Map sets a custom variable mapping to a value.
15
+func (r *Replacer) Map(variable, value string) {
16
+ r.custom[variable] = value
17
+}
18
+
19
+// Replace replaces placeholders in input with the value. If
20
+// the value is empty string, the placeholder is substituted
21
+// with the value empty.
22
+func (r *Replacer) Replace(input, empty string) string {
23
+ if !strings.Contains(input, phOpen) {
24
+ return input
25
+ }
26
+
27
+ input = r.replaceAll(input, empty, r.defaults())
28
+ input = r.replaceAll(input, empty, r.custom)
29
+
30
+ return input
31
+}
32
+
33
+func (r *Replacer) replaceAll(input, empty string, mapping map[string]string) string {
34
+ for key, val := range mapping {
35
+ if val == "" {
36
+ val = empty
37
+ }
38
+ input = strings.ReplaceAll(input, phOpen+key+phClose, val)
39
+ }
40
+ return input
41
+}
42
+
43
+func (r *Replacer) defaults() map[string]string {
44
+ m := map[string]string{
45
+ "host": r.req.Host,
46
+ "method": r.req.Method,
47
+ "scheme": func() string {
48
+ if r.req.TLS != nil {
49
+ return "https"
50
+ }
51
+ return "http"
52
+ }(),
53
+ "uri": r.req.URL.RequestURI(),
54
+ }
55
+
56
+ for field, vals := range r.req.Header {
57
+ m[">"+strings.ToLower(field)] = strings.Join(vals, ",")
58
+ }
59
+
60
+ for field, vals := range r.resp.Header() {
61
+ m["<"+strings.ToLower(field)] = strings.Join(vals, ",")
62
+ }
63
+
64
+ for _, cookie := range r.req.Cookies() {
65
+ m["~"+cookie.Name] = cookie.Value
66
+ }
67
+
68
+ for param, vals := range r.req.URL.Query() {
69
+ m["?"+param] = strings.Join(vals, ",")
70
+ }
71
+
72
+ return m
73
+}
74
+
75
+const phOpen, phClose = "{", "}"
modules/caddyhttp/staticresp/staticresp.go
new
+57
@@ -0,0 +1,57 @@
1
+package staticresp
2
+
3
+import (
4
+ "fmt"
5
+ "net/http"
6
+
7
+ "bitbucket.org/lightcodelabs/caddy2"
8
+ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
9
+)
10
+
11
+func init() {
12
+ caddy2.RegisterModule(caddy2.Module{
13
+ Name: "http.responders.static",
14
+ New: func() (interface{}, error) { return new(Static), nil },
15
+ })
16
+}
17
+
18
+// Static implements a simple responder for static responses.
19
+type Static struct {
20
+ StatusCode int `json:"status_code"`
21
+ Headers map[string][]string `json:"headers"`
22
+ Body string `json:"body"`
23
+ Close bool `json:"close"`
24
+}
25
+
26
+func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
27
+ repl := r.Context().Value(caddyhttp.ReplacerCtxKey).(*caddyhttp.Replacer)
28
+
29
+ // close the connection
30
+ r.Close = s.Close
31
+
32
+ // set all headers, with replacements
33
+ for field, vals := range s.Headers {
34
+ field = repl.Replace(field, "")
35
+ for i := range vals {
36
+ vals[i] = repl.Replace(vals[i], "")
37
+ }
38
+ w.Header()[field] = vals
39
+ }
40
+
41
+ // write the headers with a status code
42
+ statusCode := s.StatusCode
43
+ if statusCode == 0 {
44
+ statusCode = http.StatusOK
45
+ }
46
+ w.WriteHeader(statusCode)
47
+
48
+ // write the response body, with replacements
49
+ if s.Body != "" {
50
+ fmt.Fprint(w, repl.Replace(s.Body, ""))
51
+ }
52
+
53
+ return nil
54
+}
55
+
56
+// Interface guard
57
+var _ caddyhttp.Handler = (*Static)(nil)