apigeex policy for header and content based routing

can apigeex proxy perform header & content based routing in an api with below endpoints

1. certain endpoints in api with incoming msg with headervalue1 & headervalue2 combination of values in comparison with predefined json array of values that need to be stored& pulled from kvm & send the request to one backend versus other (like going to blue if matches , else green)

2. certain endpoints in api with incoming msg with body having value1 & value2 combination of values in comparison with predefined json array of values that need to be stored & pulled from kvm & send the request to one backend versus other (like going to blue if matches , else green)

can this be achieved with javascript policy & kvm? if yes, can anyone help with the sample to achieve using context/session variables ?

Solved Solved
0 9 784
2 ACCEPTED SOLUTIONS

The short answer is yes.

In general, an Apigee API proxy can route to different targets based on dynamic evaluation of criteria that you specify. The criteria or conditions can examine:

  • information directly related to the request. Such as: request headers, request payload, URL path, verb, originating client IP address
  • information indirectly related to the inbound request. such as: the name of the proxy handling the inbound request, client ID (derived from an OAuth token), the DN or SAN on the client certificate if you are using 2-way TLS, some custom attribute on the app, developer, or API product (again derived from the OAuth token).
  • environmental information, such as: time of day, name of the Apigee environment or organization.
  • or data that derives from the inbound request and some other external data. For example, your proxy can "look up" a route destination based on client ID or some other request element. Imagine an externally-hosted routing table; Apigee can route to different targets based on a lookup in that dynamic table

In summary: you can configure Apigee to route requests based on all sorts of things. How does it work? There are two different approaches.

  1. Define a static set of targets in the API proxy bundle. In Apigee these things are represented as "TargetEndpoints", and each one might have a single server, or a set of server endpoints (Apigee does load balancing and health monitoring across the set). Then, apply conditions in the ProxyEndpoint to select a single TargetEndpoint. You can do this with RouteRules in Apigee.
  2. Define a single target in the API proxy bundle. Then, apply your conditional logic in the Target flow to set the target.url variable to the appropriate value. This is fully dynamic, which is nice. The downside is, you don't get the load balancing and health monitoring across the set of all possible server endpoints in this case.

You might want to read up on RouteRules if you're not familiar with the possibilities there. OK, now to your specific questions. In both cases you said

in comparison with predefined json array of values that need to be stored& pulled from kvm

Getting data from the KVM is no problem. I assume you have a good idea for how to do that, and how to format the data. You'll want to cache the data of course, so set the TimeToLive value in the KVM Get policy appropriately. That saves I/O when the proxy operates at scale.

In the first case you said you want to evaluate "header1" and "header2" and then combine that with the routing table obtained from the KVM, to make a routing decision. Depending on how complex the logic is, you may want to rely on JavaScript to encode it. For example, you could do something like this:

 

var h1 = context.getVariable('request.header.header1');
var h2 = context.getVariable('request.header.header2');
var routingTable = JSON.parse(context.getVariable('thing-retrieved-from-KVM')); 

var selected = routingTable[h1] && routingTable[h1][h2]; 
if (selected) {
  context.setVariable('selected-route', selected);
}
else {
  context.setVariable('selected-route', 'default');
}

 

And then set target.url to the appropriate URL for that selected route.

The second case is essentially the same, except you're retrieving data from the request payload.  It works basically the same way. 

There are lots of other variations possible. Obviously you might have different things in your routing table, so you might need to use different JavaScript to perform the lookups.  This is just a simple example, something to start with. 

Some time ago I produced an example of dynamic selection of a target URL based on a weighted random approach. That is similar to what you describe here, but slightly different. It may be helpful to you: https://github.com/DinoChiesa/ApigeeEdge-BlueGreen-1

 

View solution in original post

Hi Raghu,

Once you use:

context.setVariable('selected-route', selected);

The variable `selected-route` will be able to be used directly in routing rules:

    <RouteRule name="blue">
        <Condition>selected-route = "blue"</Condition>
        <TargetEndpoint>blue</TargetEndpoint>
    </RouteRule>
    <RouteRule name="green">
        <Condition>selected-route = "green"</Condition>
        <TargetEndpoint>green</TargetEndpoint>
    </RouteRule>
    <RouteRule name="default">
        <TargetEndpoint>default</TargetEndpoint>
    </RouteRule>

 

View solution in original post

9 REPLIES 9

The short answer is yes.

