6 changed files with 310 additions and 43 deletions
			
			
		| @ -0,0 +1,62 @@ | |||||
|  | package caddy2 | ||||
|  | 
 | ||||
|  | import ( | ||||
|  | 	"encoding/json" | ||||
|  | 	"fmt" | ||||
|  | 	"strings" | ||||
|  | 	"time" | ||||
|  | ) | ||||
|  | 
 | ||||
|  | // Start runs Caddy with the given config.
 | ||||
|  | func Start(cfg Config) error { | ||||
|  | 	cfg.runners = make(map[string]Runner) | ||||
|  | 
 | ||||
|  | 	for modName, rawMsg := range cfg.Modules { | ||||
|  | 		mod, ok := modules[modName] | ||||
|  | 		if !ok { | ||||
|  | 			return fmt.Errorf("unrecognized module: %s", modName) | ||||
|  | 		} | ||||
|  | 		val, err := LoadModule(mod, rawMsg) | ||||
|  | 		if err != nil { | ||||
|  | 			return fmt.Errorf("loading module '%s': %v", modName, err) | ||||
|  | 		} | ||||
|  | 		cfg.runners[modName] = val.(Runner) | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	for name, r := range cfg.runners { | ||||
|  | 		err := r.Run() | ||||
|  | 		if err != nil { | ||||
|  | 			return fmt.Errorf("%s module: %v", name, err) | ||||
|  | 		} | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	return nil | ||||
|  | } | ||||
|  | 
 | ||||
|  | // Runner is a thing that Caddy runs.
 | ||||
|  | type Runner interface { | ||||
|  | 	Run() error | ||||
|  | } | ||||
|  | 
 | ||||
|  | // Config represents a Caddy configuration.
 | ||||
|  | type Config struct { | ||||
|  | 	TestVal string                     `json:"testval"` | ||||
|  | 	Modules map[string]json.RawMessage `json:"modules"` | ||||
|  | 	runners map[string]Runner | ||||
|  | } | ||||
|  | 
 | ||||
|  | // Duration is a JSON-string-unmarshable duration type.
 | ||||
|  | type Duration time.Duration | ||||
|  | 
 | ||||
|  | // UnmarshalJSON satisfies json.Unmarshaler.
 | ||||
|  | func (d *Duration) UnmarshalJSON(b []byte) (err error) { | ||||
|  | 	dd, err := time.ParseDuration(strings.Trim(string(b), `"`)) | ||||
|  | 	cd := Duration(dd) | ||||
|  | 	d = &cd | ||||
|  | 	return | ||||
|  | } | ||||
|  | 
 | ||||
|  | // MarshalJSON satisfies json.Marshaler.
 | ||||
|  | func (d Duration) MarshalJSON() (b []byte, err error) { | ||||
|  | 	return []byte(fmt.Sprintf(`"%s"`, time.Duration(d).String())), nil | ||||
|  | } | ||||
| @ -0,0 +1,51 @@ | |||||
|  | package caddy2 | ||||
|  | 
 | ||||
|  | import ( | ||||
|  | 	"fmt" | ||||
|  | 	"net" | ||||
|  | 	"sync/atomic" | ||||
|  | ) | ||||
|  | 
 | ||||
|  | // Listen returns a listener suitable for use in a Caddy module.
 | ||||
|  | func Listen(proto, addr string) (net.Listener, error) { | ||||
|  | 	ln, err := net.Listen(proto, addr) | ||||
|  | 	if err != nil { | ||||
|  | 		return nil, err | ||||
|  | 	} | ||||
|  | 	return &fakeCloseListener{Listener: ln}, nil | ||||
|  | } | ||||
|  | 
 | ||||
|  | // fakeCloseListener's Close() method is a no-op. This allows
 | ||||
|  | // stopping servers that are using the listener without giving
 | ||||
|  | // up the socket; thus, servers become hot-swappable while the
 | ||||
|  | // listener remains running. Listeners should be re-wrapped in
 | ||||
|  | // a new fakeCloseListener each time the listener is reused.
 | ||||
|  | type fakeCloseListener struct { | ||||
|  | 	closed int32 | ||||
|  | 	net.Listener | ||||
|  | } | ||||
|  | 
 | ||||
|  | // Accept accepts connections until Close() is called.
 | ||||
|  | func (fcl *fakeCloseListener) Accept() (net.Conn, error) { | ||||
|  | 	if atomic.LoadInt32(&fcl.closed) == 1 { | ||||
|  | 		return nil, ErrSwappingServers | ||||
|  | 	} | ||||
|  | 	return fcl.Listener.Accept() | ||||
|  | } | ||||
|  | 
 | ||||
|  | // Close stops accepting new connections, but does not
 | ||||
|  | // actually close the underlying listener.
 | ||||
|  | func (fcl *fakeCloseListener) Close() error { | ||||
|  | 	atomic.StoreInt32(&fcl.closed, 1) | ||||
|  | 	return nil | ||||
|  | } | ||||
|  | 
 | ||||
|  | // CloseUnderlying actually closes the underlying listener.
 | ||||
|  | func (fcl *fakeCloseListener) CloseUnderlying() error { | ||||
|  | 	return fcl.Listener.Close() | ||||
|  | } | ||||
|  | 
 | ||||
|  | // ErrSwappingServers is returned by fakeCloseListener when
 | ||||
|  | // Close() is called, indicating that it is pretending to
 | ||||
|  | // be closed so that the server using it can terminate.
 | ||||
|  | var ErrSwappingServers = fmt.Errorf("listener 'closed' 😉") | ||||
| @ -0,0 +1,80 @@ | |||||
|  | package caddyhttp | ||||
|  | 
 | ||||
|  | import ( | ||||
|  | 	"reflect" | ||||
|  | 	"testing" | ||||
|  | ) | ||||
|  | 
 | ||||
|  | func TestParseListenerAddr(t *testing.T) { | ||||
|  | 	for i, tc := range []struct { | ||||
|  | 		input       string | ||||
|  | 		expectProto string | ||||
|  | 		expectAddrs []string | ||||
|  | 		expectErr   bool | ||||
|  | 	}{ | ||||
|  | 		{ | ||||
|  | 			input:       "", | ||||
|  | 			expectProto: "tcp", | ||||
|  | 			expectErr:   true, | ||||
|  | 		}, | ||||
|  | 		{ | ||||
|  | 			input:       ":", | ||||
|  | 			expectProto: "tcp", | ||||
|  | 			expectErr:   true, | ||||
|  | 		}, | ||||
|  | 		{ | ||||
|  | 			input:       ":1234", | ||||
|  | 			expectProto: "tcp", | ||||
|  | 			expectAddrs: []string{":1234"}, | ||||
|  | 		}, | ||||
|  | 		{ | ||||
|  | 			input:       "tcp::::1234", | ||||
|  | 			expectProto: "tcp", | ||||
|  | 			expectAddrs: []string{":1234"}, | ||||
|  | 		}, | ||||
|  | 		{ | ||||
|  | 			input:       "tcp6::::1234", | ||||
|  | 			expectProto: "tcp6", | ||||
|  | 			expectAddrs: []string{":1234"}, | ||||
|  | 		}, | ||||
|  | 		{ | ||||
|  | 			input:       "tcp4:::localhost:1234", | ||||
|  | 			expectProto: "tcp4", | ||||
|  | 			expectAddrs: []string{"localhost:1234"}, | ||||
|  | 		}, | ||||
|  | 		{ | ||||
|  | 			input:       "unix:::localhost:1234-1236", | ||||
|  | 			expectProto: "unix", | ||||
|  | 			expectAddrs: []string{"localhost:1234", "localhost:1235", "localhost:1236"}, | ||||
|  | 		}, | ||||
|  | 		{ | ||||
|  | 			input:       "localhost:1234-1234", | ||||
|  | 			expectProto: "tcp", | ||||
|  | 			expectAddrs: []string{"localhost:1234"}, | ||||
|  | 		}, | ||||
|  | 		{ | ||||
|  | 			input:       "localhost:2-1", | ||||
|  | 			expectProto: "tcp", | ||||
|  | 			expectErr:   true, | ||||
|  | 		}, | ||||
|  | 		{ | ||||
|  | 			input:       "localhost:0", | ||||
|  | 			expectProto: "tcp", | ||||
|  | 			expectAddrs: []string{"localhost:0"}, | ||||
|  | 		}, | ||||
|  | 	} { | ||||
|  | 		actualProto, actualAddrs, err := parseListenAddr(tc.input) | ||||
|  | 		if tc.expectErr && err == nil { | ||||
|  | 			t.Errorf("Test %d: Expected error but got: %v", i, err) | ||||
|  | 		} | ||||
|  | 		if !tc.expectErr && err != nil { | ||||
|  | 			t.Errorf("Test %d: Expected no error but got: %v", i, err) | ||||
|  | 		} | ||||
|  | 		if actualProto != tc.expectProto { | ||||
|  | 			t.Errorf("Test %d: Expeceted protocol '%s' but got '%s'", i, tc.expectProto, actualProto) | ||||
|  | 		} | ||||
|  | 		if !reflect.DeepEqual(tc.expectAddrs, actualAddrs) { | ||||
|  | 			t.Errorf("Test %d: Expected addresses %v but got %v", i, tc.expectAddrs, actualAddrs) | ||||
|  | 		} | ||||
|  | 	} | ||||
|  | } | ||||
					Loading…
					
					
				
		Reference in new issue