Envoy proxy - how to use json_to_meta extension to route by request body?

76 Views Asked by At

I'm trying to use Envoy proxy to route requests to Service A or Service B based on the POST request bodies. For example, we care about food in the POST /v2/meal. If the request body has "food":"fruit", route the request to Service A. If the request body has "food":"soup", route the request to Service B.

Implementation

I added json_to_metadata_filter (very little example and documentations ☹️) to my proxy service. For logging, I added meta_data: "%DYNAMIC_METADATA(envoy.lb)%" to the access log.

resources:
  - name: listener_0
    "@type": type.googleapis.com/envoy.config.listener.v3.Listener
    traffic_direction: INBOUND
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    filter_chains:
      - filters:
        - name: envoy.filters.network.http_connection_manager
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
            stat_prefix: ingress_http
            access_log: # log out the requests
              - name: envoy.access_loggers.typed_json
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
                  path: "/dev/stdout"
                  typed_json_format:
                    message: "requestLog"
                    # other fields are omitted
                    meta_data: "%DYNAMIC_METADATA(envoy.lb)%" # Todo: why null?
            http_filters:
            - name: envoy.filters.http.json_to_metadata
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.http.json_to_metadata.v3.JsonToMetadata
                request_rules:
                  rules:
                  - selectors:
                    - key: food
                    on_present:
                      metadata_namespace: envoy.lb
                      key: food
                    on_missing:
                      metadata_namespace: envoy.lb
                      key: default
                      value: "foodMissing"
                      preserve_existing_metadata_value: true
                    on_error:
                      metadata_namespace: envoy.lb
                      key: default
                      value: "foodError"
                      preserve_existing_metadata_value: true
            - name: envoy.filters.http.router # must be the last filter in a http filter chain
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

Then in the route definitions (we use envoy_data_plane to build the routes in Python), I added MetadataMatcher to make it match the food value in the metadata of each request:

    # Route based on food from the request body
    metadata_matchers = []
    if route_data.get("food"):
        food = route_data.get("food")
        metadata_matchers.append(MetadataMatcher(
            filter="envoy.lb",
            path=[
                MetadataMatcherPathSegment("food")
            ],
            value=ValueMatcher(
                string_match=StringMatcher(
                    exact=food,
                    ignore_case=True
                )
            )
        ))
    route_match = RouteMatch(
        dynamic_metadata=metadata_matchers, <-- matching the metadata
        prefix=request_path_prefix,
        case_sensitive=False,
        headers=request_headers,
        query_parameters=query_parameters
    )

And finally my route is configured as

ROUTES = [
    {
        "prefix": "/v2/meal",
        "food": "fruit",
        "config": SERVICE_A
    },
    {
        "prefix": "/v2/meal",
        "food": "soup",
        "config": SERVICE_B
    },
]

The Problem

When I test the proxy with curl -X POST localhost:8080/v2/meal --data-raw '{"food":"fruit"}', I got 404 and Envoy routed the request to a default cluster Service C instead of Service A. This happened because there's no match at all and Service C doesn't have a /v2/meal endpoint. And the json_to_metadata filter didn't populate meta_data either -- see "meta_data":null below:

{"response_code":404,"request_method":"POST","trace_id":null,"user_agent":"curl/8.4.0","message":"requestLog","protocol":"HTTP/1.1","authority":"localhost:8080","full_path":"/v2/meal","bytes_sent":0,"timestamp":"2024-02-12T22:14:36.573Z","request_id":"fe632ad6-f9a7-4349-a6bc-22fd09e76b5d","duration":285,"cluster":"SERVICE_C","meta_data":null,"upstream_service_time":null,"bytes_received":164,"response_flags":"-"}

Could you help me find out what I did wrong when I use json_to_metadata to parse food from the request body and route requests based on food?

1

There are 1 best solutions below

0
Kai W On

Answering my own question here. So it worked after I added -H 'content-type: application/json' to my test POST request. The json_to_metadata config itself has been correct but it expect the request body type to be JSON.