mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-01 04:22:36 +08:00 
			
		
		
		
	 173573035c
			
		
	
	173573035c
	
	
	
		
			
			* core: add modular `network_proxy` support Co-authored-by: @ImpostorKeanu Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * move modules around Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * add caddyfile implementation Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * address feedbcak * Apply suggestions from code review Co-authored-by: Francis Lavoie <lavofr@gmail.com> * adapt ForwardProxyURL to use the NetworkProxyRaw Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * remove redundant `url` in log Co-authored-by: Matt Holt <mholt@users.noreply.github.com> * code review Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * remove `.source` from the module ID Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> --------- Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> Co-authored-by: Francis Lavoie <lavofr@gmail.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
		
			
				
	
	
		
			377 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			377 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 Matthew Holt and The Caddy Authors
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package caddy
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"reflect"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| )
 | |
| 
 | |
| // Module is a type that is used as a Caddy module. In
 | |
| // addition to this interface, most modules will implement
 | |
| // some interface expected by their host module in order
 | |
| // to be useful. To learn which interface(s) to implement,
 | |
| // see the documentation for the host module. At a bare
 | |
| // minimum, this interface, when implemented, only provides
 | |
| // the module's ID and constructor function.
 | |
| //
 | |
| // Modules will often implement additional interfaces
 | |
| // including Provisioner, Validator, and CleanerUpper.
 | |
| // If a module implements these interfaces, their
 | |
| // methods are called during the module's lifespan.
 | |
| //
 | |
| // When a module is loaded by a host module, the following
 | |
| // happens: 1) ModuleInfo.New() is called to get a new
 | |
| // instance of the module. 2) The module's configuration is
 | |
| // unmarshaled into that instance. 3) If the module is a
 | |
| // Provisioner, the Provision() method is called. 4) If the
 | |
| // module is a Validator, the Validate() method is called.
 | |
| // 5) The module will probably be type-asserted from
 | |
| // 'any' to some other, more useful interface expected
 | |
| // by the host module. For example, HTTP handler modules are
 | |
| // type-asserted as caddyhttp.MiddlewareHandler values.
 | |
| // 6) When a module's containing Context is canceled, if it is
 | |
| // a CleanerUpper, its Cleanup() method is called.
 | |
| type Module interface {
 | |
| 	// This method indicates that the type is a Caddy
 | |
| 	// module. The returned ModuleInfo must have both
 | |
| 	// a name and a constructor function. This method
 | |
| 	// must not have any side-effects.
 | |
| 	CaddyModule() ModuleInfo
 | |
| }
 | |
| 
 | |
| // ModuleInfo represents a registered Caddy module.
 | |
| type ModuleInfo struct {
 | |
| 	// ID is the "full name" of the module. It
 | |
| 	// must be unique and properly namespaced.
 | |
| 	ID ModuleID
 | |
| 
 | |
| 	// New returns a pointer to a new, empty
 | |
| 	// instance of the module's type. This
 | |
| 	// method must not have any side-effects,
 | |
| 	// and no other initialization should
 | |
| 	// occur within it. Any initialization
 | |
| 	// of the returned value should be done
 | |
| 	// in a Provision() method (see the
 | |
| 	// Provisioner interface).
 | |
| 	New func() Module
 | |
| }
 | |
| 
 | |
| // ModuleID is a string that uniquely identifies a Caddy module. A
 | |
| // module ID is lightly structured. It consists of dot-separated
 | |
| // labels which form a simple hierarchy from left to right. The last
 | |
| // label is the module name, and the labels before that constitute
 | |
| // the namespace (or scope).
 | |
| //
 | |
| // Thus, a module ID has the form: <namespace>.<name>
 | |
| //
 | |
| // An ID with no dot has the empty namespace, which is appropriate
 | |
| // for app modules (these are "top-level" modules that Caddy core
 | |
| // loads and runs).
 | |
| //
 | |
| // Module IDs should be lowercase and use underscores (_) instead of
 | |
