Matching API

Attention

The matching API is alpha and is currently under active development. Capabilities will be expanded over time and the configuration structures are likely to change.

Envoy makes use of a matching API to allow the various subsystems to express actions that should be performed based on incoming data.

The matching API is designed as a tree structure to allow for sublinear matching algorithms for better performance than the linear list matching as seen in Envoy’s HTTP routing. It makes heavy use of extension points to make it easy to extend to different inputs based on protocol or environment data as well as custom sublinear matchers and direct matchers.

Within supported environments (currently only HTTP filters), a wrapper proto can be used to instantiate a matching filter associated with the wrapped structure:

  1. static_resources:
  2. listeners:
  3. - address:
  4. socket_address:
  5. address: 0.0.0.0
  6. port_value: 443
  7. listener_filters:
  8. filter_chains:
  9. - filters:
  10. - name: envoy.filters.network.http_connection_manager
  11. typed_config:
  12. "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
  13. stat_prefix: ingress_http
  14. http_filters:
  15. - name: with-matcher
  16. typed_config:
  17. "@type": type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher
  18. extension_config:
  19. name: envoy.filters.http.fault
  20. typed_config:
  21. "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
  22. abort:
  23. http_status: 503
  24. percentage:
  25. numerator: 0
  26. denominator: HUNDRED
  27. delay:
  28. fixed_delay: 3s
  29. percentage:
  30. numerator: 0
  31. denominator: HUNDRED
  32. xds_matcher:
  33. matcher_tree:
  34. input:
  35. name: request-headers
  36. typed_config:
  37. "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
  38. header_name: some-header
  39. exact_match_map:
  40. # Note this additional indirection; this is a workaround for Protobuf oneof limitations.
  41. map:
  42. some_value_to_match_on: # This is the header value we're trying to match against.
  43. action:
  44. name: skip
  45. typed_config:
  46. "@type": type.googleapis.com/envoy.extensions.filters.common.matcher.action.v3.SkipFilter
  47. - name: envoy.filters.http.router
  48. route_config:
  49. virtual_hosts:
  50. - name: default
  51. domains: ["*"]
  52. routes:
  53. - match: {prefix: "/"}
  54. route:
  55. cluster: service_foo
  56. clusters:
  57. - name: service_foo
  58. load_assignment:
  59. cluster_name: some_service
  60. endpoints:
  61. - lb_endpoints:
  62. - endpoint:
  63. address:
  64. socket_address:
  65. address: 127.0.0.1
  66. port_value: 8080
  67. layered_runtime:
  68. layers:
  69. - name: static-layer
  70. static_layer:
  71. envoy:
  72. reloadable_features:
  73. experimental_matching_api: true

The above example wraps a HTTP filter (the HTTPFault filter) in an ExtensionWithMatcher, allowing us to define a match tree to be evaluated in conjunction with evaluation of the wrapped filter. Prior to data being made available to the filter, it will be provided to the match tree, which will then attempt to evaluate the matching rules with the provided data, triggering an action if match evaluation completes in an action.

In the above example, we are specifying that we want to match on the incoming request header some-header by setting the input to HttpRequestHeaderMatchInput and configuring the header key to use. Using the value contained by this header, the provided exact_match_map specifies which values we care about: we’ve configured a single value (some_value_to_match_on) to match against. As a result, this config means that if we receive a request which contains some-header: some_value_to_match_on as a header, the SkipFilter action will be resolved (causing the associated HTTP filter to be skipped). If no such header is present, no action will be resolved and the filter will be applied as usual.

  1. static_resources:
  2. listeners:
  3. - address:
  4. socket_address:
  5. address: 0.0.0.0
  6. port_value: 443
  7. listener_filters:
  8. filter_chains:
  9. - filters:
  10. - name: envoy.filters.network.http_connection_manager
  11. typed_config:
  12. "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
  13. stat_prefix: ingress_http
  14. http_filters:
  15. - name: with-matcher
  16. typed_config:
  17. "@type": type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher
  18. extension_config:
  19. name: envoy.filters.http.fault
  20. typed_config:
  21. "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
  22. abort:
  23. http_status: 503
  24. percentage:
  25. numerator: 0
  26. denominator: HUNDRED
  27. delay:
  28. fixed_delay: 3s
  29. percentage:
  30. numerator: 0
  31. denominator: HUNDRED
  32. xds_matcher:
  33. # The top level matcher is a matcher tree which conceptually selects one of several subtrees.
  34. matcher_tree:
  35. input:
  36. name: request-headers
  37. typed_config:
  38. "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
  39. header_name: some-header
  40. exact_match_map:
  41. # Note this additional indirection; this is a workaround for Protobuf oneof limitations.
  42. map:
  43. some_value_to_match_on: # This is the header value we're trying to match against.
  44. # The OnMatch resulting on matching with this branch of the exact matcher is another matcher, allowing for recursive matching.
  45. matcher:
  46. # The inner matcher is a matcher list, which attempts to match a list of predicates.
  47. matcher_list:
  48. matchers:
  49. - predicate:
  50. or_matcher:
  51. predicate:
  52. - single_predicate:
  53. input:
  54. name: request-headers
  55. typed_config:
  56. "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
  57. header_name: second-header
  58. value_match:
  59. exact: foo
  60. - single_predicate:
  61. input:
  62. name: request-headers
  63. typed_config:
  64. "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
  65. header_name: second-header
  66. value_match:
  67. exact: bar
  68. on_match:
  69. action:
  70. name: skip
  71. typed_config:
  72. "@type": type.googleapis.com/envoy.extensions.filters.common.matcher.action.v3.SkipFilter
  73. - name: envoy.filters.http.router
  74. route_config:
  75. virtual_hosts:
  76. - name: default
  77. domains: ["*"]
  78. routes:
  79. - match: {prefix: "/"}
  80. route:
  81. cluster: service_foo
  82. clusters:
  83. - name: service_foo
  84. load_assignment:
  85. cluster_name: some_service
  86. endpoints:
  87. - lb_endpoints:
  88. - endpoint:
  89. address:
  90. socket_address:
  91. address: 127.0.0.1
  92. port_value: 8080
  93. layered_runtime:
  94. layers:
  95. - name: static-layer
  96. static_layer:
  97. envoy:
  98. reloadable_features:
  99. experimental_matching_api: true

