@marcel91200 / caddy Mirror · read-only / commits / 27ecc7f3
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

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 )