Wednesday, November 6, 2024

Azure APIM: Policy Expression to Read application/x-www-form-urlencoded Request Data

Recently I had a requirement where a particular Client sends some request data as application/x-www-form-urlencoded and needed to get those moved to the request header before it gets forwarded to the Backend.

In this post, let's see how we can read request data sent via application/x-www-form-urlencoded.

If we have a look at .NET Framework types allowed in policy expressions, we have access to System.Net.WebUtility.

So we can make use of that as follows:
<policies>
    <inbound>
        <!--Extract URL Encoded Form Data (if any)-->
        <set-variable name="serializedFormData" value="@{
            var formData = new System.Collections.Generic.Dictionary<String, String>();
            if(context.Request.Headers.GetValueOrDefault("Content-Type", "") != "application/x-www-form-urlencoded")
            {
                return JsonConvert.SerializeObject(formData);
            }

            string encodedBody = context.Request.Body.As<String>(preserveContent: true);
            string decodedBody = System.Net.WebUtility.UrlDecode(encodedBody);
            foreach (string key in decodedBody.Split('&'))
            {
                string[] keyValue = key.Split('=');
                formData.Add(keyValue[0], keyValue[1]);
            }

            return JsonConvert.SerializeObject(formData);
        }" />
        <!--Check if the interested headers are sent in the form data-->
        <set-variable name="isRequiredHeadersSentInFormData" value="@{
            string serializedFormData = context.Variables.GetValueOrDefault<String>("serializedFormData");
            System.Collections.Generic.Dictionary<String, String> formData = 
                JsonConvert.DeserializeObject<System.Collections.Generic.Dictionary<String, String>>(serializedFormData);

             return formData.ContainsKey("key1") && formData.ContainsKey("key2");
        }" />
        <!--Set the headers from the form data if present-->
        <choose>
            <when condition="@(context.Variables.GetValueOrDefault<bool>("isRequiredHeadersSentInFormData"))">
                <set-header name="x-key1" exists-action="override">
                    <value>@{
                        string serializedFormData = context.Variables.GetValueOrDefault<String>("serializedFormData");
                        System.Collections.Generic.Dictionary<String, String> formData = 
                            JsonConvert.DeserializeObject<System.Collections.Generic.Dictionary<String, String>>(serializedFormData);

                        return formData["key1"];
                    }</value>
                </set-header>
                <set-header name="x-key2" exists-action="override">
                    <value>@{
                        string serializedFormData = context.Variables.GetValueOrDefault<String>("serializedFormData");
                        System.Collections.Generic.Dictionary<String, String> formData = 
                            JsonConvert.DeserializeObject<System.Collections.Generic.Dictionary<String, String>>(serializedFormData);

                        return formData["key2"];
                    }</value>
                </set-header>
            </when>
        </choose>
        ...
    </inbound>
    ...
</policies>
And now we test with trace, we can see the request is correctly being transformed.
Trace
Hope this helps.

Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment