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

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 +}