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
Initial commit
Matthew Holt committed
Mar 26, 2019 at 12:00 UTC
859b5d7ea3b8f660ac68d9aea5a53d25a9a7422c
6 files changed
+360
.gitignore
new
+1
@@ -0,0 +1 @@
1
+_gitignore/
\ No newline at end of file
admin.go
new
+130
@@ -0,0 +1,130 @@
1
+package caddy2
2
+
3
+import (
4
+ "context"
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "log"
9
+ "net"
10
+ "net/http"
11
+ "strings"
12
+ "sync"
13
+)
14
+
15
+var (
16
+ cfgEndptSrv *http.Server
17
+ cfgEndptSrvMu sync.Mutex
18
+)
19
+
20
+// Start starts Caddy's administration endpoint.
21
+func Start(addr string) error {
22
+ cfgEndptSrvMu.Lock()
23
+ defer cfgEndptSrvMu.Unlock()
24
+
25
+ ln, err := net.Listen("tcp", addr)
26
+ if err != nil {
27
+ return err
28
+ }
29
+
30
+ mux := http.NewServeMux()
31
+ mux.HandleFunc("/load", handleLoadConfig)
32
+
33
+ for _, m := range GetModules("admin") {
34
+ moduleValue, err := m.New()
35
+ if err != nil {
36
+ return fmt.Errorf("initializing module '%s': %v", m.Name, err)
37
+ }
38
+ route := moduleValue.(AdminRoute)
39
+ mux.Handle(route.Pattern, route)
40
+ }
41
+
42
+ cfgEndptSrv = &http.Server{
43
+ Handler: mux,
44
+ }
45
+
46
+ go cfgEndptSrv.Serve(ln)
47
+
48
+ return nil
49
+}
50
+
51
+// AdminRoute represents a route for the admin endpoint.
52
+type AdminRoute struct {
53
+ http.Handler
54
+ Pattern string
55
+}
56
+
57
+// Stop stops the API endpoint.
58
+func Stop() error {
59
+ cfgEndptSrvMu.Lock()
60
+ defer cfgEndptSrvMu.Unlock()
61
+
62
+ if cfgEndptSrv == nil {
63
+ return fmt.Errorf("no server")
64
+ }
65
+
66
+ err := cfgEndptSrv.Shutdown(context.Background()) // TODO
67
+ if err != nil {
68
+ return fmt.Errorf("shutting down server: %v", err)
69
+ }
70
+
71
+ cfgEndptSrv = nil
72
+
73
+ return nil
74
+}
75
+
76
+func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
77
+ if r.Method != "POST" {
78
+ http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
79
+ return
80
+ }
81
+
82
+ if !strings.Contains(r.Header.Get("Content-Type"), "/json") {
83
+ http.Error(w, "unacceptable Content-Type", http.StatusBadRequest)
84
+ return
85
+ }
86
+
87
+ err := Load(r.Body)
88
+ if err != nil {
89
+ log.Printf("[ADMIN][ERROR] loading config: %v", err)
90
+ http.Error(w, err.Error(), http.StatusBadRequest)
91
+ return
92
+ }
93
+}
94
+
95
+// Load loads a configuration.
96
+func Load(r io.Reader) error {
97
+ gc := globalConfig{modules: make(map[string]interface{})}
98
+ err := json.NewDecoder(r).Decode(&gc)
99
+ if err != nil {
100
+ return fmt.Errorf("decoding config: %v", err)
101
+ }
102
+
103
+ for modName, rawMsg := range gc.Modules {
104
+ mod, ok := modules[modName]
105
+ if !ok {
106
+ return fmt.Errorf("unrecognized module: %s", modName)
107
+ }
108
+
109
+ if mod.New != nil {
110
+ val, err := mod.New()
111
+ if err != nil {
112
+ return fmt.Errorf("initializing module '%s': %v", modName, err)
113
+ }
114
+ err = json.Unmarshal(rawMsg, &val)
115
+ if err != nil {
116
+ return fmt.Errorf("decoding module config: %s: %v", modName, err)
117
+ }
118
+ gc.modules[modName] = val
119
+ }
120
+ }
121
+
122
+ return nil
123
+}
124
+
125
+type globalConfig struct {
126
+ TestVal string `json:"testval"`
127
+ Modules map[string]json.RawMessage `json:"modules"`
128
+ TestArr []string `json:"test_arr"`
129
+ modules map[string]interface{}
130
+}
cmd/caddy2/main.go
new
+21
@@ -0,0 +1,21 @@
1
+package main
2
+
3
+import (
4
+ "log"
5
+
6
+ "bitbucket.org/lightcodelabs/caddy2"
7
+
8
+ // this is where modules get plugged in
9
+ _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
10
+ _ "bitbucket.org/lightcodelabs/dynamicconfig"
11
+)
12
+
13
+func main() {
14
+ err := caddy2.Start("127.0.0.1:1234")
15
+ if err != nil {
16
+ log.Fatal(err)
17
+ }
18
+ defer caddy2.Stop()
19
+
20
+ select {}
21
+}
modules.go
new
+110
@@ -0,0 +1,110 @@
1
+package caddy2
2
+
3
+import (
4
+ "fmt"
5
+ "sort"
6
+ "strings"
7
+ "sync"
8
+)
9
+
10
+// Module is a module.
11
+type Module struct {
12
+ Name string
13
+ New func() (interface{}, error)
14
+}
15
+
16
+func (m Module) String() string { return m.Name }
17
+
18
+// RegisterModule registers a module.
19
+func RegisterModule(mod Module) error {
20
+ modulesMu.Lock()
21
+ defer modulesMu.Unlock()
22
+
23
+ if _, ok := modules[mod.Name]; ok {
24
+ return fmt.Errorf("module already registered: %s", mod.Name)
25
+ }
26
+ modules[mod.Name] = mod
27
+ return nil
28
+}
29
+
30
+// GetModule returns a module by name.
31
+func GetModule(name string) (Module, error) {
32
+ modulesMu.Lock()
33
+ defer modulesMu.Unlock()
34
+
35
+ m, ok := modules[name]
36
+ if !ok {
37
+ return Module{}, fmt.Errorf("module not registered: %s", name)
38
+ }
39
+ return m, nil
40
+}
41
+
42
+// GetModules returns all modules in the given scope/namespace.
43
+// For example, a scope of "foo" returns modules named "foo.bar",
44
+// "foo.lor", but not "bar", "foo.bar.lor", etc. An empty scope
45
+// returns top-level modules, for example "foo" or "bar". Partial
46
+// scopes are not matched (i.e. scope "foo.ba" does not match
47
+// name "foo.bar").
48
+//
49
+// Because modules are registered to a map, the returned slice
50
+// will be sorted to keep it deterministic.
51
+func GetModules(scope string) []Module {
52
+ modulesMu.Lock()
53
+ defer modulesMu.Unlock()
54
+
55
+ scopeParts := strings.Split(scope, ".")
56
+
57
+ // handle the special case of an empty scope, which
58
+ // should match only the top-level modules
59
+ if len(scopeParts) == 1 && scopeParts[0] == "" {
60
+ scopeParts = []string{}
61
+ }
62
+
63
+ var mods []Module
64
+iterateModules:
65
+ for name, m := range modules {
66
+ modParts := strings.Split(name, ".")
67
+
68
+ // match only the next level of nesting
69
+ if len(modParts) != len(scopeParts)+1 {
70
+ continue
71
+ }
72
+
73
+ // specified parts must be exact matches
74
+ for i := range scopeParts {
75
+ if modParts[i] != scopeParts[i] {
76
+ continue iterateModules
77
+ }
78
+ }
79
+
80
+ mods = append(mods, m)
81
+ }
82
+
83
+ // make return value deterministic
84
+ sort.Slice(mods, func(i, j int) bool {
85
+ return mods[i].Name < mods[j].Name
86
+ })
87
+
88
+ return mods
89
+}
90
+
91
+// Modules returns the names of all registered modules
92
+// in ascending lexicographical order.
93
+func Modules() []string {
94
+ modulesMu.Lock()
95
+ defer modulesMu.Unlock()
96
+
97
+ var names []string
98
+ for name := range modules {
99
+ names = append(names, name)
100
+ }
101
+
102
+ sort.Strings(names)
103
+
104
+ return names
105
+}
106
+
107
+var (
108
+ modules = make(map[string]Module)
109
+ modulesMu sync.Mutex
110
+)
modules/caddyhttp/caddyhttp.go
new
+27
@@ -0,0 +1,27 @@
1
+package caddyhttp
2
+
3
+import (
4
+ "log"
5
+
6
+ "bitbucket.org/lightcodelabs/caddy2"
7
+)
8
+
9
+func init() {
10
+ err := caddy2.RegisterModule(caddy2.Module{
11
+ Name: "http",
12
+ New: func() (interface{}, error) { return httpModuleConfig{}, nil },
13
+ })
14
+ if err != nil {
15
+ log.Fatal(err)
16
+ }
17
+}
18
+
19
+type httpModuleConfig struct {
20
+ Servers map[string]httpServerConfig `json:"servers"`
21
+}
22
+
23
+type httpServerConfig struct {
24
+ Listen []string `json:"listen"`
25
+ ReadTimeout string `json:"read_timeout"`
26
+ ReadHeaderTimeout string `json:"read_header_timeout"`
27
+}
modules_test.go
new
+71
@@ -0,0 +1,71 @@
1
+package caddy2
2
+
3
+import (
4
+ "reflect"
5
+ "testing"
6
+)
7
+
8
+func TestGetModules(t *testing.T) {
9
+ modulesMu.Lock()
10
+ modules = map[string]Module{
11
+ "a": {Name: "a"},
12
+ "a.b": {Name: "a.b"},
13
+ "a.b.c": {Name: "a.b.c"},
14
+ "a.b.cd": {Name: "a.b.cd"},
15
+ "a.c": {Name: "a.c"},
16
+ "a.d": {Name: "a.d"},
17
+ "b": {Name: "b"},
18
+ "b.a": {Name: "b.a"},
19
+ "b.b": {Name: "b.b"},
20
+ "b.a.c": {Name: "b.a.c"},
21
+ "c": {Name: "c"},
22
+ }
23
+ modulesMu.Unlock()
24
+
25
+ for i, tc := range []struct {
26
+ input string
27
+ expect []Module
28
+ }{
29
+ {
30
+ input: "",
31
+ expect: []Module{
32
+ {Name: "a"},
33
+ {Name: "b"},
34
+ {Name: "c"},
35
+ },
36
+ },
37
+ {
38
+ input: "a",
39
+ expect: []Module{
40
+ {Name: "a.b"},
41
+ {Name: "a.c"},
42
+ {Name: "a.d"},
43
+ },
44
+ },
45
+ {
46
+ input: "a.b",
47
+ expect: []Module{
48
+ {Name: "a.b.c"},
49
+ {Name: "a.b.cd"},
50
+ },
51
+ },
52
+ {
53
+ input: "a.b.c",
54
+ },
55
+ {
56
+ input: "b",
57
+ expect: []Module{
58
+ {Name: "b.a"},
59
+ {Name: "b.b"},
60
+ },
61
+ },
62
+ {
63
+ input: "asdf",
64
+ },
65
+ } {
66
+ actual := GetModules(tc.input)
67
+ if !reflect.DeepEqual(actual, tc.expect) {
68
+ t.Errorf("Test %d: Expected %v but got %v", i, tc.expect, actual)
69
+ }
70
+ }
71
+}