0
votes

I want to limit my products like this:

  • starter: no parallel calls (only allow synchronous calls)
  • premium: 10 parallel calls (allow asynchronous call with a limit)

The service behind the api gateway needs some time so some people want to make asynchronous calls to get more results.

The only limitations I found from azure are rate and quota limits. But their limitations are for calls per time, I need it for parallel calls.

I thought about this logic:

<inbound>
<http-call>
send user id to a azure function, that increases the counter for the user, returns the current counter
</http-call>
<when>
compare counter value with the product limit (x = 0 for starter or x <= 10 for premium)
if true route to the service, if false generate a "too many requests" response
</when>
</inbound>

<outbound>
<http-call>
decrease the counter
</http-call>
</outbound>

I am not sure if this is the best logic, because the function needs time to update the values. Is there any way to hold some kind of "shared variable", that can be accessed by every requests to check the counter in the api management and not by an external function?

1
At the moment the only way is either external service (like in your example) or cache (like below by mimo). New policy will be introduced soon to allow limit concurrency, but only per node, i.e. not globally for whole service. - Vitaliy Kurokhtin

1 Answers

0
votes

I found a way with caching (I think the cache function is global, but it's not proven. You just have a cached array will user-ids as keys and value the amount of parallel requests):

<inbound>
        <set-variable name="user-id" value="@(context.User.Id)" />
        <!--Look for user-counter for this user in the cache -->
        <cache-lookup-value key="@("user-counter-" + context.Variables["user-id"])" variable-name="user-counter" />
        <choose>
            <!-- check the limit -->
            <when condition="@(context.Variables.ContainsKey("user-counter"))">
                <choose>
                    <!-- -->
                    <when condition="@((int)context.Variables["user-counter"] > 0)">
                        <return-response>
                            <set-status code="429" reason="TOO MANY REQUESTS" />
                            <set-body>too many parallel requests</set-body>
                        </return-response>
                    </when>
                    <when condition="@((int)context.Variables["user-counter"] <= 0)">
                        <cache-store-value key="@("user-counter-" + context.Variables["user-id"])" value="@((int)context.Variables["user-counter"] + 1)" duration="100000" />
                    </when>
                </choose>
            </when>
            <!-- If we don’t find it in the cache, create it with the value 1 -->
            <when condition="@(!context.Variables.ContainsKey("user-counter"))">
                <cache-store-value key="@("user-counter-" + context.Variables["user-id"])" value="0" duration="100000" />
            </when>
        </choose>
</inbound>

<outbound>
        <base />
        <!-- remove user-counter -->
        <cache-lookup-value key="@("user-counter-" + context.Variables["user-id"])" variable-name="user-counter-old" />
        <cache-store-value key="@("user-counter-" + context.Variables["user-id"])" value="@((int)context.Variables["user-counter-old"] - 1)" duration="100000" />
</outbound>