mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
Parser separate scheme/port, refactor config loading
By separating scheme and port at the parser, we are able to set the port appropriately and also keep the semantics of the scheme being specified by the user later on. The parser also stores an address' original input. Also, the config refactor makes it possible to partially load a config - valuable for determining which ones will need Let's Encrypt integration turned on during a restart.
This commit is contained in:
parent
b0397df719
commit
946ff5e87b
7 changed files with 188 additions and 169 deletions
|
@ -21,25 +21,22 @@ const (
|
||||||
DefaultConfigFile = "Caddyfile"
|
DefaultConfigFile = "Caddyfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadConfigs reads input (named filename) and parses it, returning the
|
// loadConfigsUpToIncludingTLS loads the configs from input with name filename and returns them,
|
||||||
// server configurations in the order they appeared in the input. As part
|
// the parsed server blocks, the index of the last directive it processed, and an error (if any).
|
||||||
// of this, it activates Let's Encrypt for the configs that are produced.
|
func loadConfigsUpToIncludingTLS(filename string, input io.Reader) ([]server.Config, []parse.ServerBlock, int, error) {
|
||||||
// Thus, the returned configs are already optimally configured optimally
|
|
||||||
// for HTTPS.
|
|
||||||
func loadConfigs(filename string, input io.Reader) ([]server.Config, error) {
|
|
||||||
var configs []server.Config
|
var configs []server.Config
|
||||||
|
|
||||||
// Each server block represents similar hosts/addresses, since they
|
// Each server block represents similar hosts/addresses, since they
|
||||||
// were grouped together in the Caddyfile.
|
// were grouped together in the Caddyfile.
|
||||||
serverBlocks, err := parse.ServerBlocks(filename, input, true)
|
serverBlocks, err := parse.ServerBlocks(filename, input, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
if len(serverBlocks) == 0 {
|
if len(serverBlocks) == 0 {
|
||||||
newInput := DefaultInput()
|
newInput := DefaultInput()
|
||||||
serverBlocks, err = parse.ServerBlocks(newInput.Path(), bytes.NewReader(newInput.Body()), true)
|
serverBlocks, err = parse.ServerBlocks(newInput.Path(), bytes.NewReader(newInput.Body()), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +53,7 @@ func loadConfigs(filename string, input io.Reader) ([]server.Config, error) {
|
||||||
config := server.Config{
|
config := server.Config{
|
||||||
Host: addr.Host,
|
Host: addr.Host,
|
||||||
Port: addr.Port,
|
Port: addr.Port,
|
||||||
|
Scheme: addr.Scheme,
|
||||||
Root: Root,
|
Root: Root,
|
||||||
Middleware: make(map[string][]middleware.Middleware),
|
Middleware: make(map[string][]middleware.Middleware),
|
||||||
ConfigFile: filename,
|
ConfigFile: filename,
|
||||||
|
@ -88,7 +86,7 @@ func loadConfigs(filename string, input io.Reader) ([]server.Config, error) {
|
||||||
// execute setup function and append middleware handler, if any
|
// execute setup function and append middleware handler, if any
|
||||||
midware, err := dir.setup(controller)
|
midware, err := dir.setup(controller)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, lastDirectiveIndex, err
|
||||||
}
|
}
|
||||||
if midware != nil {
|
if midware != nil {
|
||||||
// TODO: For now, we only support the default path scope /
|
// TODO: For now, we only support the default path scope /
|
||||||
|
@ -109,22 +107,31 @@ func loadConfigs(filename string, input io.Reader) ([]server.Config, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return configs, serverBlocks, lastDirectiveIndex, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadConfigs reads input (named filename) and parses it, returning the
|
||||||
|
// server configurations in the order they appeared in the input. As part
|
||||||
|
// of this, it activates Let's Encrypt for the configs that are produced.
|
||||||
|
// Thus, the returned configs are already optimally configured for HTTPS.
|
||||||
|
func loadConfigs(filename string, input io.Reader) ([]server.Config, error) {
|
||||||
|
configs, serverBlocks, lastDirectiveIndex, err := loadConfigsUpToIncludingTLS(filename, input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Now we have all the configs, but they have only been set up to the
|
// Now we have all the configs, but they have only been set up to the
|
||||||
// point of tls. We need to activate Let's Encrypt before setting up
|
// point of tls. We need to activate Let's Encrypt before setting up
|
||||||
// the rest of the middlewares so they have correct information regarding
|
// the rest of the middlewares so they have correct information regarding
|
||||||
// TLS configuration, if necessary. (this call is append-only, so our
|
// TLS configuration, if necessary. (this only appends, so our iterations
|
||||||
// iterations below shouldn't be affected)
|
// over server blocks below shouldn't be affected)
|
||||||
if !IsRestart() && !Quiet {
|
if !IsRestart() && !Quiet {
|
||||||
fmt.Print("Activating privacy features...")
|
fmt.Print("Activating privacy features...")
|
||||||
}
|
}
|
||||||
configs, err = letsencrypt.Activate(configs)
|
configs, err = letsencrypt.Activate(configs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !Quiet {
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
} else if !IsRestart() && !Quiet {
|
||||||
if !IsRestart() && !Quiet {
|
|
||||||
fmt.Println(" done.")
|
fmt.Println(" done.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,44 +284,17 @@ func arrangeBindings(allConfigs []server.Config) (bindingGroup, error) {
|
||||||
// but execution may continue. The second error, if not nil, is a real
|
// but execution may continue. The second error, if not nil, is a real
|
||||||
// problem and the server should not be started.
|
// problem and the server should not be started.
|
||||||
//
|
//
|
||||||
// This function handles edge cases gracefully. If a port name like
|
// This function does not handle edge cases like port "http" or "https" if
|
||||||
// "http" or "https" is unknown to the system, this function will
|
// they are not known to the system. It does, however, serve on the wildcard
|
||||||
// change them to 80 or 443 respectively. If a hostname fails to
|
// host if resolving the address of the specific hostname fails.
|
||||||
// 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, fatalErr error) {
|
func resolveAddr(conf server.Config) (resolvAddr *net.TCPAddr, warnErr, fatalErr error) {
|
||||||
bindHost := conf.BindHost
|
resolvAddr, warnErr = net.ResolveTCPAddr("tcp", net.JoinHostPort(conf.BindHost, conf.Port))
|
||||||
|
|
||||||
// TODO: Do we even need the port? Maybe we just need to look up the host.
|
|
||||||
resolvAddr, warnErr = net.ResolveTCPAddr("tcp", net.JoinHostPort(bindHost, conf.Port))
|
|
||||||
if warnErr != nil {
|
if warnErr != nil {
|
||||||
// Most likely the host lookup failed or the port is unknown
|
// the hostname probably couldn't be resolved, just bind to wildcard then
|
||||||
tryPort := conf.Port
|
resolvAddr, fatalErr = net.ResolveTCPAddr("tcp", net.JoinHostPort("", conf.Port))
|
||||||
|
if fatalErr != nil {
|
||||||
switch errVal := warnErr.(type) {
|
return
|
||||||
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
|
return
|
||||||
|
@ -334,12 +314,12 @@ func validDirective(d string) bool {
|
||||||
// DefaultInput returns the default Caddyfile input
|
// DefaultInput returns the default Caddyfile input
|
||||||
// to use when it is otherwise empty or missing.
|
// to use when it is otherwise empty or missing.
|
||||||
// It uses the default host and port (depends on
|
// It uses the default host and port (depends on
|
||||||
// host, e.g. localhost is 2015, otherwise https) and
|
// host, e.g. localhost is 2015, otherwise 443) and
|
||||||
// root.
|
// root.
|
||||||
func DefaultInput() CaddyfileInput {
|
func DefaultInput() CaddyfileInput {
|
||||||
port := Port
|
port := Port
|
||||||
if letsencrypt.HostQualifies(Host) {
|
if letsencrypt.HostQualifies(Host) && port == DefaultPort {
|
||||||
port = "https"
|
port = "443"
|
||||||
}
|
}
|
||||||
return CaddyfileInput{
|
return CaddyfileInput{
|
||||||
Contents: []byte(fmt.Sprintf("%s:%s\nroot %s", Host, port, Root)),
|
Contents: []byte(fmt.Sprintf("%s:%s\nroot %s", Host, port, Root)),
|
||||||
|
|
|
@ -13,10 +13,10 @@ func TestDefaultInput(t *testing.T) {
|
||||||
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
|
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
// next few tests simulate user providing -host flag
|
// next few tests simulate user providing -host and/or -port flags
|
||||||
|
|
||||||
Host = "not-localhost.com"
|
Host = "not-localhost.com"
|
||||||
if actual, expected := string(DefaultInput().Body()), "not-localhost.com:https\nroot ."; actual != expected {
|
if actual, expected := string(DefaultInput().Body()), "not-localhost.com:443\nroot ."; actual != expected {
|
||||||
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
|
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,18 @@ func TestDefaultInput(t *testing.T) {
|
||||||
if actual, expected := string(DefaultInput().Body()), "127.0.1.1:2015\nroot ."; actual != expected {
|
if actual, expected := string(DefaultInput().Body()), "127.0.1.1:2015\nroot ."; actual != expected {
|
||||||
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
|
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Host = "not-localhost.com"
|
||||||
|
Port = "1234"
|
||||||
|
if actual, expected := string(DefaultInput().Body()), "not-localhost.com:1234\nroot ."; actual != expected {
|
||||||
|
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
Host = DefaultHost
|
||||||
|
Port = "1234"
|
||||||
|
if actual, expected := string(DefaultInput().Body()), ":1234\nroot ."; actual != expected {
|
||||||
|
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveAddr(t *testing.T) {
|
func TestResolveAddr(t *testing.T) {
|
||||||
|
@ -51,14 +63,14 @@ func TestResolveAddr(t *testing.T) {
|
||||||
{server.Config{Host: "localhost", Port: "80"}, false, false, "<nil>", 80},
|
{server.Config{Host: "localhost", Port: "80"}, false, false, "<nil>", 80},
|
||||||
{server.Config{BindHost: "localhost", Port: "1234"}, false, false, "127.0.0.1", 1234},
|
{server.Config{BindHost: "localhost", Port: "1234"}, false, false, "127.0.0.1", 1234},
|
||||||
{server.Config{BindHost: "127.0.0.1", Port: "1234"}, false, false, "127.0.0.1", 1234},
|
{server.Config{BindHost: "127.0.0.1", Port: "1234"}, false, false, "127.0.0.1", 1234},
|
||||||
{server.Config{BindHost: "should-not-resolve", Port: "1234"}, true, false, "0.0.0.0", 1234},
|
{server.Config{BindHost: "should-not-resolve", Port: "1234"}, true, false, "<nil>", 1234},
|
||||||
{server.Config{BindHost: "localhost", Port: "http"}, false, false, "127.0.0.1", 80},
|
{server.Config{BindHost: "localhost", Port: "http"}, false, false, "127.0.0.1", 80},
|
||||||
{server.Config{BindHost: "localhost", Port: "https"}, false, false, "127.0.0.1", 443},
|
{server.Config{BindHost: "localhost", Port: "https"}, false, false, "127.0.0.1", 443},
|
||||||
{server.Config{BindHost: "", Port: "1234"}, false, false, "<nil>", 1234},
|
{server.Config{BindHost: "", Port: "1234"}, false, false, "<nil>", 1234},
|
||||||
{server.Config{BindHost: "localhost", Port: "abcd"}, false, true, "", 0},
|
{server.Config{BindHost: "localhost", Port: "abcd"}, false, true, "", 0},
|
||||||
{server.Config{BindHost: "127.0.0.1", Host: "should-not-be-used", Port: "1234"}, false, false, "127.0.0.1", 1234},
|
{server.Config{BindHost: "127.0.0.1", Host: "should-not-be-used", Port: "1234"}, false, false, "127.0.0.1", 1234},
|
||||||
{server.Config{BindHost: "localhost", Host: "should-not-be-used", Port: "1234"}, false, false, "127.0.0.1", 1234},
|
{server.Config{BindHost: "localhost", Host: "should-not-be-used", Port: "1234"}, false, false, "127.0.0.1", 1234},
|
||||||
{server.Config{BindHost: "should-not-resolve", Host: "localhost", Port: "1234"}, true, false, "0.0.0.0", 1234},
|
{server.Config{BindHost: "should-not-resolve", Host: "localhost", Port: "1234"}, true, false, "<nil>", 1234},
|
||||||
} {
|
} {
|
||||||
actualAddr, warnErr, fatalErr := resolveAddr(test.config)
|
actualAddr, warnErr, fatalErr := resolveAddr(test.config)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import "io"
|
||||||
// If checkDirectives is true, only valid directives will be allowed
|
// If checkDirectives is true, only valid directives will be allowed
|
||||||
// otherwise we consider it a parse error. Server blocks are returned
|
// otherwise we consider it a parse error. Server blocks are returned
|
||||||
// in the order in which they appear.
|
// in the order in which they appear.
|
||||||
func ServerBlocks(filename string, input io.Reader, checkDirectives bool) ([]serverBlock, error) {
|
func ServerBlocks(filename string, input io.Reader, checkDirectives bool) ([]ServerBlock, error) {
|
||||||
p := parser{Dispenser: NewDispenser(filename, input)}
|
p := parser{Dispenser: NewDispenser(filename, input)}
|
||||||
p.checkDirectives = checkDirectives
|
p.checkDirectives = checkDirectives
|
||||||
blocks, err := p.parseAll()
|
blocks, err := p.parseAll()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package parse
|
package parse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -9,13 +10,13 @@ import (
|
||||||
|
|
||||||
type parser struct {
|
type parser struct {
|
||||||
Dispenser
|
Dispenser
|
||||||
block serverBlock // current server block being parsed
|
block ServerBlock // current server block being parsed
|
||||||
eof bool // if we encounter a valid EOF in a hard place
|
eof bool // if we encounter a valid EOF in a hard place
|
||||||
checkDirectives bool // if true, directives must be known
|
checkDirectives bool // if true, directives must be known
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) parseAll() ([]serverBlock, error) {
|
func (p *parser) parseAll() ([]ServerBlock, error) {
|
||||||
var blocks []serverBlock
|
var blocks []ServerBlock
|
||||||
|
|
||||||
for p.Next() {
|
for p.Next() {
|
||||||
err := p.parseOne()
|
err := p.parseOne()
|
||||||
|
@ -31,7 +32,7 @@ func (p *parser) parseAll() ([]serverBlock, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) parseOne() error {
|
func (p *parser) parseOne() error {
|
||||||
p.block = serverBlock{Tokens: make(map[string][]token)}
|
p.block = ServerBlock{Tokens: make(map[string][]token)}
|
||||||
|
|
||||||
err := p.begin()
|
err := p.begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -99,11 +100,11 @@ func (p *parser) addresses() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse and save this address
|
// Parse and save this address
|
||||||
host, port, err := standardAddress(tkn)
|
addr, err := standardAddress(tkn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.block.Addresses = append(p.block.Addresses, address{host, port})
|
p.block.Addresses = append(p.block.Addresses, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advance token and possibly break out of loop or return error
|
// Advance token and possibly break out of loop or return error
|
||||||
|
@ -273,39 +274,57 @@ func (p *parser) closeCurlyBrace() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// standardAddress turns the accepted host and port patterns
|
// standardAddress parses an address string into a structured format with separate
|
||||||
// into a format accepted by net.Dial.
|
// scheme, host, and port portions, as well as the original input string.
|
||||||
func standardAddress(str string) (host, port string, err error) {
|
func standardAddress(str string) (address, error) {
|
||||||
var schemePort, splitPort string
|
var scheme string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// first check for scheme and strip it off
|
||||||
|
input := str
|
||||||
if strings.HasPrefix(str, "https://") {
|
if strings.HasPrefix(str, "https://") {
|
||||||
schemePort = "https"
|
scheme = "https"
|
||||||
str = str[8:]
|
str = str[8:]
|
||||||
} else if strings.HasPrefix(str, "http://") {
|
} else if strings.HasPrefix(str, "http://") {
|
||||||
schemePort = "http"
|
scheme = "http"
|
||||||
str = str[7:]
|
str = str[7:]
|
||||||
}
|
}
|
||||||
|
|
||||||
host, splitPort, err = net.SplitHostPort(str)
|
// separate host and port
|
||||||
|
host, port, err := net.SplitHostPort(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
host, splitPort, err = net.SplitHostPort(str + ":") // tack on empty port
|
host, port, err = net.SplitHostPort(str + ":")
|
||||||
}
|
// no error check here; return err at end of function
|
||||||
if err != nil {
|
|
||||||
// ¯\_(ツ)_/¯
|
|
||||||
host = str
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if splitPort != "" {
|
// see if we can set port based off scheme
|
||||||
port = splitPort
|
if port == "" {
|
||||||
} else {
|
if scheme == "http" {
|
||||||
port = schemePort
|
port = "80"
|
||||||
|
} else if scheme == "https" {
|
||||||
|
port = "443"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
// repeated or conflicting scheme is confusing, so error
|
||||||
|
if scheme != "" && (port == "http" || port == "https") {
|
||||||
|
return address{}, fmt.Errorf("[%s] scheme specified twice in address", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// standardize http and https ports to their respective port numbers
|
||||||
|
if port == "http" {
|
||||||
|
scheme = "http"
|
||||||
|
port = "80"
|
||||||
|
} else if port == "https" {
|
||||||
|
scheme = "https"
|
||||||
|
port = "443"
|
||||||
|
}
|
||||||
|
|
||||||
|
return address{Original: input, Scheme: scheme, Host: host, Port: port}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// replaceEnvVars replaces environment variables that appear in the token
|
// replaceEnvVars replaces environment variables that appear in the token
|
||||||
// and understands both the Unix $SYNTAX and Windows %SYNTAX%.
|
// and understands both the $UNIX and %WINDOWS% syntaxes.
|
||||||
func replaceEnvVars(s string) string {
|
func replaceEnvVars(s string) string {
|
||||||
s = replaceEnvReferences(s, "{%", "%}")
|
s = replaceEnvReferences(s, "{%", "%}")
|
||||||
s = replaceEnvReferences(s, "{$", "}")
|
s = replaceEnvReferences(s, "{$", "}")
|
||||||
|
@ -330,26 +349,26 @@ func replaceEnvReferences(s, refStart, refEnd string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// serverBlock associates tokens with a list of addresses
|
// ServerBlock associates tokens with a list of addresses
|
||||||
// and groups tokens by directive name.
|
// and groups tokens by directive name.
|
||||||
serverBlock struct {
|
ServerBlock struct {
|
||||||
Addresses []address
|
Addresses []address
|
||||||
Tokens map[string][]token
|
Tokens map[string][]token
|
||||||
}
|
}
|
||||||
|
|
||||||
address struct {
|
address struct {
|
||||||
Host, Port string
|
Original, Scheme, Host, Port string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// HostList converts the list of addresses (hosts)
|
// HostList converts the list of addresses that are
|
||||||
// that are associated with this server block into
|
// associated with this server block into a slice of
|
||||||
// a slice of strings. Each string is a host:port
|
// strings, where each address is as it was originally
|
||||||
// combination.
|
// read from the input.
|
||||||
func (sb serverBlock) HostList() []string {
|
func (sb ServerBlock) HostList() []string {
|
||||||
sbHosts := make([]string, len(sb.Addresses))
|
sbHosts := make([]string, len(sb.Addresses))
|
||||||
for j, addr := range sb.Addresses {
|
for j, addr := range sb.Addresses {
|
||||||
sbHosts[j] = net.JoinHostPort(addr.Host, addr.Port)
|
sbHosts[j] = addr.Original
|
||||||
}
|
}
|
||||||
return sbHosts
|
return sbHosts
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,51 +8,55 @@ import (
|
||||||
|
|
||||||
func TestStandardAddress(t *testing.T) {
|
func TestStandardAddress(t *testing.T) {
|
||||||
for i, test := range []struct {
|
for i, test := range []struct {
|
||||||
input string
|
input string
|
||||||
host, port string
|
scheme, host, port string
|
||||||
shouldErr bool
|
shouldErr bool
|
||||||
}{
|
}{
|
||||||
{`localhost`, "localhost", "", false},
|
{`localhost`, "", "localhost", "", false},
|
||||||
{`localhost:1234`, "localhost", "1234", false},
|
{`localhost:1234`, "", "localhost", "1234", false},
|
||||||
{`localhost:`, "localhost", "", false},
|
{`localhost:`, "", "localhost", "", false},
|
||||||
{`0.0.0.0`, "0.0.0.0", "", false},
|
{`0.0.0.0`, "", "0.0.0.0", "", false},
|
||||||
{`127.0.0.1:1234`, "127.0.0.1", "1234", false},
|
{`127.0.0.1:1234`, "", "127.0.0.1", "1234", false},
|
||||||
{`:1234`, "", "1234", false},
|
{`:1234`, "", "", "1234", false},
|
||||||
{`[::1]`, "::1", "", false},
|
{`[::1]`, "", "::1", "", false},
|
||||||
{`[::1]:1234`, "::1", "1234", false},
|
{`[::1]:1234`, "", "::1", "1234", false},
|
||||||
{`:`, "", "", false},
|
{`:`, "", "", "", false},
|
||||||
{`localhost:http`, "localhost", "http", false},
|
{`localhost:http`, "http", "localhost", "80", false},
|
||||||
{`localhost:https`, "localhost", "https", false},
|
{`localhost:https`, "https", "localhost", "443", false},
|
||||||
{`:http`, "", "http", false},
|
{`:http`, "http", "", "80", false},
|
||||||
{`:https`, "", "https", false},
|
{`:https`, "https", "", "443", false},
|
||||||
{`http://localhost`, "localhost", "http", false},
|
{`http://localhost:https`, "", "", "", true}, // conflict
|
||||||
{`https://localhost`, "localhost", "https", false},
|
{`http://localhost:http`, "", "", "", true}, // repeated scheme
|
||||||
{`http://127.0.0.1`, "127.0.0.1", "http", false},
|
{`http://localhost`, "http", "localhost", "80", false},
|
||||||
{`https://127.0.0.1`, "127.0.0.1", "https", false},
|
{`https://localhost`, "https", "localhost", "443", false},
|
||||||
{`http://[::1]`, "::1", "http", false},
|
{`http://127.0.0.1`, "http", "127.0.0.1", "80", false},
|
||||||
{`http://localhost:1234`, "localhost", "1234", false},
|
{`https://127.0.0.1`, "https", "127.0.0.1", "443", false},
|
||||||
{`https://127.0.0.1:1234`, "127.0.0.1", "1234", false},
|
{`http://[::1]`, "http", "::1", "80", false},
|
||||||
{`http://[::1]:1234`, "::1", "1234", false},
|
{`http://localhost:1234`, "http", "localhost", "1234", false},
|
||||||
{``, "", "", false},
|
{`https://127.0.0.1:1234`, "https", "127.0.0.1", "1234", false},
|
||||||
{`::1`, "::1", "", true},
|
{`http://[::1]:1234`, "http", "::1", "1234", false},
|
||||||
{`localhost::`, "localhost::", "", true},
|
{``, "", "", "", false},
|
||||||
{`#$%@`, "#$%@", "", true},
|
{`::1`, "", "::1", "", true},
|
||||||
|
{`localhost::`, "", "localhost::", "", true},
|
||||||
|
{`#$%@`, "", "#$%@", "", true},
|
||||||
} {
|
} {
|
||||||
host, port, err := standardAddress(test.input)
|
actual, err := standardAddress(test.input)
|
||||||
|
|
||||||
if err != nil && !test.shouldErr {
|
if err != nil && !test.shouldErr {
|
||||||
t.Errorf("Test %d: Expected no error, but had error: %v", i, err)
|
t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
|
||||||
}
|
}
|
||||||
if err == nil && test.shouldErr {
|
if err == nil && test.shouldErr {
|
||||||
t.Errorf("Test %d: Expected error, but had none", i)
|
t.Errorf("Test %d (%s): Expected error, but had none", i, test.input)
|
||||||
}
|
}
|
||||||
|
|
||||||
if host != test.host {
|
if actual.Scheme != test.scheme {
|
||||||
t.Errorf("Test %d: Expected host '%s', got '%s'", i, test.host, host)
|
t.Errorf("Test %d (%s): Expected scheme '%s', got '%s'", i, test.input, test.scheme, actual.Scheme)
|
||||||
}
|
}
|
||||||
|
if actual.Host != test.host {
|
||||||
if port != test.port {
|
t.Errorf("Test %d (%s): Expected host '%s', got '%s'", i, test.input, test.host, actual.Host)
|
||||||
t.Errorf("Test %d: Expected port '%s', got '%s'", i, test.port, port)
|
}
|
||||||
|
if actual.Port != test.port {
|
||||||
|
t.Errorf("Test %d (%s): Expected port '%s', got '%s'", i, test.input, test.port, actual.Port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +64,7 @@ func TestStandardAddress(t *testing.T) {
|
||||||
func TestParseOneAndImport(t *testing.T) {
|
func TestParseOneAndImport(t *testing.T) {
|
||||||
setupParseTests()
|
setupParseTests()
|
||||||
|
|
||||||
testParseOne := func(input string) (serverBlock, error) {
|
testParseOne := func(input string) (ServerBlock, error) {
|
||||||
p := testParser(input)
|
p := testParser(input)
|
||||||
p.Next() // parseOne doesn't call Next() to start, so we must
|
p.Next() // parseOne doesn't call Next() to start, so we must
|
||||||
err := p.parseOne()
|
err := p.parseOne()
|
||||||
|
@ -74,19 +78,19 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
tokens map[string]int // map of directive name to number of tokens expected
|
tokens map[string]int // map of directive name to number of tokens expected
|
||||||
}{
|
}{
|
||||||
{`localhost`, false, []address{
|
{`localhost`, false, []address{
|
||||||
{"localhost", ""},
|
{"localhost", "", "localhost", ""},
|
||||||
}, map[string]int{}},
|
}, map[string]int{}},
|
||||||
|
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1`, false, []address{
|
dir1`, false, []address{
|
||||||
{"localhost", ""},
|
{"localhost", "", "localhost", ""},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 1,
|
"dir1": 1,
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{`localhost:1234
|
{`localhost:1234
|
||||||
dir1 foo bar`, false, []address{
|
dir1 foo bar`, false, []address{
|
||||||
{"localhost", "1234"},
|
{"localhost:1234", "", "localhost", "1234"},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 3,
|
"dir1": 3,
|
||||||
}},
|
}},
|
||||||
|
@ -94,7 +98,7 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
{`localhost {
|
{`localhost {
|
||||||
dir1
|
dir1
|
||||||
}`, false, []address{
|
}`, false, []address{
|
||||||
{"localhost", ""},
|
{"localhost", "", "localhost", ""},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 1,
|
"dir1": 1,
|
||||||
}},
|
}},
|
||||||
|
@ -103,7 +107,7 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
dir1 foo bar
|
dir1 foo bar
|
||||||
dir2
|
dir2
|
||||||
}`, false, []address{
|
}`, false, []address{
|
||||||
{"localhost", "1234"},
|
{"localhost:1234", "", "localhost", "1234"},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 3,
|
"dir1": 3,
|
||||||
"dir2": 1,
|
"dir2": 1,
|
||||||
|
@ -111,8 +115,8 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
|
|
||||||
{`http://localhost https://localhost
|
{`http://localhost https://localhost
|
||||||
dir1 foo bar`, false, []address{
|
dir1 foo bar`, false, []address{
|
||||||
{"localhost", "http"},
|
{"http://localhost", "http", "localhost", "80"},
|
||||||
{"localhost", "https"},
|
{"https://localhost", "https", "localhost", "443"},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 3,
|
"dir1": 3,
|
||||||
}},
|
}},
|
||||||
|
@ -120,8 +124,8 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
{`http://localhost https://localhost {
|
{`http://localhost https://localhost {
|
||||||
dir1 foo bar
|
dir1 foo bar
|
||||||
}`, false, []address{
|
}`, false, []address{
|
||||||
{"localhost", "http"},
|
{"http://localhost", "http", "localhost", "80"},
|
||||||
{"localhost", "https"},
|
{"https://localhost", "https", "localhost", "443"},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 3,
|
"dir1": 3,
|
||||||
}},
|
}},
|
||||||
|
@ -129,22 +133,22 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
{`http://localhost, https://localhost {
|
{`http://localhost, https://localhost {
|
||||||
dir1 foo bar
|
dir1 foo bar
|
||||||
}`, false, []address{
|
}`, false, []address{
|
||||||
{"localhost", "http"},
|
{"http://localhost", "http", "localhost", "80"},
|
||||||
{"localhost", "https"},
|
{"https://localhost", "https", "localhost", "443"},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 3,
|
"dir1": 3,
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{`http://localhost, {
|
{`http://localhost, {
|
||||||
}`, true, []address{
|
}`, true, []address{
|
||||||
{"localhost", "http"},
|
{"http://localhost", "http", "localhost", "80"},
|
||||||
}, map[string]int{}},
|
}, map[string]int{}},
|
||||||
|
|
||||||
{`host1:80, http://host2.com
|
{`host1:80, http://host2.com
|
||||||
dir1 foo bar
|
dir1 foo bar
|
||||||
dir2 baz`, false, []address{
|
dir2 baz`, false, []address{
|
||||||
{"host1", "80"},
|
{"host1:80", "", "host1", "80"},
|
||||||
{"host2.com", "http"},
|
{"http://host2.com", "http", "host2.com", "80"},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 3,
|
"dir1": 3,
|
||||||
"dir2": 2,
|
"dir2": 2,
|
||||||
|
@ -153,9 +157,9 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
{`http://host1.com,
|
{`http://host1.com,
|
||||||
http://host2.com,
|
http://host2.com,
|
||||||
https://host3.com`, false, []address{
|
https://host3.com`, false, []address{
|
||||||
{"host1.com", "http"},
|
{"http://host1.com", "http", "host1.com", "80"},
|
||||||
{"host2.com", "http"},
|
{"http://host2.com", "http", "host2.com", "80"},
|
||||||
{"host3.com", "https"},
|
{"https://host3.com", "https", "host3.com", "443"},
|
||||||
}, map[string]int{}},
|
}, map[string]int{}},
|
||||||
|
|
||||||
{`http://host1.com:1234, https://host2.com
|
{`http://host1.com:1234, https://host2.com
|
||||||
|
@ -163,8 +167,8 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
bar baz
|
bar baz
|
||||||
}
|
}
|
||||||
dir2`, false, []address{
|
dir2`, false, []address{
|
||||||
{"host1.com", "1234"},
|
{"http://host1.com:1234", "http", "host1.com", "1234"},
|
||||||
{"host2.com", "https"},
|
{"https://host2.com", "https", "host2.com", "443"},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 6,
|
"dir1": 6,
|
||||||
"dir2": 1,
|
"dir2": 1,
|
||||||
|
@ -177,7 +181,7 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
dir2 {
|
dir2 {
|
||||||
foo bar
|
foo bar
|
||||||
}`, false, []address{
|
}`, false, []address{
|
||||||
{"127.0.0.1", ""},
|
{"127.0.0.1", "", "127.0.0.1", ""},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 5,
|
"dir1": 5,
|
||||||
"dir2": 5,
|
"dir2": 5,
|
||||||
|
@ -185,13 +189,13 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
|
|
||||||
{`127.0.0.1
|
{`127.0.0.1
|
||||||
unknown_directive`, true, []address{
|
unknown_directive`, true, []address{
|
||||||
{"127.0.0.1", ""},
|
{"127.0.0.1", "", "127.0.0.1", ""},
|
||||||
}, map[string]int{}},
|
}, map[string]int{}},
|
||||||
|
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1 {
|
dir1 {
|
||||||
foo`, true, []address{
|
foo`, true, []address{
|
||||||
{"localhost", ""},
|
{"localhost", "", "localhost", ""},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 3,
|
"dir1": 3,
|
||||||
}},
|
}},
|
||||||
|
@ -199,7 +203,7 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1 {
|
dir1 {
|
||||||
}`, false, []address{
|
}`, false, []address{
|
||||||
{"localhost", ""},
|
{"localhost", "", "localhost", ""},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 3,
|
"dir1": 3,
|
||||||
}},
|
}},
|
||||||
|
@ -207,7 +211,7 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1 {
|
dir1 {
|
||||||
} }`, true, []address{
|
} }`, true, []address{
|
||||||
{"localhost", ""},
|
{"localhost", "", "localhost", ""},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 3,
|
"dir1": 3,
|
||||||
}},
|
}},
|
||||||
|
@ -219,7 +223,7 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dir2 foo bar`, false, []address{
|
dir2 foo bar`, false, []address{
|
||||||
{"localhost", ""},
|
{"localhost", "", "localhost", ""},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 7,
|
"dir1": 7,
|
||||||
"dir2": 3,
|
"dir2": 3,
|
||||||
|
@ -230,7 +234,7 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1 arg1
|
dir1 arg1
|
||||||
import import_test1.txt`, false, []address{
|
import import_test1.txt`, false, []address{
|
||||||
{"localhost", ""},
|
{"localhost", "", "localhost", ""},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 2,
|
"dir1": 2,
|
||||||
"dir2": 3,
|
"dir2": 3,
|
||||||
|
@ -238,7 +242,7 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{`import import_test2.txt`, false, []address{
|
{`import import_test2.txt`, false, []address{
|
||||||
{"host1", ""},
|
{"host1", "", "host1", ""},
|
||||||
}, map[string]int{
|
}, map[string]int{
|
||||||
"dir1": 1,
|
"dir1": 1,
|
||||||
"dir2": 2,
|
"dir2": 2,
|
||||||
|
@ -301,23 +305,23 @@ func TestParseAll(t *testing.T) {
|
||||||
addresses [][]address // addresses per server block, in order
|
addresses [][]address // addresses per server block, in order
|
||||||
}{
|
}{
|
||||||
{`localhost`, false, [][]address{
|
{`localhost`, false, [][]address{
|
||||||
{{"localhost", ""}},
|
{{"localhost", "", "localhost", ""}},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{`localhost:1234`, false, [][]address{
|
{`localhost:1234`, false, [][]address{
|
||||||
[]address{{"localhost", "1234"}},
|
[]address{{"localhost:1234", "", "localhost", "1234"}},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{`localhost:1234 {
|
{`localhost:1234 {
|
||||||
}
|
}
|
||||||
localhost:2015 {
|
localhost:2015 {
|
||||||
}`, false, [][]address{
|
}`, false, [][]address{
|
||||||
[]address{{"localhost", "1234"}},
|
[]address{{"localhost:1234", "", "localhost", "1234"}},
|
||||||
[]address{{"localhost", "2015"}},
|
[]address{{"localhost:2015", "", "localhost", "2015"}},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{`localhost:1234, http://host2`, false, [][]address{
|
{`localhost:1234, http://host2`, false, [][]address{
|
||||||
[]address{{"localhost", "1234"}, {"host2", "http"}},
|
[]address{{"localhost:1234", "", "localhost", "1234"}, {"http://host2", "http", "host2", "80"}},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{`localhost:1234, http://host2,`, true, [][]address{}},
|
{`localhost:1234, http://host2,`, true, [][]address{}},
|
||||||
|
@ -326,8 +330,8 @@ func TestParseAll(t *testing.T) {
|
||||||
}
|
}
|
||||||
https://host3.com, https://host4.com {
|
https://host3.com, https://host4.com {
|
||||||
}`, false, [][]address{
|
}`, false, [][]address{
|
||||||
[]address{{"host1.com", "http"}, {"host2.com", "http"}},
|
[]address{{"http://host1.com", "http", "host1.com", "80"}, {"http://host2.com", "http", "host2.com", "80"}},
|
||||||
[]address{{"host3.com", "https"}, {"host4.com", "https"}},
|
[]address{{"https://host3.com", "https", "host3.com", "443"}, {"https://host4.com", "https", "host4.com", "443"}},
|
||||||
}},
|
}},
|
||||||
} {
|
} {
|
||||||
p := testParser(test.input)
|
p := testParser(test.input)
|
||||||
|
|
|
@ -17,6 +17,9 @@ type Config struct {
|
||||||
// The port to listen on
|
// The port to listen on
|
||||||
Port string
|
Port string
|
||||||
|
|
||||||
|
// The protocol (http/https) to serve with this config; only set if user explicitly specifies it
|
||||||
|
Scheme string
|
||||||
|
|
||||||
// The directory from which to serve files
|
// The directory from which to serve files
|
||||||
Root string
|
Root string
|
||||||
|
|
||||||
|
@ -62,10 +65,11 @@ func (c Config) Address() string {
|
||||||
|
|
||||||
// TLSConfig describes how TLS should be configured and used.
|
// TLSConfig describes how TLS should be configured and used.
|
||||||
type TLSConfig struct {
|
type TLSConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Certificate string
|
Certificate string
|
||||||
Key string
|
Key string
|
||||||
LetsEncryptEmail string
|
LetsEncryptEmail string
|
||||||
|
//DisableHTTPRedir bool // TODO: not a good idea - should we really allow it?
|
||||||
OCSPStaple []byte
|
OCSPStaple []byte
|
||||||
Ciphers []uint16
|
Ciphers []uint16
|
||||||
ProtocolMinVersion uint16
|
ProtocolMinVersion uint16
|
||||||
|
|
|
@ -18,8 +18,8 @@ func TestConfigAddress(t *testing.T) {
|
||||||
t.Errorf("Expected '%s' but got '%s'", expected, actual)
|
t.Errorf("Expected '%s' but got '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg = Config{Host: "::1", Port: "https"}
|
cfg = Config{Host: "::1", Port: "443"}
|
||||||
if actual, expected := cfg.Address(), "[::1]:https"; expected != actual {
|
if actual, expected := cfg.Address(), "[::1]:443"; expected != actual {
|
||||||
t.Errorf("Expected '%s' but got '%s'", expected, actual)
|
t.Errorf("Expected '%s' but got '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue