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
Protocol and Caddyscript matchers
* Added matcher to determine what protocol the request is being made by - grpc, tls, http * Added ability to run caddyscript in a matcher to evaluate the http request * Added TLS field to caddyscript request time * Added a library to manipulate and compare a new caddyscript time type * Library for regex in starlark
dev committed
Apr 3, 2019 at 11:47 UTC
27ecc7f384bbfc98b28dc73881968897fb8c9a8a
9 files changed
+341
-12
.gitignore
+3
@@ -3,3 +3,6 @@ _gitignore/
3
# artifacts from pprof tooling
4
*.prof
5
*.test
6
+
7
+# mac specific
8
+.DS_Store
cmd/caddy2/main.go
+3
-2
@@ -3,11 +3,12 @@ package main
3
import (
4
"log"
5
6
- "bitbucket.org/lightcodelabs/caddy2"
7
-
6
_ "net/http/pprof"
7
8
+ "bitbucket.org/lightcodelabs/caddy2"
9
+
10
// this is where modules get plugged in
11
+
12
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
13
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/caddylog"
14
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticfiles"
internal/caddyscript/lib/http.go
new
+72
@@ -0,0 +1,72 @@
1
+package caddyscript
2
+
3
+import (
4
+ "fmt"
5
+ "net/http"
6
+
7
+ "go.starlark.net/starlark"
8
+)
9
+
10
+// HTTPRequest represents an http request type in caddyscript.
11
+type HTTPRequest struct{ Req *http.Request }
12
+
13
+// AttrNames defines what properties and methods are available on the HTTPRequest type.
14
+func (r HTTPRequest) AttrNames() []string {
15
+ return []string{"header", "query", "url", "method", "host", "tls"}
16
+}
17
+
18
+func (r HTTPRequest) Freeze() {}
19
+func (r HTTPRequest) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: HTTPRequest") }
20
+func (r HTTPRequest) String() string { return fmt.Sprint(r.Req) }
21
+func (r HTTPRequest) Type() string { return "HTTPRequest" }
22
+func (r HTTPRequest) Truth() starlark.Bool { return true }
23
+
24
+// Header handles returning a header key.
25
+func (r HTTPRequest) Header(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
26
+ var key string
27
+ err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &key)
28
+ if err != nil {
29
+ return starlark.None, fmt.Errorf("get request header: %v", err.Error())
30
+ }
31
+
32
+ return starlark.String(r.Req.Header.Get(key)), nil
33
+}
34
+
35
+// Attr defines what happens when props or methods are called on the HTTPRequest type.
36
+func (r HTTPRequest) Attr(name string) (starlark.Value, error) {
37
+ switch name {
38
+ case "tls":
39
+ tls := new(starlark.Dict)
40
+ tls.SetKey(starlark.String("cipher_suite"), starlark.MakeUint(uint(r.Req.TLS.CipherSuite)))
41
+ tls.SetKey(starlark.String("did_resume"), starlark.Bool(r.Req.TLS.DidResume))
42
+ tls.SetKey(starlark.String("handshake_complete"), starlark.Bool(r.Req.TLS.HandshakeComplete))
43
+ tls.SetKey(starlark.String("negotiated_protocol"), starlark.String(r.Req.TLS.NegotiatedProtocol))
44
+ tls.SetKey(starlark.String("negotiated_protocol_is_mutual"), starlark.Bool(r.Req.TLS.NegotiatedProtocolIsMutual))
45
+ tls.SetKey(starlark.String("server_name"), starlark.String(r.Req.TLS.ServerName))
46
+ tls.SetKey(starlark.String("version"), starlark.String(r.Req.TLS.Version))
47
+
48
+ return tls, nil
49
+ case "header":
50
+ b := starlark.NewBuiltin("Header", r.Header)
51
+ b = b.BindReceiver(r)
52
+
53
+ return b, nil
54
+ case "query":
55
+ qVals := r.Req.URL.Query()
56
+ query := starlark.NewDict(len(qVals))
57
+
58
+ for k, v := range qVals {
59
+ query.SetKey(starlark.String(k), starlark.String(v[0]))
60
+ }
61
+
62
+ return query, nil
63
+ case "url":
64
+ return starlark.String(r.Req.URL.Path), nil
65
+ case "method":
66
+ return starlark.String(r.Req.Method), nil
67
+ case "host":
68
+ return starlark.String(r.Req.Host), nil
69
+ }
70
+
71
+ return nil, nil
72
+}
internal/caddyscript/lib/lib.go
new
+11
@@ -0,0 +1,11 @@
1
+package caddyscript
2
+
3
+import (
4
+ "fmt"
5
+
6
+ "go.starlark.net/starlark"
7
+)
8
+
9
+func invalidReciever(v starlark.Value, want string) (starlark.Value, error) {
10
+ return starlark.None, fmt.Errorf("invalid receiver: receiver set to type %v, want %v", v.Type(), want)
11
+}
internal/caddyscript/lib/regex.go
new
+50
@@ -0,0 +1,50 @@
1
+package caddyscript
2
+
3
+import (
4
+ "fmt"
5
+ "regexp"
6
+
7
+ "go.starlark.net/starlark"
8
+)
9
+
10
+// Regexp represents a regexp type for caddyscript.
11
+type Regexp struct{}
12
+
13
+// AttrNames defines what properties and methods are available on the Time type.
14
+func (r Regexp) AttrNames() []string {
15
+ return []string{"match_string"}
16
+}
17
+
18
+// Attr defines what happens when props or methods are called on the Time type.
19
+func (r Regexp) Attr(name string) (starlark.Value, error) {
20
+ switch name {
21
+ case "match_string":
22
+ b := starlark.NewBuiltin("match_string", r.MatchString)
23
+ b = b.BindReceiver(r)
24
+ return b, nil
25
+ }
26
+
27
+ return nil, nil
28
+}
29
+
30
+// MatchString reports whether the string s contains any match of the regular expression pattern. More complicated queries need to use Compile and the full Regexp interface.
31
+func (r Regexp) MatchString(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
32
+ var pattern, match string
33
+ err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &pattern, &match)
34
+ if err != nil {
35
+ return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error())
36
+ }
37
+
38
+ matched, err := regexp.MatchString(pattern, match)
39
+ if err != nil {
40
+ return starlark.False, fmt.Errorf("matchstring: %v", err.Error())
41
+ }
42
+
43
+ return starlark.Bool(matched), nil
44
+}
45
+
46
+func (r Regexp) Freeze() {}
47
+func (r Regexp) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: Regexp") }
48
+func (r Regexp) String() string { return fmt.Sprint(r) }
49
+func (r Regexp) Type() string { return "Regexp" }
50
+func (r Regexp) Truth() starlark.Bool { return true }
internal/caddyscript/lib/time.go
new
+130
@@ -0,0 +1,130 @@
1
+package caddyscript
2
+
3
+import (
4
+ "fmt"
5
+ ti "time"
6
+
7
+ "go.starlark.net/starlark"
8
+)
9
+
10
+// Time represents a time type for caddyscript.
11
+type Time struct {
12
+ value int64 // time since epoch in nanoseconds
13
+}
14
+
15
+// AttrNames defines what properties and methods are available on the Time type.
16
+func (r Time) AttrNames() []string {
17
+ return []string{"now", "parse", "add", "subtract", "minute", "hour", "day", "value"}
18
+}
19
+
20
+// Attr defines what happens when props or methods are called on the Time type.
21
+func (r Time) Attr(name string) (starlark.Value, error) {
22
+ switch name {
23
+ case "now":
24
+ b := starlark.NewBuiltin("now", r.Now)
25
+ b = b.BindReceiver(r)
26
+ return b, nil
27
+ case "parse_duration":
28
+ b := starlark.NewBuiltin("parse_duration", r.ParseDuration)
29
+ b = b.BindReceiver(r)
30
+ return b, nil
31
+ case "add":
32
+ b := starlark.NewBuiltin("add", r.Add)
33
+ b = b.BindReceiver(r)
34
+ return b, nil
35
+ case "subtract":
36
+ b := starlark.NewBuiltin("subtract", r.Subtract)
37
+ b = b.BindReceiver(r)
38
+ return b, nil
39
+ case "minute":
40
+ b := starlark.NewBuiltin("minute", r.Minute)
41
+ b = b.BindReceiver(r)
42
+ return b, nil
43
+ case "hour":
44
+ b := starlark.NewBuiltin("hour", r.Hour)
45
+ b = b.BindReceiver(r)
46
+ return b, nil
47
+ case "day":
48
+ b := starlark.NewBuiltin("day", r.Day)
49
+ b = b.BindReceiver(r)
50
+ return b, nil
51
+ case "value":
52
+ return starlark.MakeInt64(r.value), nil
53
+ }
54
+
55
+ return nil, nil
56
+}
57
+
58
+func (r Time) Freeze() {}
59
+func (r Time) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: Time") }
60
+func (r Time) String() string { return fmt.Sprint(r.value) }
61
+func (r Time) Type() string { return "Time" }
62
+func (r Time) Truth() starlark.Bool { return true }
63
+
64
+// Hour returns the current hour of a unix timestamp in range [0, 23].
65
+func (r Time) Hour(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
66
+ t := ti.Unix(0, r.value)
67
+ return starlark.MakeInt(t.Hour()), nil
68
+}
69
+
70
+// Minute returns the current minute of the hour for a unix timestamp in range [0, 59].
71
+func (r Time) Minute(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
72
+ t := ti.Unix(0, r.value)
73
+ return starlark.MakeInt(t.Minute()), nil
74
+}
75
+
76
+// Day returns the current day in a week of a unix timestamp... [Sunday = 0...]
77
+func (r Time) Day(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
78
+ t := ti.Unix(0, r.value)
79
+ return starlark.MakeInt(int(t.Weekday())), nil
80
+}
81
+
82
+// Now returns the current time as a unix timestamp.
83
+func (r Time) Now(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
84
+ val := ti.Now().UnixNano()
85
+ r.value = val
86
+ return r, nil
87
+}
88
+
89
+// ParseDuration parses a go duration string to a time type.
90
+func (r Time) ParseDuration(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
91
+ var dur string
92
+ err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &dur)
93
+ if err != nil {
94
+ return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error())
95
+ }
96
+
97
+ if parsed, err := ti.ParseDuration(dur); err == nil {
98
+ val := parsed.Nanoseconds()
99
+ r.value = val
100
+ return r, nil
101
+ }
102
+
103
+ return starlark.None, fmt.Errorf("time.parse_duration: argument cannot be parsed as a valid go time duration")
104
+}
105
+
106
+// Add adds time to a time type.
107
+func (r Time) Add(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
108
+ var t Time
109
+ err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &t)
110
+ if err != nil {
111
+ return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error())
112
+ }
113
+
114
+ val := r.value + t.value
115
+ r.value = val
116
+ return r, nil
117
+}
118
+
119
+// Subtract adds time to a time type.
120
+func (r Time) Subtract(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
121
+ var t Time
122
+ err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &t)
123
+ if err != nil {
124
+ return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error())
125
+ }
126
+
127
+ val := r.value - t.value
128
+ r.value = val
129
+ return r, nil
130
+}
internal/caddyscript/matcherenv.go
new
+18
@@ -0,0 +1,18 @@
1
+package caddyscript
2
+
3
+import (
4
+ "net/http"
5
+
6
+ caddyscript "bitbucket.org/lightcodelabs/caddy2/internal/caddyscript/lib"
7
+ "go.starlark.net/starlark"
8
+)
9
+
10
+// MatcherEnv sets up the global context for the matcher caddyscript environment.
11
+func MatcherEnv(r *http.Request) starlark.StringDict {
12
+ env := make(starlark.StringDict)
13
+ env["req"] = caddyscript.HTTPRequest{Req: r}
14
+ env["time"] = caddyscript.Time{}
15
+ env["regexp"] = caddyscript.Regexp{}
16
+
17
+ return env
18
+}
modules/caddyhttp/caddyhttp.go
-2
@@ -32,9 +32,7 @@ type httpModuleConfig struct {
32
33
func (hc *httpModuleConfig) Run() error {
34
// TODO: Either prevent overlapping listeners on different servers, or combine them into one
35
-
35
// TODO: A way to loop requests back through, so have them start the matching over again, but keeping any mutations
37
-
36
for _, srv := range hc.Servers {
37
// set up the routes
38
for i, route := range srv.Routes {
modules/caddyhttp/matchers.go
+54
-8
@@ -1,10 +1,24 @@
1
package caddyhttp
2
3
import (
4
+ "log"
5
"net/http"
6
"strings"
7
8
"bitbucket.org/lightcodelabs/caddy2"
9
+ "bitbucket.org/lightcodelabs/caddy2/internal/caddyscript"
10
+ "go.starlark.net/starlark"
11
+)
12
+
13
+// TODO: Matchers should probably support regex of some sort... performance trade-offs?
14
+type (
15
+ matchHost []string
16
+ matchPath []string
17
+ matchMethod []string
18
+ matchQuery map[string][]string
19
+ matchHeader map[string][]string
20
+ matchProtocol string
21
+ matchScript string
22
)
23
24
func init() {
@@ -28,17 +42,47 @@ func init() {
42
Name: "http.matchers.header",
43
New: func() (interface{}, error) { return matchHeader{}, nil },
44
})
45
+ caddy2.RegisterModule(caddy2.Module{
46
+ Name: "http.matchers.protocol",
47
+ New: func() (interface{}, error) { return new(matchProtocol), nil },
48
+ })
49
+ caddy2.RegisterModule(caddy2.Module{
50
+ Name: "http.matchers.caddyscript",
51
+ New: func() (interface{}, error) { return new(matchScript), nil },
52
+ })
53
}
54
33
-// TODO: Matchers should probably support regex of some sort... performance trade-offs?
55
+func (m matchScript) Match(r *http.Request) bool {
56
+ input := string(m)
57
+ thread := new(starlark.Thread)
58
+ env := caddyscript.MatcherEnv(r)
59
+ val, err := starlark.Eval(thread, "", input, env)
60
+ if err != nil {
61
+ log.Printf("caddyscript for matcher is invalid: attempting to evaluate expression `%v` error `%v`", input, err)
62
+ return false
63
+ }
64
35
-type (
36
- matchHost []string
37
- matchPath []string
38
- matchMethod []string
39
- matchQuery map[string][]string
40
- matchHeader map[string][]string
41
-)
65
+ return val.String() == "True"
66
+}
67
+
68
+func (m matchProtocol) Match(r *http.Request) bool {
69
+ switch string(m) {
70
+ case "grpc":
71
+ if r.Header.Get("content-type") == "application/grpc" {
72
+ return true
73
+ }
74
+ case "https":
75
+ if r.TLS != nil {
76
+ return true
77
+ }
78
+ case "http":
79
+ if r.TLS == nil {
80
+ return true
81
+ }
82
+ }
83
+
84
+ return false
85
+}
86
87
func (m matchHost) Match(r *http.Request) bool {
88
for _, host := range m {
@@ -99,4 +143,6 @@ var (
143
_ RouteMatcher = matchMethod{}
144
_ RouteMatcher = matchQuery{}
145
_ RouteMatcher = matchHeader{}
146
+ _ RouteMatcher = new(matchProtocol)
147
+ _ RouteMatcher = new(matchScript)
148
)