Documentation ¶
Overview ¶
Package eskip implements an in-memory representation of Skipper routes and a DSL for describing Skipper route expressions, route definitions and complete routing tables.
Grammar Summary ¶
A routing table is built up from 0 or more route definitions. The definitions are separated by ';'. A route definition contains one route expression prefixed by its id and a ':'.
A routing table example:
catalog: Path("/*category") -> "https://catalog.example.org"; productPage: Path("/products/:id") -> "https://products.example.org"; userAccount: Path("/user/:id/*userpage") -> "https://users.example.org"; // 404 notfound: * -> modPath(/.+/, "/notfound.html") -> static("/", "/var/www") -> <shunt>
A route expression always contains a match expression and a backend expression, and it can contain optional filter expressions. The match expression, each filter and the backend are separated by '->'. The filters take place between the matcher and the backend.
A route expression example:
Path("/api/*resource") && Header("Accept", "application/json") -> modPath("^/api", "") -> requestHeader("X-Type", "external") -> "https://api.example.org"
Match Expressions - Predicates ¶
A match expression contains one or more conditions. An incoming request must fulfil each of them to match the route. The conditions are separated by '&&'.
A match expression example:
Path("/api/*resource") && Header("Accept", "application/json")
The following condition expressions are recognized:
Path("/some/path")
The path condition accepts a single argument, that can be a fixed path like "/some/path", or it can contain wildcards in place of one or more names in the path, e.g. "/some/:dir/:name", or it can end with a free wildcard like "/some/path/*param", where the free wildcard can contain a sub-path with multiple names. The arguments are available to the filters while processing the matched requests.
PathRegexp(/regular-expression/)
The regexp path condition accepts a regular expression as a single argument that needs to be matched by the request path. The regular expression can be surrounded by '/' or '"'.
Host(/host-regular-expression/)
The host condition accepts a regular expression as a single argument that needs to be matched by the host header in the request.
Method("HEAD")
The method condition is used to match the http request method.
Header("Accept", "application/json")
The header condition is used to match the http headers in the request. It accepts two arguments, the name of the header field and the exact header value to match.
HeaderRegexp("Accept", /\Wapplication\/json\W/)
The header regexp condition works similar to the header expression, but the value to be matched is a regular expression.
*
Catch all condition.
Any()
Former, deprecated form of the catch all condition.
Custom Predicates ¶
Eskip supports custom route matching predicates, that can be implemented in extensions. The notation of custom predicates is the same as of the built-in route matching expressions:
Foo(3.14, "bar")
During parsing, custom predicates may define any arbitrary list of arguments of types number, string or regular expression, and it is the responsibility of the implementation to validate them.
(See the documentation of the routing package.)
Filters ¶
Filters are used to augment the incoming requests and the outgoing responses, or do other useful or fun stuff. Filters can have different numbers of arguments depending on the implementation of the particular filter. The arguments can be of type string ("a string"), number (3.1415) or regular expression (/[.]html$/ or "[.]html$").
A filter example:
setResponseHeader("max-age", "86400") -> static("/", "/var/www/public")
The default Skipper implementation provides the following built-in filters:
setRequestHeader("header-name", "header-value") setResponseHeader("header-name", "header-value") appendRequestHeader("header-name", "header-value") appendResponseHeader("header-name", "header-value") dropRequestHeader("header-name") dropResponseHeader("header-name") modPath(/regular-expression/, "replacement") redirectTo(302, "https://ui.example.org") flowId("reuse", 64) healthcheck() static("/images", "/var/www/images") stripQuery("true") preserveHost() status(418)
For details about the built-in filters, please, refer to the documentation of the skipper/filters package. Skipper is designed to be extendable primarily by implementing custom filters, for details about how to create custom filters, please, refer to the documentation of the root skipper package.
Backend ¶
There are two types of backends: a network endpoint address or a shunt.
A network endpoint address example:
"http://internal.example.org:9090"
An endpoint address backend is surrounded by '"'. It contains the scheme and the hostname of the endpoint, and optionally the port number that is inferred from the scheme if not specified.
A shunt backend:
<shunt>
The shunt backend means that the route will not forward the request to a network endpoint, but the router will handle the request itself. By default, the response is in this case 404 Not found, unless a filter in the route does not change it.
Comments ¶
An eskip document can contain comments. The rule for comments is simple: everything is a comment that starts with '//' and ends with a new-line character.
Example with comments:
// forwards to the API endpoint route1: Path("/api") -> "https://api.example.org"; route2: * -> <shunt> // everything else 404
Regular expressions ¶
The matching conditions and the built-in filters that use regular expressions, use the go stdlib regexp, which uses re2:
https://github.com/google/re2/wiki/Syntax
Parsing Filters ¶
The eskip.ParseFilters method can be used to parse a chain of filters, without the matcher and backend part of a full route expression.
Parsing ¶
Parsing a routing table or a route expression happens with the eskip.Parse function. In case of grammar error, it returns an error with the approximate position of the invalid syntax element, otherwise it returns a list of structured, in-memory route definitions.
The eskip parser does not validate the routes against all semantic rules, e.g., whether a filter or a custom predicate implementation is available. This validation happens during processing the parsed definitions.
Serializing ¶
Serializing a single route happens by calling its String method. Serializing a complete routing table happens by calling the eskip.String method.
Example ¶
package main import ( "fmt" "github.com/zalando/skipper/eskip" "log" ) func main() { code := ` // Skipper - Eskip: // a routing table document, containing multiple route definitions // route definition to a jsx page renderer route0: PathRegexp(/\.html$/) && HeaderRegexp("Accept", "text/html") -> modPath(/\.html$/, ".jsx") -> requestHeader("X-Type", "page") -> "https://render.example.org"; route1: Path("/some/path") -> "https://backend-0.example.org"; // a simple route // route definition with a shunt (no backend address) route2: Path("/some/other/path") -> static("/", "/var/www") -> <shunt>; // route definition directing requests to an api endpoint route3: Method("POST") && Path("/api") -> requestHeader("X-Type", "ajax-post") -> "https://api.example.org" ` routes, err := eskip.Parse(code) if err != nil { log.Println(err) return } format := "%v: [match] -> [%v filter(s) ->] <%v> \"%v\"\n" fmt.Println("Parsed routes:") for _, r := range routes { fmt.Printf(format, r.Id, len(r.Filters), r.Shunt, r.Backend) } }
Output: Parsed routes: route0: [match] -> [2 filter(s) ->] <false> "https://render.example.org" route1: [match] -> [0 filter(s) ->] <false> "https://backend-0.example.org" route2: [match] -> [1 filter(s) ->] <true> "" route3: [match] -> [1 filter(s) ->] <false> "https://api.example.org"
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GenerateIfNeeded ¶
generate weak random id for a route if it doesn't have one.
Types ¶
type Filter ¶
type Filter struct { // name of the filter specification Name string // filter args applied withing a particular route Args []interface{} }
A Filter object represents a parsed, in-memory filter expression.
Example ¶
package main import ( "fmt" "github.com/zalando/skipper/eskip" "log" ) func main() { code := ` Method("GET") -> helloFilter("Hello, world!", 3.14) -> "https://backend.example.org"` routes, err := eskip.Parse(code) if err != nil { log.Println(err) return } f := routes[0].Filters[0] fmt.Println("Parsed a route with a filter:") fmt.Printf("filter name: %v\n", f.Name) fmt.Printf("filter arg 0: %v\n", f.Args[0].(string)) fmt.Printf("filter arg 1: %v\n", f.Args[1].(float64)) }
Output: Parsed a route with a filter: filter name: helloFilter filter arg 0: Hello, world! filter arg 1: 3.14
func ParseFilters ¶
Parses a filter chain into a list of parsed filter definitions.
Example ¶
package main import ( "fmt" "github.com/zalando/skipper/eskip" "log" ) func main() { code := `filter0() -> filter1(3.14, "Hello, world!")` filters, err := eskip.ParseFilters(code) if err != nil { log.Println(err) return } fmt.Println("Parsed a chain of filters:") fmt.Printf("filters count: %d\n", len(filters)) fmt.Printf("first filter: %s\n", filters[0].Name) fmt.Printf("second filter: %s\n", filters[1].Name) fmt.Printf("second filter, first arg: %g\n", filters[1].Args[0].(float64)) fmt.Printf("second filter, second arg: %s\n", filters[1].Args[1].(string)) }
Output: Parsed a chain of filters: filters count: 2 first filter: filter0 second filter: filter1 second filter, first arg: 3.14 second filter, second arg: Hello, world!
type Predicate ¶
type Predicate struct { // The name of the custom predicate as referenced // in the route definition. E.g. 'Foo'. Name string // The arguments of the predicate as defined in the // route definition. The arguments can be of type // float64 or string (string for both strings and // regular expressions). Args []interface{} }
A Predicate object represents a parsed, in-memory, route matching predicate that is defined by extensions.
type Route ¶
type Route struct { // Id of the route definition. // E.g. route1: ... Id string // Exact path to be matched. // E.g. Path("/some/path") Path string // Host regular expressions to match. // E.g. Host(/[.]example[.]org/) HostRegexps []string // Path regular expressions to match. // E.g. PathRegexp(/\/api\//) PathRegexps []string // Method to match. // E.g. Method("HEAD") Method string // Exact header definitions to match. // E.g. Header("Accept", "application/json") Headers map[string]string // Header regular expressions to match. // E.g. HeaderRegexp("Accept", /\Wapplication\/json\W/) HeaderRegexps map[string][]string // Custom predicates to match. // E.g. Traffic(.3) Predicates []*Predicate // Set of filters in a particular route. // E.g. redirect(302, "https://www.example.org/hello") Filters []*Filter // Indicates that the parsed route has a shunt backend. // (<shunt>, no forwarding to a backend) Shunt bool // The address of a backend for a parsed route. // E.g. "https://www.example.org" Backend string }
A Route object represents a parsed, in-memory route definition.
Example ¶
package main import ( "fmt" "github.com/zalando/skipper/eskip" "log" ) func main() { code := ` ajaxRouteV3: PathRegexp(/^\/api\/v3\/.*/) -> ajaxHeader("v3") -> "https://api.example.org"` routes, err := eskip.Parse(code) if err != nil { log.Println(err) return } r := routes[0] fmt.Println("Parsed a route:") fmt.Printf("id: %v\n", r.Id) fmt.Printf("match regexp: %s\n", r.PathRegexps[0]) fmt.Printf("# of filters: %v\n", len(r.Filters)) fmt.Printf("is shunt: %v\n", r.Shunt) fmt.Printf("backend address: \"%v\"\n", r.Backend) }
Output: Parsed a route: id: ajaxRouteV3 match regexp: ^/api/v3/.* # of filters: 1 is shunt: false backend address: "https://api.example.org"
func Parse ¶
Parses a route expression or a routing document to a set of route definitions.
Example ¶
package main import ( "fmt" "github.com/zalando/skipper/eskip" "log" ) func main() { code := ` PathRegexp(/\.html$/) && Header("Accept", "text/html") -> modPath(/\.html$/, ".jsx") -> requestHeader("X-Type", "page") -> "https://render.example.org"` routes, err := eskip.Parse(code) if err != nil { log.Println(err) return } fmt.Printf("Parsed route with backend: %s\n", routes[0].Backend) }
Output: Parsed route with backend: https://render.example.org
type RouteInfo ¶
type RouteInfo struct { // The route id plus the route data or if parsing was successful. Route // The parsing error if the parsing failed. ParseError error }
RouteInfo contains a route id, plus the loaded and parsed route or the parse error in case of failure.