mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-22 10:14:15 +08:00
Initial commit
This commit is contained in:
commit
859b5d7ea3
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 New Issue
Block a user