@ -114,19 +114,31 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
}
// extract matcher definitions
d := sb . block . DispenseDirective ( "matcher" )
matcherDefs , err := parseMatcherDefinitions ( d )
matcherDefs := make ( map [ string ] caddy . ModuleMap )
for _ , segment := range sb . block . Segments {
if dir := segment . Directive ( ) ; strings . HasPrefix ( dir , matcherPrefix ) {
d := sb . block . DispenseDirective ( dir )
err := parseMatcherDefinitions ( d , matcherDefs )
if err != nil {
return nil , warnings , err
}
}
}
for _ , segment := range sb . block . Segments {
dir := segment . Directive ( )
if dir == "matcher" {
// TODO: This is a special case because we pre-processed it; handle this better
if strings . HasPrefix ( dir , matcherPrefix ) {
// matcher definitions were pre-processed
continue
}
if dirFunc , ok := registeredDirectives [ dir ] ; ok {
dirFunc , ok := registeredDirectives [ dir ]
if ! ok {
tkn := segment [ 0 ]
return nil , warnings , fmt . Errorf ( "%s:%d: unrecognized directive: %s" , tkn . File , tkn . Line , dir )
}
results , err := dirFunc ( Helper {
Dispenser : caddyfile . NewDispenser ( segment ) ,
options : options ,
@ -141,10 +153,6 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
result . directive = dir
sb . pile [ result . Class ] = append ( sb . pile [ result . Class ] , result )
}
} else {
tkn := segment [ 0 ]
return nil , warnings , fmt . Errorf ( "%s:%d: unrecognized directive: %s" , tkn . File , tkn . Line , dir )
}
}
}
@ -372,10 +380,63 @@ func (st *ServerType) serversFromPairings(
}
sort . SliceStable ( dirRoutes , func ( i , j int ) bool {
iDir , jDir := dirRoutes [ i ] . directive , dirRoutes [ j ] . directive
if iDir == jDir {
// TODO: we really need to refactor this into a separate function or method...
// sub-sort by path matcher length, if there's only one
iRoute := dirRoutes [ i ] . Value . ( caddyhttp . Route )
jRoute := dirRoutes [ j ] . Value . ( caddyhttp . Route )
if len ( iRoute . MatcherSetsRaw ) == 1 && len ( jRoute . MatcherSetsRaw ) == 1 {
// for slightly better efficiency, only decode the path matchers once,
// then just store them arbitrarily in the decoded MatcherSets field,
// ours should be the only thing in there
var iPM , jPM caddyhttp . MatchPath
if len ( iRoute . MatcherSets ) == 1 {
iPM = iRoute . MatcherSets [ 0 ] [ 0 ] . ( caddyhttp . MatchPath )
}
if len ( jRoute . MatcherSets ) == 1 {
jPM = jRoute . MatcherSets [ 0 ] [ 0 ] . ( caddyhttp . MatchPath )
}
// if it's our first time seeing this route's path matcher, decode it
if iPM == nil {
var pathMatcher caddyhttp . MatchPath
_ = json . Unmarshal ( iRoute . MatcherSetsRaw [ 0 ] [ "path" ] , & pathMatcher )
iRoute . MatcherSets = caddyhttp . MatcherSets { { pathMatcher } }
iPM = pathMatcher
}
if jPM == nil {
var pathMatcher caddyhttp . MatchPath
_ = json . Unmarshal ( jRoute . MatcherSetsRaw [ 0 ] [ "path" ] , & pathMatcher )
jRoute . MatcherSets = caddyhttp . MatcherSets { { pathMatcher } }
jPM = pathMatcher
}
// finally, if there is only one path in the
// matcher, sort by longer path first
if len ( iPM ) == 1 && len ( jPM ) == 1 {
return len ( iPM [ 0 ] ) > len ( jPM [ 0 ] )
}
}
}
return dirPositions [ iDir ] < dirPositions [ jDir ]
} )
}
// 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
}
handlerSubroute . Routes = append ( handlerSubroute . Routes , r . Value . ( caddyhttp . Route ) )
}
@ -480,17 +541,16 @@ func matcherSetFromMatcherToken(
if tkn . Text == "*" {
// match all requests == no matchers, so nothing to do
return nil , true , nil
} else if strings . HasPrefix ( tkn . Text , "/" ) || strings . HasPrefix ( tkn . Text , "=/" ) {
} else if strings . HasPrefix ( tkn . Text , "/" ) {
// convenient way to specify a single path match
return caddy . ModuleMap {
"path" : caddyconfig . JSON ( caddyhttp . MatchPath { tkn . Text } , warnings ) ,
} , true , nil
} else if strings . HasPrefix ( tkn . Text , "match:" ) {
} else if strings . HasPrefix ( tkn . Text , matcherPrefix ) {
// pre-defined matcher
matcherName := strings . TrimPrefix ( tkn . Text , "match:" )
m , ok := matcherDefs [ matcherName ]
m , ok := matcherDefs [ tkn . Text ]
if ! ok {
return nil , false , fmt . Errorf ( "unrecognized matcher name: %+v" , matcherName )
return nil , false , fmt . Errorf ( "unrecognized matcher name: %+v" , tkn . Text )
}
return m , true , nil
}
@ -577,35 +637,37 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
return matcherSetsEnc , nil
}
func parseMatcherDefinitions ( d * caddyfile . Dispenser ) ( map [ string ] caddy . ModuleMap , error ) {
matchers := make ( map [ string ] caddy . ModuleMap )
func parseMatcherDefinitions ( d * caddyfile . Dispenser , matchers map [ string ] caddy . ModuleMap ) error {
for d . Next ( ) {
definitionName := d . Val ( )
if _ , ok := matchers [ definitionName ] ; ok {
return fmt . Errorf ( "matcher is defined more than once: %s" , definitionName )
}
matchers [ definitionName ] = make ( caddy . ModuleMap )
for nesting := d . Nesting ( ) ; d . NextBlock ( nesting ) ; {
matcherName := d . Val ( )
mod , err := caddy . GetModule ( "http.matchers." + matcherName )
if err != nil {
return nil , fmt . Errorf ( "getting matcher module '%s': %v" , matcherName , err )
return fmt . Errorf ( "getting matcher module '%s': %v" , matcherName , err )
}
unm , ok := mod . New ( ) . ( caddyfile . Unmarshaler )
if ! ok {
return nil , fmt . Errorf ( "matcher module '%s' is not a Caddyfile unmarshaler" , matcherName )
return fmt . Errorf ( "matcher module '%s' is not a Caddyfile unmarshaler" , matcherName )
}
err = unm . UnmarshalCaddyfile ( d . NewFromNextTokens ( ) )
if err != nil {
return nil , err
return err
}
rm , ok := unm . ( caddyhttp . RequestMatcher )
if ! ok {
return nil , fmt . Errorf ( "matcher module '%s' is not a request matcher" , matcherName )
}
if _ , ok := matchers [ definitionName ] ; ! ok {
matchers [ definitionName ] = make ( caddy . ModuleMap )
return fmt . Errorf ( "matcher module '%s' is not a request matcher" , matcherName )
}
matchers [ definitionName ] [ matcherName ] = caddyconfig . JSON ( rm , nil )
}
}
return matchers , nil
return nil
}
func encodeMatcherSet ( matchers map [ string ] caddyhttp . RequestMatcher ) ( caddy . ModuleMap , error ) {
@ -643,5 +705,7 @@ type sbAddrAssociation struct {
serverBlocks [ ] serverBlock
}
const matcherPrefix = "@"
// Interface guard
var _ caddyfile . ServerType = ( * ServerType ) ( nil )