mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 13:43:47 +03:00
Rudimentary start of HTTP servers
This commit is contained in:
parent
859b5d7ea3
commit
86e2d1b0a4
6 changed files with 311 additions and 44 deletions
55
admin.go
55
admin.go
|
@ -17,8 +17,8 @@ var (
|
|||
cfgEndptSrvMu sync.Mutex
|
||||
)
|
||||
|
||||
// Start starts Caddy's administration endpoint.
|
||||
func Start(addr string) error {
|
||||
// StartAdmin starts Caddy's administration endpoint.
|
||||
func StartAdmin(addr string) error {
|
||||
cfgEndptSrvMu.Lock()
|
||||
defer cfgEndptSrvMu.Unlock()
|
||||
|
||||
|
@ -48,14 +48,8 @@ func Start(addr string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// AdminRoute represents a route for the admin endpoint.
|
||||
type AdminRoute struct {
|
||||
http.Handler
|
||||
Pattern string
|
||||
}
|
||||
|
||||
// Stop stops the API endpoint.
|
||||
func Stop() error {
|
||||
// StopAdmin stops the API endpoint.
|
||||
func StopAdmin() error {
|
||||
cfgEndptSrvMu.Lock()
|
||||
defer cfgEndptSrvMu.Unlock()
|
||||
|
||||
|
@ -73,6 +67,12 @@ func Stop() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// AdminRoute represents a route for the admin endpoint.
|
||||
type AdminRoute struct {
|
||||
http.Handler
|
||||
Pattern string
|
||||
}
|
||||
|
||||
func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
|
@ -92,39 +92,16 @@ func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// Load loads a configuration.
|
||||
// Load loads and starts a configuration.
|
||||
func Load(r io.Reader) error {
|
||||
gc := globalConfig{modules: make(map[string]interface{})}
|
||||
err := json.NewDecoder(r).Decode(&gc)
|
||||
var cfg Config
|
||||
err := json.NewDecoder(r).Decode(&cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding config: %v", err)
|
||||
}
|
||||
|
||||
for modName, rawMsg := range gc.Modules {
|
||||
mod, ok := modules[modName]
|
||||
if !ok {
|
||||
return fmt.Errorf("unrecognized module: %s", modName)
|
||||
}
|
||||
|
||||
if mod.New != nil {
|
||||
val, err := mod.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing module '%s': %v", modName, err)
|
||||
}
|
||||
err = json.Unmarshal(rawMsg, &val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding module config: %s: %v", modName, err)
|
||||
}
|
||||
gc.modules[modName] = val
|
||||
}
|
||||
err = Start(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type globalConfig struct {
|
||||
TestVal string `json:"testval"`
|
||||
Modules map[string]json.RawMessage `json:"modules"`
|
||||
TestArr []string `json:"test_arr"`
|
||||
modules map[string]interface{}
|
||||
}
|
||||
|
|
62
caddy.go
Normal file
62
caddy.go
Normal file
|
@ -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
|
||||
}
|
51
listeners.go
Normal file
51
listeners.go
Normal file
|
@ -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' 😉")
|
30
modules.go
30
modules.go
|
@ -1,7 +1,9 @@
|
|||
package caddy2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -104,6 +106,34 @@ func Modules() []string {
|
|||
return names
|
||||
}
|
||||
|
||||
// LoadModule decodes rawMsg into a new instance of mod and
|
||||
// returns the value. If mod.New() does not return a pointer
|
||||
// value, it is converted to one so that it is unmarshaled
|
||||
// into the underlying concrete type. If mod.New is nil, an
|
||||
// error is returned.
|
||||
func LoadModule(mod Module, rawMsg json.RawMessage) (interface{}, error) {
|
||||
if mod.New == nil {
|
||||
return nil, fmt.Errorf("no constructor")
|
||||
}
|
||||
|
||||
val, err := mod.New()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initializing module '%s': %v", mod.Name, err)
|
||||
}
|
||||
|
||||
// value must be a pointer for unmarshaling into concrete type
|
||||
if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr {
|
||||
val = reflect.New(rv.Type()).Elem().Addr().Interface()
|
||||
}
|
||||
|
||||
err = json.Unmarshal(rawMsg, &val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding module config: %s: %v", mod.Name, err)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
var (
|
||||
modules = make(map[string]Module)
|
||||
modulesMu sync.Mutex
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
package caddyhttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/lightcodelabs/caddy2"
|
||||
)
|
||||
|
@ -9,7 +15,7 @@ import (
|
|||
func init() {
|
||||
err := caddy2.RegisterModule(caddy2.Module{
|
||||
Name: "http",
|
||||
New: func() (interface{}, error) { return httpModuleConfig{}, nil },
|
||||
New: func() (interface{}, error) { return new(httpModuleConfig), nil },
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -20,8 +26,69 @@ type httpModuleConfig struct {
|
|||
Servers map[string]httpServerConfig `json:"servers"`
|
||||
}
|
||||
|
||||
type httpServerConfig struct {
|
||||
Listen []string `json:"listen"`
|
||||
ReadTimeout string `json:"read_timeout"`
|
||||
ReadHeaderTimeout string `json:"read_header_timeout"`
|
||||
func (hc *httpModuleConfig) Run() error {
|
||||
fmt.Printf("RUNNING: %#v\n", hc)
|
||||
|
||||
for _, srv := range hc.Servers {
|
||||
s := http.Server{
|
||||
ReadTimeout: time.Duration(srv.ReadTimeout),
|
||||
ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout),
|
||||
}
|
||||
|
||||
for _, lnAddr := range srv.Listen {
|
||||
proto, addrs, err := parseListenAddr(lnAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing listen address '%s': %v", lnAddr, err)
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ln, err := caddy2.Listen(proto, addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: listening on %s: %v", proto, addr, err)
|
||||
}
|
||||
go s.Serve(ln)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseListenAddr(a string) (proto string, addrs []string, err error) {
|
||||
proto = "tcp"
|
||||
if idx := strings.Index(a, ":::"); idx >= 0 {
|
||||
proto = strings.ToLower(strings.TrimSpace(a[:idx]))
|
||||
a = a[idx+3:]
|
||||
}
|
||||
var host, port string
|
||||
host, port, err = net.SplitHostPort(a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ports := strings.SplitN(port, "-", 2)
|
||||
if len(ports) == 1 {
|
||||
ports = append(ports, ports[0])
|
||||
}
|
||||
var start, end int
|
||||
start, err = strconv.Atoi(ports[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
end, err = strconv.Atoi(ports[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if end < start {
|
||||
err = fmt.Errorf("end port must be greater than start port")
|
||||
return
|
||||
}
|
||||
for p := start; p <= end; p++ {
|
||||
addrs = append(addrs, net.JoinHostPort(host, fmt.Sprintf("%d", p)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type httpServerConfig struct {
|
||||
Listen []string `json:"listen"`
|
||||
ReadTimeout caddy2.Duration `json:"read_timeout"`
|
||||
ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
|
||||
}
|
||||
|
|
80
modules/caddyhttp/caddyhttp_test.go
Normal file
80
modules/caddyhttp/caddyhttp_test.go
Normal file
|
@ -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 a new issue