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

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