| // spaces.
 | |
| //
 | |
| // Examples of valid IDs:
 | |
| // - http
 | |
| // - http.handlers.file_server
 | |
| // - caddy.logging.encoders.json
 | |
| type ModuleID string
 | |
| 
 | |
| // Namespace returns the namespace (or scope) portion of a module ID,
 | |
| // which is all but the last label of the ID. If the ID has only one
 | |
| // label, then the namespace is empty.
 | |
| func (id ModuleID) Namespace() string {
 | |
| 	lastDot := strings.LastIndex(string(id), ".")
 | |
| 	if lastDot < 0 {
 | |
| 		return ""
 | |
| 	}
 | |
| 	return string(id)[:lastDot]
 | |
| }
 | |
| 
 | |
| // Name returns the Name (last element) of a module ID.
 | |
| func (id ModuleID) Name() string {
 | |
| 	if id == "" {
 | |
| 		return ""
 | |
| 	}
 | |
| 	parts := strings.Split(string(id), ".")
 | |
| 	return parts[len(parts)-1]
 | |
| }
 | |
| 
 | |
| func (mi ModuleInfo) String() string { return string(mi.ID) }
 | |
| 
 | |
| // ModuleMap is a map that can contain multiple modules,
 | |
| // where the map key is the module's name. (The namespace
 | |
| // is usually read from an associated field's struct tag.)
 | |
| // Because the module's name is given as the key in a
 | |
| // module map, the name does not have to be given in the
 | |
| // json.RawMessage.
 | |
| type ModuleMap map[string]json.RawMessage
 | |
| 
 | |
| // RegisterModule registers a module by receiving a
 | |
| // plain/empty value of the module. For registration to
 | |
| // be properly recorded, this should be called in the
 | |
| // init phase of runtime. Typically, the module package
 | |
| // will do this as a side-effect of being imported.
 | |
| // This function panics if the module's info is
 | |
| // incomplete or invalid, or if the module is already
 | |
| // registered.
 | |
| func RegisterModule(instance Module) {
 | |
| 	mod := instance.CaddyModule()
 | |
| 
 | |
| 	if mod.ID == "" {
 | |
| 		panic("module ID missing")
 | |
| 	}
 | |
| 	if mod.ID == "caddy" || mod.ID == "admin" {
 | |
| 		panic(fmt.Sprintf("module ID '%s' is reserved", mod.ID))
 | |
| 	}
 | |
| 	if mod.New == nil {
 | |
| 		panic("missing ModuleInfo.New")
 | |
| 	}
 | |
| 	if val := mod.New(); val == nil {
 | |
| 		panic("ModuleInfo.New must return a non-nil module instance")
 | |
| 	}
 | |
| 
 | |
| 	modulesMu.Lock()
 | |
| 	defer modulesMu.Unlock()
 | |
| 
 | |
| 	if _, ok := modules[string(mod.ID)]; ok {
 | |
| 		panic(fmt.Sprintf("module already registered: %s", mod.ID))
 | |
| 	}
 | |
| 	modules[string(mod.ID)] = mod
 | |
| }
 | |
| 
 | |
| // GetModule returns module information from its ID (full name).
 | |
| func GetModule(name string) (ModuleInfo, error) {
 | |
| 	modulesMu.RLock()
 | |
| 	defer modulesMu.RUnlock()
 | |
| 	m, ok := modules[name]
 | |
| 	if !ok {
 | |
| 		return ModuleInfo{}, fmt.Errorf("module not registered: %s", name)
 | |
| 	}
 | |
| 	return m, nil
 | |
| }
 | |
| 
 | |
| // GetModuleName returns a module's name (the last label of its ID)
 | |
| // from an instance of its value. If the value is not a module, an
 | |
| // empty string will be returned.
 | |
| func GetModuleName(instance any) string {
 | |
| 	var name string
 | |
| 	if mod, ok := instance.(Module); ok {
 | |
| 		name = mod.CaddyModule().ID.Name()
 | |
| 	}
 | |
| 	return name
 | |
| }
 | |
| 
 | |
| // GetModuleID returns a module's ID from an instance of its value.
 | |
| // If the value is not a module, an empty string will be returned.
 | |
| func GetModuleID(instance any) string {
 | |
| 	var id string
 | |
| 	if mod, ok := instance.(Module); ok {
 | |
| 		id = string(mod.CaddyModule().ID)
 | |
| 	}
 | |
| 	return id
 | |
| }
 | |
| 
 | |
| // GetModules returns all modules in the given scope/namespace.
 | |
| // For example, a scope of "foo" returns modules named "foo.bar",
 | |
| // "foo.loo", but not "bar", "foo.bar.loo", 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 under the hood, the
 | |
| // returned slice will be sorted to keep it deterministic.
 | |
| func GetModules(scope string) []ModuleInfo {
 | |
| 	modulesMu.RLock()
 | |
| 	defer modulesMu.RUnlock()
 | |
| 
 | |
| 	scopeParts := strings.Split(scope, ".")
 | |
| 
 | |
| 	// handle the special case of an empty scope, which
 | |
| 	// should match only the top-level modules
 | |
| 	if scope == "" {
 | |
| 		scopeParts = []string{}
 | |
| 	}
 | |
| 
 | |
| 	var mods []ModuleInfo
 | |
| iterateModules:
 | |
| 	for id, m := range modules {
 | |
| 		modParts := strings.Split(id, ".")
 | |
| 
 | |
| 		// 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].ID < mods[j].ID
 | |
| 	})
 | |
| 
 | |
| 	return mods
 | |
| }
 | |
| 
 | |
| // Modules returns the names of all registered modules
 | |
| // in ascending lexicographical order.
 | |
| func Modules() []string {
 | |
| 	modulesMu.RLock()
 | |
| 	defer modulesMu.RUnlock()
 | |
| 
 | |
| 	names := make([]string, 0, len(modules))
 | |
| 	for name := range modules {
 | |
| 		names = append(names, name)
 | |
| 	}
 | |
| 
 | |
| 	sort.Strings(names)
 | |
| 
 | |
| 	return names
 | |
| }
 | |
| 
 | |
| // getModuleNameInline loads the string value from raw of moduleNameKey,
 | |
| // where raw must be a JSON encoding of a map. It returns that value,
 | |
| // along with the result of removing that key from raw.
 | |
