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
Rudimentary start of HTTP servers
Matthew Holt committed
Mar 26, 2019 at 15:45 UTC
86e2d1b0a48fbd84590291969611f1870471c3e0
6 files changed
+310
-43
admin.go
+16
-39
@@ -17,8 +17,8 @@ var (
17
cfgEndptSrvMu sync.Mutex
18
)
19
20
-// Start starts Caddy's administration endpoint.
21
-func Start(addr string) error {
20
+// StartAdmin starts Caddy's administration endpoint.
21
+func StartAdmin(addr string) error {
22
cfgEndptSrvMu.Lock()
23
defer cfgEndptSrvMu.Unlock()
24
@@ -48,14 +48,8 @@ func Start(addr string) error {
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 {
51
+// StopAdmin stops the API endpoint.
52
+func StopAdmin() error {
53
cfgEndptSrvMu.Lock()
54
defer cfgEndptSrvMu.Unlock()
55
@@ -73,6 +67,12 @@ func Stop() error {
67
return nil
68
}
69
70
+// AdminRoute represents a route for the admin endpoint.
71
+type AdminRoute struct {
72
+ http.Handler
73
+ Pattern string
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)
@@ -92,39 +92,16 @@ func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
92
}
93
}
94
95
-// Load loads a configuration.
95
+// Load loads and starts a configuration.
96
func Load(r io.Reader) error {
97
- gc := globalConfig{modules: make(map[string]interface{})}
98
- err := json.NewDecoder(r).Decode(&gc)
97
+ var cfg Config
98
+ err := json.NewDecoder(r).Decode(&cfg)
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
- }
102
+ err = Start(cfg)
103
+ if err != nil {
104
+ return fmt.Errorf("starting: %v", err)
105
}
121
-
106
return nil
107
}
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
-}
caddy.go
new
+62
@@ -0,0 +1,62 @@
1
+package caddy2
2
+
3
+import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "strings"
7
+ "time"
8
+)
9
+
10
+// Start runs Caddy with the given config.
11
+func Start(cfg Config) error {
12
+ cfg.runners = make(map[string]Runner)
13
+
14
+ for modName, rawMsg := range cfg.Modules {
15
+ mod, ok := modules[modName]
16
+ if !ok {
17
+ return fmt.Errorf("unrecognized module: %s", modName)
18
+ }
19
+ val, err := LoadModule(mod, rawMsg)
20
+ if err != nil {
21
+ return fmt.Errorf("loading module '%s': %v", modName, err)
22
+ }
23
+ cfg.runners[modName] = val.(Runner)
24
+ }
25
+
26
+ for name, r := range cfg.runners {
27
+ err := r.Run()
28
+ if err != nil {
29
+ return fmt.Errorf("%s module: %v", name, err)
30
+ }
31
+ }
32
+
33
+ return nil
34
+}
35
+
36
+// Runner is a thing that Caddy runs.
37
+type Runner interface {
38
+ Run() error
39
+}
40
+
41
+// Config represents a Caddy configuration.
42
+type Config struct {
43
+ TestVal string `json:"testval"`
44
+ Modules map[string]json.RawMessage `json:"modules"`
45
+ runners map[string]Runner
46
+}
47
+
48
+// Duration is a JSON-string-unmarshable duration type.
49
+type Duration time.Duration
50
+
51
+// UnmarshalJSON satisfies json.Unmarshaler.
52
+func (d *Duration) UnmarshalJSON(b []byte) (err error) {
53
+ dd, err := time.ParseDuration(strings.Trim(string(b), `"`))
54
+ cd := Duration(dd)
55
+ d = &cd
56
+ return
57
+}
58
+
59
+// MarshalJSON satisfies json.Marshaler.
60
+func (d Duration) MarshalJSON() (b []byte, err error) {
61
+ return []byte(fmt.Sprintf(`"%s"`, time.Duration(d).String())), nil
62
+}
listeners.go
new
+51
@@ -0,0 +1,51 @@
1
+package caddy2
2
+
3
+import (
4
+ "fmt"
5
+ "net"
6
+ "sync/atomic"
7
+)
8
+
9
+// Listen returns a listener suitable for use in a Caddy module.
10
+func Listen(proto, addr string) (net.Listener, error) {
11
+ ln, err := net.Listen(proto, addr)
12
+ if err != nil {
13
+ return nil, err
14
+ }
15
+ return &fakeCloseListener{Listener: ln}, nil
16
+}
17
+
18
+// fakeCloseListener's Close() method is a no-op. This allows
19
+// stopping servers that are using the listener without giving
20
+// up the socket; thus, servers become hot-swappable while the
21
+// listener remains running. Listeners should be re-wrapped in
22
+// a new fakeCloseListener each time the listener is reused.
23
+type fakeCloseListener struct {
24
+ closed int32
25
+ net.Listener
26
+}
27
+
28
+// Accept accepts connections until Close() is called.
29
+func (fcl *fakeCloseListener) Accept() (net.Conn, error) {
30
+ if atomic.LoadInt32(&fcl.closed) == 1 {
31
+ return nil, ErrSwappingServers
32
+ }
33
+ return fcl.Listener.Accept()
34
+}
35
+
36
+// Close stops accepting new connections, but does not
37
+// actually close the underlying listener.
38
+func (fcl *fakeCloseListener) Close() error {
39
+ atomic.StoreInt32(&fcl.closed, 1)
40
+ return nil
41
+}
42
+
43
+// CloseUnderlying actually closes the underlying listener.
44
+func (fcl *fakeCloseListener) CloseUnderlying() error {
45
+ return fcl.Listener.Close()
46
+}
47
+
48
+// ErrSwappingServers is returned by fakeCloseListener when
49
+// Close() is called, indicating that it is pretending to
50
+// be closed so that the server using it can terminate.
51
+var ErrSwappingServers = fmt.Errorf("listener 'closed' 😉")
modules.go
+30
@@ -1,7 +1,9 @@
1
package caddy2
2
3
import (
4
+ "encoding/json"
5
"fmt"
6
+ "reflect"
7
"sort"
8
"strings"
9
"sync"
@@ -104,6 +106,34 @@ func Modules() []string {
106
return names
107
}
108
109
+// LoadModule decodes rawMsg into a new instance of mod and
110
+// returns the value. If mod.New() does not return a pointer
111
+// value, it is converted to one so that it is unmarshaled
112
+// into the underlying concrete type. If mod.New is nil, an
113
+// error is returned.
114
+func LoadModule(mod Module, rawMsg json.RawMessage) (interface{}, error) {
115
+ if mod.New == nil {
116
+ return nil, fmt.Errorf("no constructor")
117
+ }
118
+
119
+ val, err := mod.New()
120
+ if err != nil {
121
+ return nil, fmt.Errorf("initializing module '%s': %v", mod.Name, err)
122
+ }
123
+
124
+ // value must be a pointer for unmarshaling into concrete type
125
+ if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr {
126
+ val = reflect.New(rv.Type()).Elem().Addr().Interface()
127
+ }
128
+
129
+ err = json.Unmarshal(rawMsg, &val)
130
+ if err != nil {
131
+ return nil, fmt.Errorf("decoding module config: %s: %v", mod.Name, err)
132
+ }
133
+
134
+ return val, nil
135
+}
136
+
137
var (
138
modules = make(map[string]Module)
139
modulesMu sync.Mutex
modules/caddyhttp/caddyhttp.go
+71
-4
@@ -1,7 +1,13 @@
1
package caddyhttp
2
3
import (
4
+ "fmt"
5
"log"
6
+ "net"
7
+ "net/http"
8
+ "strconv"
9
+ "strings"
10
+ "time"
11
12
"bitbucket.org/lightcodelabs/caddy2"
13
)
@@ -9,7 +15,7 @@ import (
15
func init() {
16
err := caddy2.RegisterModule(caddy2.Module{
17
Name: "http",
12
- New: func() (interface{}, error) { return httpModuleConfig{}, nil },
18
+ New: func() (interface{}, error) { return new(httpModuleConfig), nil },
19
})
20
if err != nil {
21
log.Fatal(err)
@@ -20,8 +26,69 @@ type httpModuleConfig struct {
26
Servers map[string]httpServerConfig `json:"servers"`
27
}
28
29
+func (hc *httpModuleConfig) Run() error {
30
+ fmt.Printf("RUNNING: %#v\n", hc)
31
+
32
+ for _, srv := range hc.Servers {
33
+ s := http.Server{
34
+ ReadTimeout: time.Duration(srv.ReadTimeout),
35
+ ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout),
36
+ }
37
+
38
+ for _, lnAddr := range srv.Listen {
39
+ proto, addrs, err := parseListenAddr(lnAddr)
40
+ if err != nil {
41
+ return fmt.Errorf("parsing listen address '%s': %v", lnAddr, err)
42
+ }
43
+ for _, addr := range addrs {
44
+ ln, err := caddy2.Listen(proto, addr)
45
+ if err != nil {
46
+ return fmt.Errorf("%s: listening on %s: %v", proto, addr, err)
47
+ }
48
+ go s.Serve(ln)
49
+ }
50
+ }
51
+ }
52
+
53
+ return nil
54
+}
55
+
56
+func parseListenAddr(a string) (proto string, addrs []string, err error) {
57
+ proto = "tcp"
58
+ if idx := strings.Index(a, ":::"); idx >= 0 {
59
+ proto = strings.ToLower(strings.TrimSpace(a[:idx]))
60
+ a = a[idx+3:]
61
+ }
62
+ var host, port string
63
+ host, port, err = net.SplitHostPort(a)
64
+ if err != nil {
65
+ return
66
+ }
67
+ ports := strings.SplitN(port, "-", 2)
68
+ if len(ports) == 1 {
69
+ ports = append(ports, ports[0])
70
+ }
71
+ var start, end int
72
+ start, err = strconv.Atoi(ports[0])
73
+ if err != nil {
74
+ return
75
+ }
76
+ end, err = strconv.Atoi(ports[1])
77
+ if err != nil {
78
+ return
79
+ }
80
+ if end < start {
81
+ err = fmt.Errorf("end port must be greater than start port")
82
+ return
83
+ }
84
+ for p := start; p <= end; p++ {
85
+ addrs = append(addrs, net.JoinHostPort(host, fmt.Sprintf("%d", p)))
86
+ }
87
+ return
88
+}
89
+
90
type httpServerConfig struct {
24
- Listen []string `json:"listen"`
25
- ReadTimeout string `json:"read_timeout"`
26
- ReadHeaderTimeout string `json:"read_header_timeout"`
91
+ Listen []string `json:"listen"`
92
+ ReadTimeout caddy2.Duration `json:"read_timeout"`
93
+ ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
94
}
modules/caddyhttp/caddyhttp_test.go
new
+80
@@ -0,0 +1,80 @@
1
+package caddyhttp
2
+
3
+import (
4
+ "reflect"
5
+ "testing"
6
+)
7
+
8
+func TestParseListenerAddr(t *testing.T) {
9
+ for i, tc := range []struct {
10
+ input string
11
+ expectProto string
12
+ expectAddrs []string
13
+ expectErr bool
14
+ }{
15
+ {
16
+ input: "",
17
+ expectProto: "tcp",
18
+ expectErr: true,
19
+ },
20
+ {
21
+ input: ":",
22
+ expectProto: "tcp",
23
+ expectErr: true,
24
+ },
25
+ {
26
+ input: ":1234",
27
+ expectProto: "tcp",
28
+ expectAddrs: []string{":1234"},
29
+ },
30
+ {
31
+ input: "tcp::::1234",
32
+ expectProto: "tcp",
33
+ expectAddrs: []string{":1234"},
34
+ },
35
+ {
36
+ input: "tcp6::::1234",
37
+ expectProto: "tcp6",
38
+ expectAddrs: []string{":1234"},
39
+ },
40
+ {
41
+ input: "tcp4:::localhost:1234",
42
+ expectProto: "tcp4",
43
+ expectAddrs: []string{"localhost:1234"},
44
+ },
45
+ {
46
+ input: "unix:::localhost:1234-1236",
47
+ expectProto: "unix",
48
+ expectAddrs: []string{"localhost:1234", "localhost:1235", "localhost:1236"},
49
+ },
50
+ {
51
+ input: "localhost:1234-1234",
52
+ expectProto: "tcp",
53
+ expectAddrs: []string{"localhost:1234"},
54
+ },
55
+ {
56
+ input: "localhost:2-1",
57
+ expectProto: "tcp",
58
+ expectErr: true,
59
+ },
60
+ {
61
+ input: "localhost:0",
62
+ expectProto: "tcp",
63
+ expectAddrs: []string{"localhost:0"},
64
+ },
65
+ } {
66
+ actualProto, actualAddrs, err := parseListenAddr(tc.input)
67
+ if tc.expectErr && err == nil {
68
+ t.Errorf("Test %d: Expected error but got: %v", i, err)
69
+ }
70
+ if !tc.expectErr && err != nil {
71
+ t.Errorf("Test %d: Expected no error but got: %v", i, err)
72
+ }
73
+ if actualProto != tc.expectProto {
74
+ t.Errorf("Test %d: Expeceted protocol '%s' but got '%s'", i, tc.expectProto, actualProto)
75
+ }
76
+ if !reflect.DeepEqual(tc.expectAddrs, actualAddrs) {
77
+ t.Errorf("Test %d: Expected addresses %v but got %v", i, tc.expectAddrs, actualAddrs)
78
+ }
79
+ }
80
+}