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
|
cfgEndptSrvMu sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
// Start starts Caddy's administration endpoint.
|
// StartAdmin starts Caddy's administration endpoint.
|
||||||
func Start(addr string) error {
|
func StartAdmin(addr string) error {
|
||||||
cfgEndptSrvMu.Lock()
|
cfgEndptSrvMu.Lock()
|
||||||
defer cfgEndptSrvMu.Unlock()
|
defer cfgEndptSrvMu.Unlock()
|
||||||
|
|
||||||
|
@ -48,14 +48,8 @@ func Start(addr string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminRoute represents a route for the admin endpoint.
|
// StopAdmin stops the API endpoint.
|
||||||
type AdminRoute struct {
|
func StopAdmin() error {
|
||||||
http.Handler
|
|
||||||
Pattern string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the API endpoint.
|
|
||||||
func Stop() error {
|
|
||||||
cfgEndptSrvMu.Lock()
|
cfgEndptSrvMu.Lock()
|
||||||
defer cfgEndptSrvMu.Unlock()
|
defer cfgEndptSrvMu.Unlock()
|
||||||
|
|
||||||
|
@ -73,6 +67,12 @@ func Stop() error {
|
||||||
return nil
|
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) {
|
func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != "POST" {
|
if r.Method != "POST" {
|
||||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
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 {
|
func Load(r io.Reader) error {
|
||||||
gc := globalConfig{modules: make(map[string]interface{})}
|
var cfg Config
|
||||||
err := json.NewDecoder(r).Decode(&gc)
|
err := json.NewDecoder(r).Decode(&cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("decoding config: %v", err)
|
return fmt.Errorf("decoding config: %v", err)
|
||||||
}
|
}
|
||||||
|
err = Start(cfg)
|
||||||
for modName, rawMsg := range gc.Modules {
|
if err != nil {
|
||||||
mod, ok := modules[modName]
|
return fmt.Errorf("starting: %v", err)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
package caddy2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -104,6 +106,34 @@ func Modules() []string {
|
||||||
return names
|
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 (
|
var (
|
||||||
modules = make(map[string]Module)
|
modules = make(map[string]Module)
|
||||||
modulesMu sync.Mutex
|
modulesMu sync.Mutex
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
package caddyhttp
|
package caddyhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"bitbucket.org/lightcodelabs/caddy2"
|
"bitbucket.org/lightcodelabs/caddy2"
|
||||||
)
|
)
|
||||||
|
@ -9,7 +15,7 @@ import (
|
||||||
func init() {
|
func init() {
|
||||||
err := caddy2.RegisterModule(caddy2.Module{
|
err := caddy2.RegisterModule(caddy2.Module{
|
||||||
Name: "http",
|
Name: "http",
|
||||||
New: func() (interface{}, error) { return httpModuleConfig{}, nil },
|
New: func() (interface{}, error) { return new(httpModuleConfig), nil },
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -20,8 +26,69 @@ type httpModuleConfig struct {
|
||||||
Servers map[string]httpServerConfig `json:"servers"`
|
Servers map[string]httpServerConfig `json:"servers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpServerConfig struct {
|
func (hc *httpModuleConfig) Run() error {
|
||||||
Listen []string `json:"listen"`
|
fmt.Printf("RUNNING: %#v\n", hc)
|
||||||
ReadTimeout string `json:"read_timeout"`
|
|
||||||
ReadHeaderTimeout string `json:"read_header_timeout"`
|
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