diff --git a/caddyhttp/httpserver/plugin.go b/caddyhttp/httpserver/plugin.go index d1b5bf268..1a932523c 100644 --- a/caddyhttp/httpserver/plugin.go +++ b/caddyhttp/httpserver/plugin.go @@ -6,6 +6,7 @@ import ( "log" "net" "net/url" + "os" "strings" "time" @@ -326,6 +327,63 @@ func standardizeAddress(str string) (Address, error) { return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err } +// RegisterDevDirective splices name into the list of directives +// immediately before another directive. This function is ONLY +// for plugin development purposes! NEVER use it for a plugin +// that you are not currently building. If before is empty, +// the directive will be appended to the end of the list. +// +// It is imperative that directives execute in the proper +// order, and hard-coding the list of directives guarantees +// a correct, absolute order every time. This function is +// convenient when developing a plugin, but it does not +// guarantee absolute ordering. Multiple plugins registering +// directives with this function will lead to non- +// deterministic builds and buggy software. +// +// Directive names must be lower-cased and unique. Any errors +// here are fatal, and even successful calls print a message +// to stdout as a reminder to use it only in development. +func RegisterDevDirective(name, before string) { + if name == "" { + fmt.Println("[FATAL] Cannot register empty directive name") + os.Exit(1) + } + if strings.ToLower(name) != name { + fmt.Printf("[FATAL] %s: directive name must be lowercase\n", name) + os.Exit(1) + } + for _, dir := range directives { + if dir == name { + fmt.Printf("[FATAL] %s: directive name already exists\n", name) + os.Exit(1) + } + } + if before == "" { + directives = append(directives, name) + } else { + var found bool + for i, dir := range directives { + if dir == before { + directives = append(directives[:i], append([]string{name}, directives[i:]...)...) + found = true + break + } + } + if !found { + fmt.Printf("[FATAL] %s: directive not found\n", before) + os.Exit(1) + } + } + msg := fmt.Sprintf("Registered directive '%s' ", name) + if before == "" { + msg += "at end of list" + } else { + msg += fmt.Sprintf("before '%s'", before) + } + fmt.Printf("[DEV NOTICE] %s\n", msg) +} + // directives is the list of all directives known to exist for the // http server type, including non-standard (3rd-party) directives. // The ordering of this list is important. diff --git a/caddyhttp/httpserver/plugin_test.go b/caddyhttp/httpserver/plugin_test.go index b079a447f..25179f06c 100644 --- a/caddyhttp/httpserver/plugin_test.go +++ b/caddyhttp/httpserver/plugin_test.go @@ -137,3 +137,23 @@ func TestInspectServerBlocksWithCustomDefaultPort(t *testing.T) { t.Errorf("Expected the port on the address to be set, but got: %#v", addr) } } + +func TestDirectivesList(t *testing.T) { + for i, dir1 := range directives { + if dir1 == "" { + t.Errorf("directives[%d]: empty directive name", i) + continue + } + if got, want := dir1, strings.ToLower(dir1); got != want { + t.Errorf("directives[%d]: %s should be lower-cased", i, dir1) + continue + } + for j := i + 1; j < len(directives); j++ { + dir2 := directives[j] + if dir1 == dir2 { + t.Errorf("directives[%d] (%s) is a duplicate of directives[%d] (%s)", + j, dir2, i, dir1) + } + } + } +}