| func getModuleNameInline(moduleNameKey string, raw json.RawMessage) (string, json.RawMessage, error) {
 | |
| 	var tmp map[string]any
 | |
| 	err := json.Unmarshal(raw, &tmp)
 | |
| 	if err != nil {
 | |
| 		return "", nil, err
 | |
| 	}
 | |
| 
 | |
| 	moduleName, ok := tmp[moduleNameKey].(string)
 | |
| 	if !ok || moduleName == "" {
 | |
| 		return "", nil, fmt.Errorf("module name not specified with key '%s' in %+v", moduleNameKey, tmp)
 | |
| 	}
 | |
| 
 | |
| 	// remove key from the object, otherwise decoding it later
 | |
| 	// will yield an error because the struct won't recognize it
 | |
| 	// (this is only needed because we strictly enforce that
 | |
| 	// all keys are recognized when loading modules)
 | |
| 	delete(tmp, moduleNameKey)
 | |
| 	result, err := json.Marshal(tmp)
 | |
| 	if err != nil {
 | |
| 		return "", nil, fmt.Errorf("re-encoding module configuration: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	return moduleName, result, nil
 | |
| }
 | |
| 
 | |
| // Provisioner is implemented by modules which may need to perform
 | |
| // some additional "setup" steps immediately after being loaded.
 | |
| // Provisioning should be fast (imperceptible running time). If
 | |
| // any side-effects result in the execution of this function (e.g.
 | |
| // creating global state, any other allocations which require
 | |
| // garbage collection, opening files, starting goroutines etc.),
 | |
| // be sure to clean up properly by implementing the CleanerUpper
 | |
| // interface to avoid leaking resources.
 | |
| type Provisioner interface {
 | |
| 	Provision(Context) error
 | |
| }
 | |
| 
 | |
| // Validator is implemented by modules which can verify that their
 | |
| // configurations are valid. This method will be called after
 | |
| // Provision() (if implemented). Validation should always be fast
 | |
| // (imperceptible running time) and an error must be returned if
 | |
| // the module's configuration is invalid.
 | |
| type Validator interface {
 | |
| 	Validate() error
 | |
| }
 | |
| 
 | |
| // CleanerUpper is implemented by modules which may have side-effects
 | |
| // such as opened files, spawned goroutines, or allocated some sort
 | |
| // of non-stack state when they were provisioned. This method should
 | |
| // deallocate/cleanup those resources to prevent memory leaks. Cleanup
 | |
| // should be fast and efficient. Cleanup should work even if Provision
 | |
| // returns an error, to allow cleaning up from partial provisionings.
 | |
| type CleanerUpper interface {
 | |
| 	Cleanup() error
 | |
| }
 | |
| 
 | |
| // ParseStructTag parses a caddy struct tag into its keys and values.
 | |
| // It is very simple. The expected syntax is:
 | |
| // `caddy:"key1=val1 key2=val2 ..."`
 | |
| func ParseStructTag(tag string) (map[string]string, error) {
 | |
| 	results := make(map[string]string)
 | |
| 	pairs := strings.Split(tag, " ")
 | |
| 	for i, pair := range pairs {
 | |
| 		if pair == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 		before, after, isCut := strings.Cut(pair, "=")
 | |
| 		if !isCut {
 | |
| 			return nil, fmt.Errorf("missing key in '%s' (pair %d)", pair, i)
 | |
| 		}
 | |
| 		results[before] = after
 | |
| 	}
 | |
| 	return results, nil
 | |
| }
 | |
| 
 | |
| // StrictUnmarshalJSON is like json.Unmarshal but returns an error
 | |
| // if any of the fields are unrecognized. Useful when decoding
 | |
| // module configurations, where you want to be more sure they're
 | |
| // correct.
 | |
| func StrictUnmarshalJSON(data []byte, v any) error {
 | |
| 	dec := json.NewDecoder(bytes.NewReader(data))
 | |
| 	dec.DisallowUnknownFields()
 | |
| 	return dec.Decode(v)
 | |
| }
 | |
| 
 | |
| // isJSONRawMessage returns true if the type is encoding/json.RawMessage.
 | |
| func isJSONRawMessage(typ reflect.Type) bool {
 | |
| 	return typ.PkgPath() == "encoding/json" && typ.Name() == "RawMessage"
 | |
| }
 | |
| 
 | |
| // isModuleMapType returns true if the type is map[string]json.RawMessage.
 | |
| // It assumes that the string key is the module name, but this is not
 | |
| // always the case. To know for sure, this function must return true, but
 | |
| // also the struct tag where this type appears must NOT define an inline_key
 | |
| // attribute, which would mean that the module names appear inline with the
 | |
| // values, not in the key.
 | |
| func isModuleMapType(typ reflect.Type) bool {
 | |
| 	return typ.Kind() == reflect.Map &&
 | |
| 		typ.Key().Kind() == reflect.String &&
 | |
| 		isJSONRawMessage(typ.Elem())
 | |
| }
 | |
| 
 | |
| // ProxyFuncProducer is implemented by modules which produce a
 | |
| // function that returns a URL to use as network proxy. Modules
 | |
| // in the namespace `caddy.network_proxy` must implement this
 | |
| // interface.
 | |
| type ProxyFuncProducer interface {
 | |
| 	ProxyFunc() func(*http.Request) (*url.URL, error)
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	modules   = make(map[string]ModuleInfo)
 | |
| 	modulesMu sync.RWMutex
 | |
| )
 |