In general, an Apigee API proxy can route to different targets based on dynamic evaluation of criteria that you specify. The criteria or conditions can examine:

  • information directly related to the request. Such as: request headers, request payload, URL path, verb, originating client IP address
  • information indirectly related to the inbound request. such as: the name of the proxy handling the inbound request, client ID (derived from an OAuth token), the DN or SAN on the client certificate if you are using 2-way TLS, some custom attribute on the app, developer, or API product (again derived from the OAuth token).
  • environmental information, such as: time of day, name of the Apigee environment or organization.
  • or data that derives from the inbound request and some other external data. For example, your proxy can "look up" a route destination based on client ID or some other request element. Imagine an externally-hosted routing table; Apigee can route to different targets based on a lookup in that dynamic table

In summary: you can configure Apigee to route requests based on all sorts of things. How does it work? There are two different approaches.

  1. Define a static set of targets in the API proxy bundle. In Apigee these things are represented as "TargetEndpoints", and each one might have a single server, or a set of server endpoints (Apigee does load balancing and health monitoring across the set). Then, apply conditions in the ProxyEndpoint to select a single TargetEndpoint. You can do this with RouteRules in Apigee.
  2. Define a single target in the API proxy bundle. Then, apply your conditional logic in the Target flow to set the target.url variable to the appropriate value. This is fully dynamic, which is nice. The downside is, you don't get the load balancing and health monitoring across the set of all possible server endpoints in this case.

You might want to read up on RouteRules if you're not familiar with the possibilities there. OK, now to your specific questions. In both cases you said

in comparison with predefined json array of values that need to be stored& pulled from kvm

Getting data from the KVM is no problem. I assume you have a good idea for how to do that, and how to format the data. You'll want to cache the data of course, so set the TimeToLive value in the KVM Get policy appropriately. That saves I/O when the proxy operates at scale.

In the first case you said you want to evaluate "header1" and "header2" and then combine that with the routing table obtained from the KVM, to make a routing decision. Depending on how complex the logic is, you may want to rely on JavaScript to encode it. For example, you could do something like this:

 

var h1 = context.getVariable('request.header.header1');
var h2 = context.getVariable('request.header.header2');
var routingTable = JSON.parse(context.getVariable('thing-retrieved-from-KVM')); 

var selected = routingTable[h1] && routingTable[h1][h2]; 
if (selected) {
  context.setVariable('selected-route', selected);
}
else {
  context.setVariable('selected-route', 'default');
}

 

And then set target.url to the appropriate URL for that selected route.

The second case is essentially the same, except you're retrieving data from the request payload.  It works basically the same way. 

There are lots of other variations possible. Obviously you might have different things in your routing table, so you might need to use different JavaScript to perform the lookups.  This is just a simple example, something to start with. 

Some time ago I produced an example of dynamic selection of a target URL based on a weighted random approach. That is similar to what you describe here, but slightly different. It may be helpful to you: https://github.com/DinoChiesa/ApigeeEdge-BlueGreen-1

 

I was trying to store/cache the column data into KVM but no luck. so the combination of these values would decide which backend to go to. Does this need to be in a json format. I might have max 800 rows in production like this. 

 

IDECHM
00
372
572
871
1072
3072

 kvmadmin api is throwing error if I send it as json. please help

It's common and easy to use a KVM to store JSON, the secret is to not use an index attribute on the get element (e.g. `<Get assignTo="private.params">`).
For example:

<KeyValueMapOperations continueOnError="false" enabled="true" name="KV-GetParams" mapIdentifier="kvm-usage">
<DisplayName>KV-GetParams</DisplayName>
<ExclusiveCache>false</ExclusiveCache>
<ExpiryTimeInSecs>300</ExpiryTimeInSecs>
<Get assignTo="private.params">
<Key>
<Parameter>params</Parameter>
</Key>
</Get>
<Scope>environment</Scope>
</KeyValueMapOperations>

To set the KVM, first use an ExtractVariables to get the request:

<ExtractVariables async="false" continueOnError="false" enabled="true" name="EV-ParamsData">
<DisplayName>EV-ParamsData</DisplayName>
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
<JSONPayload>
<Variable name="params_update">
<JSONPath>$.[*]</JSONPath>
</Variable>
</JSONPayload>
<Source clearPayload="false">request</Source>
</ExtractVariables>

Then update the KVM:

<KeyValueMapOperations mapIdentifier="kvm-usage" async="false" continueOnError="false" enabled="true" name="KV-UpdateParams">
    <DisplayName>KV-UpdateParams</DisplayName>
    <ExclusiveCache>false</ExclusiveCache>
    <ExpiryTimeInSecs>300</ExpiryTimeInSecs>
    <Put override="true">
        <Key>
            <Parameter>params</Parameter>
        </Key>
        <Value ref="params_update"/>
    </Put>
    <Scope>environment</Scope>
