mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
Initial commit
This commit is contained in:
commit
859b5d7ea3
6 changed files with 360 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
_gitignore/
|
130
admin.go
Normal file
130
admin.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
package caddy2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgEndptSrv *http.Server
|
||||
cfgEndptSrvMu sync.Mutex
|
||||
)
|
||||
|
||||
// Start starts Caddy's administration endpoint.
|
||||
func Start(addr string) error {
|
||||
cfgEndptSrvMu.Lock()
|
||||
defer cfgEndptSrvMu.Unlock()
|
||||
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/load", handleLoadConfig)
|
||||
|
||||
for _, m := range GetModules("admin") {
|
||||
moduleValue, err := m.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing module '%s': %v", m.Name, err)
|
||||
}
|
||||
route := moduleValue.(AdminRoute)
|
||||
mux.Handle(route.Pattern, route)
|
||||
}
|
||||
|
||||
cfgEndptSrv = &http.Server{
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
go cfgEndptSrv.Serve(ln)
|
||||
|
||||
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 {
|
||||
cfgEndptSrvMu.Lock()
|
||||
defer cfgEndptSrvMu.Unlock()
|
||||
|
||||
if cfgEndptSrv == nil {
|
||||
return fmt.Errorf("no server")
|
||||
}
|
||||
|
||||
err := cfgEndptSrv.Shutdown(context.Background()) // TODO
|
||||
if err != nil {
|
||||
return fmt.Errorf("shutting down server: %v", err)
|
||||
}
|
||||
|
||||
cfgEndptSrv = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), "/json") {
|
||||
http.Error(w, "unacceptable Content-Type", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := Load(r.Body)
|
||||
if err != nil {
|
||||
log.Printf("[ADMIN][ERROR] loading config: %v", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Load loads a configuration.
|
||||
func Load(r io.Reader) error {
|
||||
gc := globalConfig{modules: make(map[string]interface{})}
|
||||
err := json.NewDecoder(r).Decode(&gc)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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{}
|
||||
}
|
21
cmd/caddy2/main.go
Normal file
21
cmd/caddy2/main.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"bitbucket.org/lightcodelabs/caddy2"
|
||||
|
||||
// this is where modules get plugged in
|
||||
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
|
||||
_ "bitbucket.org/lightcodelabs/dynamicconfig"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := caddy2.Start("127.0.0.1:1234")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer caddy2.Stop()
|
||||
|
||||
select {}
|
||||
}
|
110
modules.go
Normal file
110
modules.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package caddy2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Module is a module.
|
||||
type Module struct {
|
||||
Name string
|
||||
New func() (interface{}, error)
|
||||
}
|
||||
|
||||
func (m Module) String() string { return m.Name }
|
||||
|
||||
// RegisterModule registers a module.
|
||||
func RegisterModule(mod Module) error {
|
||||
modulesMu.Lock()
|
||||
defer modulesMu.Unlock()
|
||||
|
||||
if _, ok := modules[mod.Name]; ok {
|
||||
return fmt.Errorf("module already registered: %s", mod.Name)
|
||||
}
|
||||
modules[mod.Name] = mod
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetModule returns a module by name.
|
||||
func GetModule(name string) (Module, error) {
|
||||
modulesMu.Lock()
|
||||
defer modulesMu.Unlock()
|
||||
|
||||
m, ok := modules[name]
|
||||
if !ok {
|
||||
return Module{}, fmt.Errorf("module not registered: %s", name)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// GetModules returns all modules in the given scope/namespace.
|
||||
// For example, a scope of "foo" returns modules named "foo.bar",
|
||||
// "foo.lor", but not "bar", "foo.bar.lor", etc. An empty scope
|
||||
// returns top-level modules, for example "foo" or "bar". Partial
|
||||
// scopes are not matched (i.e. scope "foo.ba" does not match
|
||||
// name "foo.bar").
|
||||
//
|
||||
// Because modules are registered to a map, the returned slice
|
||||
// will be sorted to keep it deterministic.
|
||||
func GetModules(scope string) []Module {
|
||||
modulesMu.Lock()
|
||||
defer modulesMu.Unlock()
|
||||
|
||||
scopeParts := strings.Split(scope, ".")
|
||||
|
||||
// handle the special case of an empty scope, which
|
||||
// should match only the top-level modules
|
||||
if len(scopeParts) == 1 && scopeParts[0] == "" {
|
||||
scopeParts = []string{}
|
||||
}
|
||||
|
||||
var mods []Module
|
||||
iterateModules:
|
||||
for name, m := range modules {
|
||||
modParts := strings.Split(name, ".")
|
||||
|
||||
// match only the next level of nesting
|
||||
if len(modParts) != len(scopeParts)+1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// specified parts must be exact matches
|
||||
for i := range scopeParts {
|
||||
if modParts[i] != scopeParts[i] {
|
||||
continue iterateModules
|
||||
}
|
||||
}
|
||||
|
||||
mods = append(mods, m)
|
||||
}
|
||||
|
||||
// make return value deterministic
|
||||
sort.Slice(mods, func(i, j int) bool {
|
||||
return mods[i].Name < mods[j].Name
|
||||
})
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
// Modules returns the names of all registered modules
|
||||
// in ascending lexicographical order.
|
||||
func Modules() []string {
|
||||
modulesMu.Lock()
|
||||
defer modulesMu.Unlock()
|
||||
|
||||
var names []string
|
||||
for name := range modules {
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
var (
|
||||
modules = make(map[string]Module)
|
||||
modulesMu sync.Mutex
|
||||
)
|
27
modules/caddyhttp/caddyhttp.go
Normal file
27
modules/caddyhttp/caddyhttp.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package caddyhttp
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"bitbucket.org/lightcodelabs/caddy2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
err := caddy2.RegisterModule(caddy2.Module{
|
||||
Name: "http",
|
||||
New: func() (interface{}, error) { return httpModuleConfig{}, nil },
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
71
modules_test.go
Normal file
71
modules_test.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package caddy2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetModules(t *testing.T) {
|
||||
modulesMu.Lock()
|
||||
modules = map[string]Module{
|
||||
"a": {Name: "a"},
|
||||
"a.b": {Name: "a.b"},
|
||||
"a.b.c": {Name: "a.b.c"},
|
||||
"a.b.cd": {Name: "a.b.cd"},
|
||||
"a.c": {Name: "a.c"},
|
||||
"a.d": {Name: "a.d"},
|
||||
"b": {Name: "b"},
|
||||
"b.a": {Name: "b.a"},
|
||||
"b.b": {Name: "b.b"},
|
||||
"b.a.c": {Name: "b.a.c"},
|
||||
"c": {Name: "c"},
|
||||
}
|
||||
modulesMu.Unlock()
|
||||
|
||||
for i, tc := range []struct {
|
||||
input string
|
||||
expect []Module
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expect: []Module{
|
||||
{Name: "a"},
|
||||
{Name: "b"},
|
||||
{Name: "c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "a",
|
||||
expect: []Module{
|
||||
{Name: "a.b"},
|
||||
{Name: "a.c"},
|
||||
{Name: "a.d"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "a.b",
|
||||
expect: []Module{
|
||||
{Name: "a.b.c"},
|
||||
{Name: "a.b.cd"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "a.b.c",
|
||||
},
|
||||
{
|
||||
input: "b",
|
||||
expect: []Module{
|
||||
{Name: "b.a"},
|
||||
{Name: "b.b"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "asdf",
|
||||
},
|
||||
} {
|
||||
actual := GetModules(tc.input)
|
||||
if !reflect.DeepEqual(actual, tc.expect) {
|
||||
t.Errorf("Test %d: Expected %v but got %v", i, tc.expect, actual)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue