@ -41,7 +41,7 @@ type ServerType struct {
func ( st ServerType ) Setup ( originalServerBlocks [ ] caddyfile . ServerBlock ,
options map [ string ] interface { } ) ( * caddy . Config , [ ] caddyconfig . Warning , error ) {
var warnings [ ] caddyconfig . Warning
groupCounter := new ( int )
gc := counter { new ( int ) }
var serverBlocks [ ] serverBlock
for _ , sblock := range originalServerBlocks {
@ -146,7 +146,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
warnings : & warnings ,
matcherDefs : matcherDefs ,
parentBlock : sb . block ,
groupCounter : groupCounter ,
groupCounter : gc ,
} )
if err != nil {
return nil , warnings , fmt . Errorf ( "parsing caddyfile tokens for '%s': %v" , dir , err )
@ -169,7 +169,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
// each pairing of listener addresses to list of server
// blocks is basically a server definition
servers , err := st . serversFromPairings ( pairings , options , & warnings )
servers , err := st . serversFromPairings ( pairings , options , & warnings , gc )
if err != nil {
return nil , warnings , err
}
@ -306,6 +306,7 @@ func (st *ServerType) serversFromPairings(
pairings [ ] sbAddrAssociation ,
options map [ string ] interface { } ,
warnings * [ ] caddyconfig . Warning ,
groupCounter counter ,
) ( map [ string ] * caddyhttp . Server , error ) {
servers := make ( map [ string ] * caddyhttp . Server )
@ -320,32 +321,32 @@ func (st *ServerType) serversFromPairings(
// case their matchers overlap; we do this somewhat naively by
// descending sort by length of host then path
sort . SliceStable ( p . serverBlocks , func ( i , j int ) bool {
// TODO: we could pre-process the length s for efficiency,
// TODO: we could pre-process the specificitie s for efficiency,
// but I don't expect many blocks will have SO many keys...
var iLongestPath , jLongestPath string
var iLongestHost , jLongestHost string
for _ , key := range p . serverBlocks [ i ] . block . Keys {
addr , _ := ParseAddress ( key )
if length ( addr . Host ) > length ( iLongestHost ) {
if specificity ( addr . Host ) > specificity ( iLongestHost ) {
iLongestHost = addr . Host
}
if len ( addr . Path ) > len ( iLongestPath ) {
if specificity ( addr . Path ) > specificity ( iLongestPath ) {
iLongestPath = addr . Path
}
}
for _ , key := range p . serverBlocks [ j ] . block . Keys {
addr , _ := ParseAddress ( key )
if length ( addr . Host ) > length ( jLongestHost ) {
if specificity ( addr . Host ) > specificity ( jLongestHost ) {
jLongestHost = addr . Host
}
if len ( addr . Path ) > len ( jLongestPath ) {
if specificity ( addr . Path ) > specificity ( jLongestPath ) {
jLongestPath = addr . Path
}
}
if length ( iLongestHost ) == length ( jLongestHost ) {
if specificity ( iLongestHost ) == specificity ( jLongestHost ) {
return len ( iLongestPath ) > len ( jLongestPath )
}
return length ( iLongestHost ) > length ( jLongestHost )
return specificity ( iLongestHost ) > specificity ( jLongestHost )
} )
// create a subroute for each site in the server block
@ -355,10 +356,7 @@ func (st *ServerType) serversFromPairings(
return nil , fmt . Errorf ( "server block %v: compiling matcher sets: %v" , sblock . block . Keys , err )
}
siteSubroute := new ( caddyhttp . Subroute )
// tls: connection policies and toggle auto HTTPS
autoHTTPSQualifiedHosts , err := st . autoHTTPSHosts ( sblock )
if err != nil {
return nil , err
@ -391,38 +389,13 @@ func (st *ServerType) serversFromPairings(
// TODO: consolidate equal conn policies
}
// vars: make sure these are linked in first so future
// routes can use the variables they define
for _ , cfgVal := range sblock . pile [ "var" ] {
siteSubroute . Routes = append ( siteSubroute . Routes , cfgVal . Value . ( caddyhttp . Route ) )
}
// set up each handler directive, making sure to honor directive order
dirRoutes := sblock . pile [ "route" ]
sortRoutes ( dirRoutes )
// add all the routes piled in from directives
for _ , r := range dirRoutes {
// as a special case, group rewrite directives so that they are mutually exclusive;
// this means that only the first matching rewrite will be evaluated, and that's
// probably a good thing, since there should never be a need to do more than one
// rewrite (I think?), and cascading rewrites smell bad... imagine these rewrites:
// rewrite /docs/json/* /docs/json/index.html
// rewrite /docs/* /docs/index.html
// (We use this on the Caddy website, or at least we did once.) The first rewrite's
// result is also matched by the second rewrite, making the first rewrite pointless.
// See issue #2959.
if r . directive == "rewrite" {
route := r . Value . ( caddyhttp . Route )
route . Group = "rewriting"
r . Value = route
}
siteSubroute . Routes = append ( siteSubroute . Routes , r . Value . ( caddyhttp . Route ) )
siteSubroute , err := buildSubroute ( dirRoutes , groupCounter )
if err != nil {
return nil , err
}
siteSubroute . Routes = consolidateRoutes ( siteSubroute . Routes )
if len ( matcherSetsEnc ) == 0 && len ( p . serverBlocks ) == 1 {
// no need to wrap the handlers in a subroute if this is
// the only server block and there is no matcher for it
@ -446,6 +419,98 @@ func (st *ServerType) serversFromPairings(
return servers , nil
}
func buildSubroute ( routes [ ] ConfigValue , groupCounter counter ) ( * caddyhttp . Subroute , error ) {
for _ , val := range routes {
if ! directiveIsOrdered ( val . directive ) {
return nil , fmt . Errorf ( "directive '%s' is not ordered, so it cannot be used here" , val . directive )
}
}
sortRoutes ( routes )
subroute := new ( caddyhttp . Subroute )
// get a group name for rewrite directives, if needed
var rewriteGroupName string
var rewriteCount int
for _ , r := range routes {
if r . directive == "rewrite" {
rewriteCount ++
if rewriteCount > 1 {
break
}
}
}
if rewriteCount > 1 {
rewriteGroupName = groupCounter . nextGroup ( )
}
// get a group name for handle blocks, if needed
var handleGroupName string
var handleCount int
for _ , r := range routes {
if r . directive == "handle" {
handleCount ++
if handleCount > 1 {
break
}
}
}
if handleCount > 1 {
handleGroupName = groupCounter . nextGroup ( )
}
// add all the routes piled in from directives
for _ , r := range routes {
// as a special case, group rewrite directives so that they are mutually exclusive;
// this means that only the first matching rewrite will be evaluated, and that's
// probably a good thing, since there should never be a need to do more than one
// rewrite (I think?), and cascading rewrites smell bad... imagine these rewrites:
// rewrite /docs/json/* /docs/json/index.html
// rewrite /docs/* /docs/index.html
// (We use this on the Caddy website, or at least we did once.) The first rewrite's
// result is also matched by the second rewrite, making the first rewrite pointless.
// See issue #2959.
if r . directive == "rewrite" {
route := r . Value . ( caddyhttp . Route )
route . Group = rewriteGroupName
r . Value = route
}
// handle blocks are also mutually exclusive by definition
if r . directive == "handle" {
route := r . Value . ( caddyhttp . Route )
route . Group = handleGroupName
r . Value = route
}
switch route := r . Value . ( type ) {
case caddyhttp . Subroute :
// if a route-class config value is actually a Subroute handler
// with nothing but a list of routes, then it is the intention
// of the directive to keep these handlers together and in this
// same order, but not necessarily in a subroute (if it wanted
// to keep them in a subroute, the directive would have returned
// a route with a Subroute as its handler); this is useful to
// keep multiple handlers/routes together and in the same order
// so that the sorting procedure we did above doesn't reorder them
if route . Errors != nil {
// if error handlers are also set, this is confusing; it's
// probably supposed to be wrapped in a Route and encoded
// as a regular handler route... programmer error.
panic ( "found subroute with more than just routes; perhaps it should have been wrapped in a route?" )
}
subroute . Routes = append ( subroute . Routes , route . Routes ... )
case caddyhttp . Route :
subroute . Routes = append ( subroute . Routes , route )
}
}
subroute . Routes = consolidateRoutes ( subroute . Routes )
return subroute , nil
}
func ( st ServerType ) autoHTTPSHosts ( sb serverBlock ) ( [ ] string , error ) {
// get the hosts for this server block...
hosts , err := st . hostsFromServerBlockKeys ( sb . block )
@ -521,7 +586,6 @@ func matcherSetFromMatcherToken(
}
return m , true , nil
}
return nil , false , nil
}
@ -659,14 +723,40 @@ func tryInt(val interface{}, warnings *[]caddyconfig.Warning) int {
return intVal
}
// length returns len(s) minus any wildcards (*). Basically,
// it's a length count that penalizes the use of wildcards.
// This is useful for comparing hostnames, but probably not
// paths so much (for example, '*.example.com' is clearly
// less specific than 'a.example.com', but is '/a' more or
// less specific than '/a*'?).
func length ( s string ) int {
return len ( s ) - strings . Count ( s , "*" )
// specifity returns len(s) minus any wildcards (*) and
// placeholders ({...}). Basically, it's a length count
// that penalizes the use of wildcards and placeholders.
// This is useful for comparing hostnames and paths.
// However, wildcards in paths are not a sure answer to
// the question of specificity. For exmaple,
// '*.example.com' is clearly less specific than
// 'a.example.com', but is '/a' more or less specific
// than '/a*'?
func specificity ( s string ) int {
l := len ( s ) - strings . Count ( s , "*" )
for len ( s ) > 0 {
start := strings . Index ( s , "{" )
if start < 0 {
return l
}
end := strings . Index ( s [ start : ] , "}" ) + start + 1
if end <= start {
return l
}
l -= end - start
s = s [ end : ]
}
return l
}
type counter struct {
n * int
}
func ( c counter ) nextGroup ( ) string {
name := fmt . Sprintf ( "group%d" , * c . n )
* c . n ++
return name
}
type matcherSetAndTokens struct {