mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
7796ff0f69
Fixes bug introduced in 0ac8bf5
- Also note that setup functions no longer have access to server port. Will need to fix later.
259 lines
7.5 KiB
Go
259 lines
7.5 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
|
|
"github.com/mholt/caddy/app"
|
|
"github.com/mholt/caddy/config/parse"
|
|
"github.com/mholt/caddy/config/setup"
|
|
"github.com/mholt/caddy/middleware"
|
|
"github.com/mholt/caddy/server"
|
|
)
|
|
|
|
const (
|
|
DefaultHost = "0.0.0.0"
|
|
DefaultPort = "2015"
|
|
DefaultRoot = "."
|
|
|
|
// DefaultConfigFile is the name of the configuration file that is loaded
|
|
// by default if no other file is specified.
|
|
DefaultConfigFile = "Caddyfile"
|
|
)
|
|
|
|
// Load reads input (named filename) and parses it, returning server
|
|
// configurations grouped by listening address.
|
|
func Load(filename string, input io.Reader) (Group, error) {
|
|
var configs []server.Config
|
|
|
|
// turn off timestamp for parsing
|
|
flags := log.Flags()
|
|
log.SetFlags(0)
|
|
|
|
serverBlocks, err := parse.ServerBlocks(filename, input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(serverBlocks) == 0 {
|
|
return Default()
|
|
}
|
|
|
|
// Each server block represents one or more servers/addresses.
|
|
// Iterate each server block and make a config for each one,
|
|
// executing the directives that were parsed.
|
|
for _, sb := range serverBlocks {
|
|
sharedConfig, err := serverBlockToConfig(filename, sb)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Now share the config with as many hosts as share the server block
|
|
for i, addr := range sb.Addresses {
|
|
config := sharedConfig
|
|
config.Host = addr.Host
|
|
config.Port = addr.Port
|
|
if config.Port == "" {
|
|
config.Port = Port
|
|
}
|
|
if config.Port == "http" {
|
|
config.TLS.Enabled = false
|
|
log.Printf("Warning: TLS disabled for %s://%s. To force TLS over the plaintext HTTP port, "+
|
|
"specify port 80 explicitly (https://%s:80).", config.Port, config.Host, config.Host)
|
|
}
|
|
if i == 0 {
|
|
sharedConfig.Startup = []func() error{}
|
|
sharedConfig.Shutdown = []func() error{}
|
|
}
|
|
configs = append(configs, config)
|
|
}
|
|
}
|
|
|
|
// restore logging settings
|
|
log.SetFlags(flags)
|
|
|
|
// Group by address/virtualhosts
|
|
return arrangeBindings(configs)
|
|
}
|
|
|
|
// serverBlockToConfig makes a config for the server block
|
|
// by executing the tokens that were parsed. The returned
|
|
// config is shared among all hosts/addresses for the server
|
|
// block, so Host and Port information is not filled out
|
|
// here.
|
|
func serverBlockToConfig(filename string, sb parse.ServerBlock) (server.Config, error) {
|
|
sharedConfig := server.Config{
|
|
Root: Root,
|
|
Middleware: make(map[string][]middleware.Middleware),
|
|
ConfigFile: filename,
|
|
AppName: app.Name,
|
|
AppVersion: app.Version,
|
|
}
|
|
|
|
// It is crucial that directives are executed in the proper order.
|
|
for _, dir := range directiveOrder {
|
|
// Execute directive if it is in the server block
|
|
if tokens, ok := sb.Tokens[dir.name]; ok {
|
|
// Each setup function gets a controller, which is the
|
|
// server config and the dispenser containing only
|
|
// this directive's tokens.
|
|
controller := &setup.Controller{
|
|
Config: &sharedConfig,
|
|
Dispenser: parse.NewDispenserTokens(filename, tokens),
|
|
}
|
|
|
|
midware, err := dir.setup(controller)
|
|
if err != nil {
|
|
return sharedConfig, err
|
|
}
|
|
if midware != nil {
|
|
// TODO: For now, we only support the default path scope /
|
|
sharedConfig.Middleware["/"] = append(sharedConfig.Middleware["/"], midware)
|
|
}
|
|
}
|
|
}
|
|
|
|
return sharedConfig, nil
|
|
}
|
|
|
|
// arrangeBindings groups configurations by their bind address. For example,
|
|
// a server that should listen on localhost and another on 127.0.0.1 will
|
|
// be grouped into the same address: 127.0.0.1. It will return an error
|
|
// if an address is malformed or a TLS listener is configured on the
|
|
// same address as a plaintext HTTP listener. The return value is a map of
|
|
// bind address to list of configs that would become VirtualHosts on that
|
|
// server. Use the keys of the returned map to create listeners, and use
|
|
// the associated values to set up the virtualhosts.
|
|
func arrangeBindings(allConfigs []server.Config) (Group, error) {
|
|
addresses := make(Group)
|
|
|
|
// Group configs by bind address
|
|
for _, conf := range allConfigs {
|
|
newAddr, warnErr, fatalErr := resolveAddr(conf)
|
|
if fatalErr != nil {
|
|
return addresses, fatalErr
|
|
}
|
|
if warnErr != nil {
|
|
log.Println("[Warning]", warnErr)
|
|
}
|
|
|
|
// Make sure to compare the string representation of the address,
|
|
// not the pointer, since a new *TCPAddr is created each time.
|
|
var existing bool
|
|
for addr := range addresses {
|
|
if addr.String() == newAddr.String() {
|
|
addresses[addr] = append(addresses[addr], conf)
|
|
existing = true
|
|
break
|
|
}
|
|
}
|
|
if !existing {
|
|
addresses[newAddr] = append(addresses[newAddr], conf)
|
|
}
|
|
}
|
|
|
|
// Don't allow HTTP and HTTPS to be served on the same address
|
|
for _, configs := range addresses {
|
|
isTLS := configs[0].TLS.Enabled
|
|
for _, config := range configs {
|
|
if config.TLS.Enabled != isTLS {
|
|
thisConfigProto, otherConfigProto := "HTTP", "HTTP"
|
|
if config.TLS.Enabled {
|
|
thisConfigProto = "HTTPS"
|
|
}
|
|
if configs[0].TLS.Enabled {
|
|
otherConfigProto = "HTTPS"
|
|
}
|
|
return addresses, fmt.Errorf("configuration error: Cannot multiplex %s (%s) and %s (%s) on same address",
|
|
configs[0].Address(), otherConfigProto, config.Address(), thisConfigProto)
|
|
}
|
|
}
|
|
}
|
|
|
|
return addresses, nil
|
|
}
|
|
|
|
// resolveAddr determines the address (host and port) that a config will
|
|
// bind to. The returned address, resolvAddr, should be used to bind the
|
|
// listener or group the config with other configs using the same address.
|
|
// The first error, if not nil, is just a warning and should be reported
|
|
// but execution may continue. The second error, if not nil, is a real
|
|
// problem and the server should not be started.
|
|
//
|
|
// This function handles edge cases gracefully. If a port name like
|
|
// "http" or "https" is unknown to the system, this function will
|
|
// change them to 80 or 443 respectively. If a hostname fails to
|
|
// resolve, that host can still be served but will be listening on
|
|
// the wildcard host instead. This function takes care of this for you.
|
|
func resolveAddr(conf server.Config) (resolvAddr *net.TCPAddr, warnErr error, fatalErr error) {
|
|
bindHost := conf.BindHost
|
|
|
|
resolvAddr, warnErr = net.ResolveTCPAddr("tcp", net.JoinHostPort(bindHost, conf.Port))
|
|
if warnErr != nil {
|
|
// Most likely the host lookup failed or the port is unknown
|
|
tryPort := conf.Port
|
|
|
|
switch errVal := warnErr.(type) {
|
|
case *net.AddrError:
|
|
if errVal.Err == "unknown port" {
|
|
// some odd Linux machines don't support these port names; see issue #136
|
|
switch conf.Port {
|
|
case "http":
|
|
tryPort = "80"
|
|
case "https":
|
|
tryPort = "443"
|
|
}
|
|
}
|
|
resolvAddr, fatalErr = net.ResolveTCPAddr("tcp", net.JoinHostPort(bindHost, tryPort))
|
|
if fatalErr != nil {
|
|
return
|
|
}
|
|
default:
|
|
// the hostname probably couldn't be resolved, just bind to wildcard then
|
|
resolvAddr, fatalErr = net.ResolveTCPAddr("tcp", net.JoinHostPort("0.0.0.0", tryPort))
|
|
if fatalErr != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// validDirective returns true if d is a valid
|
|
// directive; false otherwise.
|
|
func validDirective(d string) bool {
|
|
for _, dir := range directiveOrder {
|
|
if dir.name == d {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func NewDefault() server.Config {
|
|
return server.Config{
|
|
Root: Root,
|
|
Host: Host,
|
|
Port: Port,
|
|
}
|
|
}
|
|
|
|
// Default makes a default configuration which
|
|
// is empty except for root, host, and port,
|
|
// which are essentials for serving the cwd.
|
|
func Default() (Group, error) {
|
|
return arrangeBindings([]server.Config{NewDefault()})
|
|
}
|
|
|
|
// These three defaults are configurable through the command line
|
|
var (
|
|
Root = DefaultRoot
|
|
Host = DefaultHost
|
|
Port = DefaultPort
|
|
)
|
|
|
|
type Group map[*net.TCPAddr][]server.Config
|