</KeyValueMapOperations>

Sample Postman request

curl --location --request POST '{{HOST}}/kvm-usage/params' \
--header 'Content-Type: application/json' \
--data-raw '[
    {
        "STORE_ID": 42,
        "DIVISION_ID": 42
    },
    {
        "STORE_ID": 3,
        "DIVISION_ID": 72
    }
]'

 Hope that helps get you past your KVM issue and Dino's suggestions work for routing.

Thanks Kurt. Was able to update the KVM with the json. 

Is there a way to read the javascript output variable in the routerule condition & route to blue versus green? 

Is there a way to read the javascript output variable in the routerule condition & route to blue versus green?

Not directly. You would need to extract something out of that JSON and then make a decision. (Maybe both at the same time). But there is no JSONPath capability available in the Condition elements. You'd need to use JavaScript or an AssignMessage with JSONPath to get the information out of the JSON.

Basically trying to read 'selected-route' in the above example provided by you.

var h1 = context.getVariable('request.header.header1');
var h2 = context.getVariable('request.header.header2');
var routingTable = JSON.parse(context.getVariable('thing-retrieved-from-KVM')); 

var selected = routingTable[h1] && routingTable[h1][h2]; 
if (selected) {
  context.setVariable('selected-route', selected);
}
else {
  context.setVariable('selected-route', 'default');
}

 Does it need a assignmessage policy to read this in a condition in routerule? 

Hi Raghu,

Once you use:

context.setVariable('selected-route', selected);

The variable `selected-route` will be able to be used directly in routing rules:

    <RouteRule name="blue">
        <Condition>selected-route = "blue"</Condition>
        <TargetEndpoint>blue</TargetEndpoint>
    </RouteRule>
    <RouteRule name="green">
        <Condition>selected-route = "green"</Condition>
        <TargetEndpoint>green</TargetEndpoint>
    </RouteRule>
    <RouteRule name="default">
        <TargetEndpoint>default</TargetEndpoint>
    </RouteRule>

 

it works Kurt. Thanks for your help Dino & Kurt.

Hey Raghu,

Now that I understand your use case a bit better, that is:

When I receive a request with specific combinations of header values representing a store id (e.g. x-storedid:4) and a location id (e.g. x-locationid:2) I need to route to different targets (e.g. 4 and 2 --> blue, 8 and 4 --> green, all others --> default).

There's a solution that just uses a KVM. You store the lookup value in the KVM using a composite key made up of the store ID and location ID.

You can do this via InitialEntries in a KeyValueMapOperations policy:

<KeyValueMapOperations name="KV-GetParams" mapIdentifier="kvm-usage">
    <InitialEntries>

        <Entry>
            <Key>
                <Parameter>8</Parameter> <!-- store -->
                <Parameter>4</Parameter> <!-- location -->
            </Key>
            <Value>green</Value>
        </Entry>
    </InitialEntries>

Or using the kvm-admin-v1 API as per legacy docs for Parameter:

curl -X POST -H {{AUTH}}{HOST}}/kvm-admin/v1/organizations/{{ORG}}/environments/{{ENV}}/keyvaluemaps/kvm-usage/entries \
--header 'Content-type: application/json' --header 'Authorization:Bearer {{TOKEN}}' \
--data-raw '{
"key":"4__2",
"value": "blue"
}'

NOTE: the use of "__" to separate the composite key components.

Then in your proxy you merely do a KVM get to assign the value to a variable `private.route_target`

<KeyValueMapOperations name="KV-GetParams" mapIdentifier="kvm-usage">
    <Get assignTo="private.route_target">
        <Key>
            <Parameter ref="request.header.x-storeid"/>
            <Parameter ref="request.header.x-divisionid"/>
        </Key>
    </Get>

And finally in your Proxy Route Rules:

    <RouteRule name="blue">
        <Condition>private.route_target = "blue"</Condition>
        <TargetEndpoint>blue</TargetEndpoint>
    </RouteRule>
    <RouteRule name="green">
        <Condition>private.route_target = "green"</Condition>
        <TargetEndpoint>green</TargetEndpoint>
    </RouteRule>
    <RouteRule name="default">
        <TargetEndpoint>default</TargetEndpoint>
    </RouteRule>

This approach is very efficient as it only needs to use a single KVM get operation.

Hope that helps!