Above is a slightly more complicated example which combines a top level tree matcher with a linear matcher. While the tree matchers provide very efficient matching, they are not very expressive. The list matcher can be used to provide a much richer matching API, and can be combined with the tree matcher in an arbitrary order. The example describes the following match logic: skip the filter if some-header: skip_filter is present and second-header is set to either foo or bar.

HTTP Filter Iteration Impact

The above example only demonstrates matching on request headers, which ends up being the simplest case due to it happening before the associated filter receives any data. Matching on other HTTP input sources is supported (e.g. response headers), but some discussion is warranted on how this works at a filter level.

Currently the match evaluation for HTTP filters does not impact control flow at all: if insufficient data is available to perform the match, callbacks will be sent to the associated filter as normal. Once sufficient data is available to match an action, this is provided to the filter. A consequence of this is that if the filter wishes to gate some behavior on a match result, it has to manage stopping the iteration on its own.

When it comes to actions such as SkipFilter, this means that if the skip condition is based on anything but the request headers, the filter might get partially applied, which might result in surprising behavior. An example of this would be to have a matching tree that attempts to skip the gRPC-Web filter based on response headers: clients assume that if they send a gRPC-Web request to Envoy, the filter will transform that into a gRPC request before proxying it upstream, then back into a gRPC-Web response on the encoding path. By skipping the filter based on response headers, the forward transformation will happen (the upstream receives a gRPC request), but the response is never converted back to gRPC-Web. As a result, the client will receive an invalid response back from Envoy. If the skip action was instead resolved on trailers, the same gRPC-Web filter would consume all the data but never write it back out (as this happens when it sees the trailers), resulting in a gRPC-Web response with an empty body.

Match Tree Validation

As the match tree structure is very flexible, some filters might need to impose additional restrictions on what kind of match trees can be used. This system is somewhat inflexible at the moment, only supporting limiting the input sources to a specific set. For example, a filter might specify that it only works with request headers: in this case a match tree that attempts to match on request trailers or response headers will fail during configuration load, reporting back which data input was invalid.

This is done for example to limit the issues talked about in the above section or to help users understand in what context a match tree can be used for a specific filter. Due to the limitations of the validations framework at the current time, it is not used for all filters.

For HTTP filters, the restrictions are specified by the filter implementation, so consult the individual filter documentation to understand whether there are restrictions in place.

For example, in the example below, the match tree could not be used with a filter that restricts the the match tree to only use HttpRequestHeaderMatchInput.

  1. static_resources:
  2. listeners:
  3. - address:
  4. socket_address:
  5. address: 0.0.0.0
  6. port_value: 443
  7. listener_filters:
  8. filter_chains:
  9. - filters:
  10. - name: envoy.filters.network.http_connection_manager
  11. typed_config:
  12. "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
  13. stat_prefix: ingress_http
  14. http_filters:
  15. - name: with-matcher
  16. typed_config:
  17. "@type": type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher
  18. extension_config:
  19. name: envoy.filters.http.fault
  20. typed_config:
  21. "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
  22. abort:
  23. http_status: 503
  24. percentage:
  25. numerator: 0
  26. denominator: HUNDRED
  27. delay:
  28. fixed_delay: 3s
  29. percentage:
  30. numerator: 0
  31. denominator: HUNDRED
  32. xds_matcher:
  33. matcher_list:
  34. matchers:
  35. - predicate:
  36. or_matcher:
  37. predicate:
  38. - single_predicate:
  39. input:
  40. name: request-headers
  41. typed_config:
  42. "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
  43. header_name: request-header
  44. value_match:
  45. exact: foo
  46. - single_predicate:
  47. input:
  48. name: request-headers
  49. typed_config:
  50. "@type": type.googleapis.com/envoy.type.matcher.v3.HttpResponseHeaderMatchInput
  51. header_name: response-header
  52. value_match:
  53. exact: bar
  54. on_match:
  55. action:
  56. name: skip
  57. typed_config:
  58. "@type": type.googleapis.com/envoy.extensions.filters.common.matcher.action.v3.SkipFilter
  59. - name: envoy.filters.http.router
  60. route_config:
  61. virtual_hosts:
  62. - name: default
  63. domains: ["*"]
  64. routes:
  65. - match: {prefix: "/"}
  66. route:
  67. cluster: service_foo
  68. clusters:
  69. - name: service_foo
  70. load_assignment:
  71. cluster_name: some_service
  72. endpoints:
  73. - lb_endpoints:
  74. - endpoint:
  75. address:
  76. socket_address:
  77. address: 127.0.0.1
  78. port_value: 8080
  79. layered_runtime:
  80. layers:
  81. - name: static-layer
  82. static_layer:
  83. envoy:
  84. reloadable_features:
  85. experimental_matching_api: true