Use Azure API Management (APIM) to rewrite a response

Have you ever been on a project where an API returns a response that you can’t, or don’t want to, handle in your own application? Or a customer asks to generate a different response?

Yeah, me neither…

If you ever come across a project, where they want you to return a response like this:

HTTP/1.1 200 OK
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json
Date: Wed, 10 Jan 2024 20:59:21 GMT

{
    "error": 404,
    "status": "NotFound",
    "description": "Could not find the object"
}

This post is for you!

Azure API Management (APIM)

Azure API Management has a lot of features available, one of them is to rewrite requests AND responses. This feature is especially useful if you have to integrate with a third party using a strange type of requests or responses, or when you’re building a platform used by thousands of customers and a couple of them can’t integrate proper with it.
While APIM has a dozen of more cool features, I won’t go into those in this post.

At this moment I’m working on a platform solution and we try to adhere to the most appropriate HTTP response codes for each situation. There are a lot of them!
Most customers will be able to handle the common status codes, like 200, 400 and 500. However, we use many more. While designing our API we adhere to the Microsoft Azure REST API guidelines, which I think is quite a good document to start with when creating APIs.

Of course, not every piece of software will be able to handle the responses we produce. A lot of times, especially in legacy software systems, responses need to be handled in a very specific way. Needless to say, I don’t want to mess around with standardized responses of our platform for specific pieces of software. This is where the APIM rewrite policies come into play.

Rewrite a response

To rewrite a response, coming from a backend API, you can use the set-body policy. There’s also the return-response policy, but this one appears less useful for the purpose of rewriting a response due to this note:

A liquid template doesn’t work when specified inside the body (set using set-body) of the return-response policy. The return-response policy cancels the current execution pipeline and removes the request body and response body in the current context. As a result, a liquid template specified inside the policy receives an empty string as its input and won’t produce the expected output.

According to the earlier mentioned REST API guidelines, an error response should look somewhat like this:

{
  "error": {
    "code": "InvalidPasswordFormat",
    "message": "Human-readable description",
    "target": "target of error",
    "innererror": {
      "code": "PasswordTooShort",
      "minLength": 6,
    }
  }
}

If the client needs to handle this response in a different format, you can create a policy using the liquid syntax of APIM.

<policies>
    <inbound>
        <base />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
        <choose>
            <when condition="@((int)context.Response.StatusCode >= 400 && (int)context.Response.StatusCode <= 499)">
                <set-status code="200" reason="OK" />
                <set-body template="liquid">
                    {
                        "error": "{{ context.Response.StatusCode }}",
                        "status": "{{ body.error.code }}",
                        "message": "{{ body.error.message }}"
                    }
                </set-body>
            </when>
            <when condition="@((int)context.Response.StatusCode >= 500 && (int)context.Response.StatusCode <= 599)">
                <set-status code="200" reason="OK" />
                <set-body template="liquid">
                    {
                        "error": "{{ context.Response.StatusCode }}",
                        "status": "{{ body.error.code }}",
                        "message": "{{ body.error.message }}"
                    }
                </set-body>
            </when>
        </choose>
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

This policy will return a response with the 200 OK status code, and a rewritten payload in the body.

Is it a good idea to do this? It depends.
For this specific example, I’d prefer if the client software would be rewritten to handle the original responses. However, I have seen enough real world (legacy) projects to know this isn’t feasible or even possible to do. Without messing too much with the backend APIs, you can still accomodate these type of clients.


Share

comments powered by Disqus