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