| 
									
										
										
										
											2019-08-09 12:05:47 -06:00
										 |  |  | // 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 rewrite | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2020-05-26 17:27:51 -04:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2019-12-12 15:46:13 -07:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2020-01-22 09:36:05 -07:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2019-12-12 15:46:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-26 17:27:51 -04:00
										 |  |  | 	"github.com/caddyserver/caddy/v2" | 
					
						
							|  |  |  | 	"github.com/caddyserver/caddy/v2/caddyconfig" | 
					
						
							| 
									
										
										
										
											2019-08-09 12:19:56 -06:00
										 |  |  | 	"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" | 
					
						
							| 
									
										
										
										
											2019-08-21 10:46:35 -06:00
										 |  |  | 	"github.com/caddyserver/caddy/v2/modules/caddyhttp" | 
					
						
							| 
									
										
										
										
											2019-08-09 12:05:47 -06:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-21 10:46:35 -06:00
										 |  |  | func init() { | 
					
						
							| 
									
										
										
										
											2024-01-15 11:57:08 -05:00
										 |  |  | 	httpcaddyfile.RegisterDirective("rewrite", parseCaddyfileRewrite) | 
					
						
							| 
									
										
										
										
											2022-01-18 14:17:35 -05:00
										 |  |  | 	httpcaddyfile.RegisterHandlerDirective("method", parseCaddyfileMethod) | 
					
						
							| 
									
										
										
										
											2020-03-19 11:51:28 -06:00
										 |  |  | 	httpcaddyfile.RegisterHandlerDirective("uri", parseCaddyfileURI) | 
					
						
							| 
									
										
										
										
											2020-05-26 17:27:51 -04:00
										 |  |  | 	httpcaddyfile.RegisterDirective("handle_path", parseCaddyfileHandlePath) | 
					
						
							| 
									
										
										
										
											2019-08-21 10:46:35 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-12 15:46:13 -07:00
										 |  |  | // parseCaddyfileRewrite sets up a basic rewrite handler from Caddyfile tokens. Syntax: | 
					
						
							| 
									
										
										
										
											2019-08-09 12:05:47 -06:00
										 |  |  | // | 
					
						
							| 
									
										
										
										
											2022-11-14 11:38:02 -05:00
										 |  |  | //	rewrite [<matcher>] <to> | 
					
						
							| 
									
										
										
										
											2019-08-09 12:05:47 -06:00
										 |  |  | // | 
					
						
							| 
									
										
										
										
											2020-01-11 13:47:42 -07:00
										 |  |  | // Only URI components which are given in <to> will be set in the resulting URI. | 
					
						
							|  |  |  | // See the docs for the rewrite handler for more information. | 
					
						
							| 
									
										
										
										
											2024-01-15 11:57:08 -05:00
										 |  |  | func parseCaddyfileRewrite(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 	h.Next() // consume directive name | 
					
						
							| 
									
										
										
										
											2024-01-15 11:57:08 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// count the tokens to determine what to do | 
					
						
							|  |  |  | 	argsCount := h.CountRemainingArgs() | 
					
						
							|  |  |  | 	if argsCount == 0 { | 
					
						
							|  |  |  | 		return nil, h.Errf("too few arguments; must have at least a rewrite URI") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if argsCount > 2 { | 
					
						
							|  |  |  | 		return nil, h.Errf("too many arguments; should only be a matcher and a URI") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// with only one arg, assume it's a rewrite URI with no matcher token | 
					
						
							|  |  |  | 	if argsCount == 1 { | 
					
						
							|  |  |  | 		if !h.NextArg() { | 
					
						
							|  |  |  | 			return nil, h.ArgErr() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return h.NewRoute(nil, Rewrite{URI: h.Val()}), nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// parse the matcher token into a matcher set | 
					
						
							|  |  |  | 	userMatcherSet, err := h.ExtractMatcherSet() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 	h.Next() // consume directive name again, matcher parsing does a reset | 
					
						
							|  |  |  | 	h.Next() // advance to the rewrite URI | 
					
						
							| 
									
										
										
										
											2024-01-15 11:57:08 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return h.NewRoute(userMatcherSet, Rewrite{URI: h.Val()}), nil | 
					
						
							| 
									
										
										
										
											2022-01-18 14:17:35 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // parseCaddyfileMethod sets up a basic method rewrite handler from Caddyfile tokens. Syntax: | 
					
						
							|  |  |  | // | 
					
						
							| 
									
										
										
										
											2022-11-14 11:38:02 -05:00
										 |  |  | //	method [<matcher>] <method> | 
					
						
							| 
									
										
										
										
											2022-01-18 14:17:35 -05:00
										 |  |  | func parseCaddyfileMethod(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 	h.Next() // consume directive name | 
					
						
							|  |  |  | 	if !h.NextArg() { | 
					
						
							|  |  |  | 		return nil, h.ArgErr() | 
					
						
							| 
									
										
										
										
											2022-01-18 14:17:35 -05:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 	if h.NextArg() { | 
					
						
							|  |  |  | 		return nil, h.ArgErr() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return Rewrite{Method: h.Val()}, nil | 
					
						
							| 
									
										
										
										
											2019-08-09 12:05:47 -06:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-12-12 15:46:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-19 11:51:28 -06:00
										 |  |  | // parseCaddyfileURI sets up a handler for manipulating (but not "rewriting") the | 
					
						
							|  |  |  | // URI from Caddyfile tokens. Syntax: | 
					
						
							| 
									
										
										
										
											2019-12-12 15:46:13 -07:00
										 |  |  | // | 
					
						
							| 
									
										
										
										
											2022-11-14 11:38:02 -05:00
										 |  |  | //	uri [<matcher>] strip_prefix|strip_suffix|replace|path_regexp <target> [<replacement> [<limit>]] | 
					
						
							| 
									
										
										
										
											2019-12-12 15:46:13 -07:00
										 |  |  | // | 
					
						
							| 
									
										
										
										
											2020-03-19 11:51:28 -06:00
										 |  |  | // If strip_prefix or strip_suffix are used, then <target> will be stripped | 
					
						
							|  |  |  | // only if it is the beginning or the end, respectively, of the URI path. If | 
					
						
							|  |  |  | // replace is used, then <target> will be replaced with <replacement> across | 
					
						
							| 
									
										
										
										
											2021-03-01 18:27:59 -07:00
										 |  |  | // the whole URI, up to <limit> times (or unlimited if unspecified). If | 
					
						
							|  |  |  | // path_regexp is used, then regular expression replacements will be performed | 
					
						
							|  |  |  | // on the path portion of the URI (and a limit cannot be set). | 
					
						
							| 
									
										
										
										
											2020-03-19 11:51:28 -06:00
										 |  |  | func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 	h.Next() // consume directive name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	args := h.RemainingArgs() | 
					
						
							| 
									
										
										
										
											2024-03-06 09:08:46 -06:00
										 |  |  | 	if len(args) < 1 { | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 		return nil, h.ArgErr() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-12 15:46:13 -07:00
										 |  |  | 	var rewr Rewrite | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	switch args[0] { | 
					
						
							|  |  |  | 	case "strip_prefix": | 
					
						
							| 
									
										
										
										
											2024-09-13 08:22:03 +01:00
										 |  |  | 		if len(args) != 2 { | 
					
						
							| 
									
										
										
										
											2019-12-12 15:46:13 -07:00
										 |  |  | 			return nil, h.ArgErr() | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 		rewr.StripPathPrefix = args[1] | 
					
						
							| 
									
										
										
										
											2019-12-12 15:46:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 	case "strip_suffix": | 
					
						
							| 
									
										
										
										
											2024-09-13 08:22:03 +01:00
										 |  |  | 		if len(args) != 2 { | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 			return nil, h.ArgErr() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		rewr.StripPathSuffix = args[1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case "replace": | 
					
						
							|  |  |  | 		var find, replace, lim string | 
					
						
							|  |  |  | 		switch len(args) { | 
					
						
							|  |  |  | 		case 4: | 
					
						
							|  |  |  | 			lim = args[3] | 
					
						
							|  |  |  | 			fallthrough | 
					
						
							|  |  |  | 		case 3: | 
					
						
							|  |  |  | 			find = args[1] | 
					
						
							|  |  |  | 			replace = args[2] | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			return nil, h.ArgErr() | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-12-12 15:46:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 		var limInt int | 
					
						
							|  |  |  | 		if lim != "" { | 
					
						
							|  |  |  | 			var err error | 
					
						
							|  |  |  | 			limInt, err = strconv.Atoi(lim) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, h.Errf("limit must be an integer; invalid: %v", err) | 
					
						
							| 
									
										
										
										
											2021-03-01 18:27:59 -07:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-03-19 11:51:28 -06:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		rewr.URISubstring = append(rewr.URISubstring, substrReplacer{ | 
					
						
							|  |  |  | 			Find:    find, | 
					
						
							|  |  |  | 			Replace: replace, | 
					
						
							|  |  |  | 			Limit:   limInt, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case "path_regexp": | 
					
						
							|  |  |  | 		if len(args) != 3 { | 
					
						
							|  |  |  | 			return nil, h.ArgErr() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		find, replace := args[1], args[2] | 
					
						
							|  |  |  | 		rewr.PathRegexp = append(rewr.PathRegexp, ®exReplacer{ | 
					
						
							|  |  |  | 			Find:    find, | 
					
						
							|  |  |  | 			Replace: replace, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-06 09:08:46 -06:00
										 |  |  | 	case "query": | 
					
						
							|  |  |  | 		if len(args) > 4 { | 
					
						
							|  |  |  | 			return nil, h.ArgErr() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		rewr.Query = &queryOps{} | 
					
						
							|  |  |  | 		var hasArgs bool | 
					
						
							|  |  |  | 		if len(args) > 1 { | 
					
						
							|  |  |  | 			hasArgs = true | 
					
						
							|  |  |  | 			err := applyQueryOps(h, rewr.Query, args[1:]) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for h.NextBlock(0) { | 
					
						
							|  |  |  | 			if hasArgs { | 
					
						
							|  |  |  | 				return nil, h.Err("Cannot specify uri query rewrites in both argument and block") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			queryArgs := []string{h.Val()} | 
					
						
							|  |  |  | 			queryArgs = append(queryArgs, h.RemainingArgs()...) | 
					
						
							|  |  |  | 			err := applyQueryOps(h, rewr.Query, queryArgs) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 		return nil, h.Errf("unrecognized URI manipulation '%s'", args[0]) | 
					
						
							| 
									
										
										
										
											2019-12-12 15:46:13 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return rewr, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-05-26 17:27:51 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-06 09:08:46 -06:00
										 |  |  | func applyQueryOps(h httpcaddyfile.Helper, qo *queryOps, args []string) error { | 
					
						
							|  |  |  | 	key := args[0] | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case strings.HasPrefix(key, "-"): | 
					
						
							|  |  |  | 		if len(args) != 1 { | 
					
						
							|  |  |  | 			return h.ArgErr() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		qo.Delete = append(qo.Delete, strings.TrimLeft(key, "-")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case strings.HasPrefix(key, "+"): | 
					
						
							|  |  |  | 		if len(args) != 2 { | 
					
						
							|  |  |  | 			return h.ArgErr() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		param := strings.TrimLeft(key, "+") | 
					
						
							|  |  |  | 		qo.Add = append(qo.Add, queryOpsArguments{Key: param, Val: args[1]}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case strings.Contains(key, ">"): | 
					
						
							|  |  |  | 		if len(args) != 1 { | 
					
						
							|  |  |  | 			return h.ArgErr() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		renameValKey := strings.Split(key, ">") | 
					
						
							|  |  |  | 		qo.Rename = append(qo.Rename, queryOpsArguments{Key: renameValKey[0], Val: renameValKey[1]}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-21 21:23:42 -05:00
										 |  |  | 	case len(args) == 3: | 
					
						
							|  |  |  | 		qo.Replace = append(qo.Replace, &queryOpsReplacement{Key: key, SearchRegexp: args[1], Replace: args[2]}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-06 09:08:46 -06:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 		if len(args) != 2 { | 
					
						
							|  |  |  | 			return h.ArgErr() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		qo.Set = append(qo.Set, queryOpsArguments{Key: key, Val: args[1]}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-26 17:27:51 -04:00
										 |  |  | // parseCaddyfileHandlePath parses the handle_path directive. Syntax: | 
					
						
							|  |  |  | // | 
					
						
							| 
									
										
										
										
											2022-11-14 11:38:02 -05:00
										 |  |  | //	handle_path [<matcher>] { | 
					
						
							|  |  |  | //	    <directives...> | 
					
						
							|  |  |  | //	} | 
					
						
							| 
									
										
										
										
											2020-05-26 17:27:51 -04:00
										 |  |  | // | 
					
						
							|  |  |  | // Only path matchers (with a `/` prefix) are supported as this is a shortcut | 
					
						
							|  |  |  | // for the handle directive with a strip_prefix rewrite. | 
					
						
							|  |  |  | func parseCaddyfileHandlePath(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { | 
					
						
							| 
									
										
										
										
											2024-01-23 19:36:59 -05:00
										 |  |  | 	h.Next() // consume directive name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// there must be a path matcher | 
					
						
							| 
									
										
										
										
											2020-05-26 17:27:51 -04:00
										 |  |  | 	if !h.NextArg() { | 
					
						
							|  |  |  | 		return nil, h.ArgErr() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// read the prefix to strip | 
					
						
							|  |  |  | 	path := h.Val() | 
					
						
							|  |  |  | 	if !strings.HasPrefix(path, "/") { | 
					
						
							|  |  |  | 		return nil, h.Errf("path matcher must begin with '/', got %s", path) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// we only want to strip what comes before the '/' if | 
					
						
							|  |  |  | 	// the user specified it (e.g. /api/* should only strip /api) | 
					
						
							|  |  |  | 	var stripPath string | 
					
						
							|  |  |  | 	if strings.HasSuffix(path, "/*") { | 
					
						
							|  |  |  | 		stripPath = path[:len(path)-2] | 
					
						
							|  |  |  | 	} else if strings.HasSuffix(path, "*") { | 
					
						
							|  |  |  | 		stripPath = path[:len(path)-1] | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		stripPath = path | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// the ParseSegmentAsSubroute function expects the cursor | 
					
						
							|  |  |  | 	// to be at the token just before the block opening, | 
					
						
							|  |  |  | 	// so we need to rewind because we already read past it | 
					
						
							|  |  |  | 	h.Reset() | 
					
						
							|  |  |  | 	h.Next() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// parse the block contents as a subroute handler | 
					
						
							|  |  |  | 	handler, err := httpcaddyfile.ParseSegmentAsSubroute(h) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	subroute, ok := handler.(*caddyhttp.Subroute) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return nil, h.Errf("segment was not parsed as a subroute") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// make a matcher on the path and everything below it | 
					
						
							|  |  |  | 	pathMatcher := caddy.ModuleMap{ | 
					
						
							|  |  |  | 		"path": h.JSON(caddyhttp.MatchPath{path}), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// build a route with a rewrite handler to strip the path prefix | 
					
						
							|  |  |  | 	route := caddyhttp.Route{ | 
					
						
							|  |  |  | 		HandlersRaw: []json.RawMessage{ | 
					
						
							|  |  |  | 			caddyconfig.JSONModuleObject(Rewrite{ | 
					
						
							|  |  |  | 				StripPathPrefix: stripPath, | 
					
						
							|  |  |  | 			}, "handler", "rewrite", nil), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// prepend the route to the subroute | 
					
						
							|  |  |  | 	subroute.Routes = append([]caddyhttp.Route{route}, subroute.Routes...) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// build and return a route from the subroute | 
					
						
							|  |  |  | 	return h.NewRoute(pathMatcher, subroute), nil | 
					
						
							|  |  |  | } |