If you’re regularly working with APIs, you’ll know the struggles of ensuring that they’re secured, protected and managed, as well as building out the core functionality.
If you haven’t already, you’ll want to check out API Management. In essence,
API management is the process of creating and publishing web application programming interfaces (APIs), enforcing their usage policies, controlling access, nurturing the subscriber community, collecting and analyzing usage statistics, and reporting on performance. API Management components provide mechanisms and tools to support developer and subscriber communities
Using the open source Gravitee.io API Management platform (GitHub repo) allows you to create, design and manage the lifecycle of your APIs. With many rich and powerful policies available, you have a huge array of tools and techniques to superpower your APIs, such as:
- Check authentication and manage other security aspects
- Transform headers
- Protocol Mediation
- Protect your backends against malicious and accidental traffic spikes
But sometimes, your designs become so complex that it is almost impossible to know where design and configuration problems occur when they pop up. Don’t panic, Debug Mode is here to the rescue. Debug Mode is a feature that unleashes the power of APIs Publisher, allowing you to design an API, (shadow) deploy it (don’t worry, we’ll explain what shadow deployment is later on), and understand exactly through which policies have been applied to the API call.
In this blog post, we are going to put ourselves in the shoes of an API publisher, looking at an API displaying some weird behaviors. We will walk through a simple example of an API, and show you not only how to use Debug Mode, but also how it works under the hood.
🙋♂️ Who am I ?
I am Yann Tavernier and I am a developer in the amazing APIM team. I had the opportunity to work on Debug Mode, and I’m delighted to walk you through and explain this feature!
🔑 Key concepts and components
Before we start looking at Debug Mode, let’s provide an overview of all of the key concepts and components that we’ll be using in this blog.
Key components:
- APIM is the API Management solution made by Gravitee.io.
- Gateway is the component of APIM responsible for handling requests from customers. As well acting as the proxy for APIs, it will also apply transformation, checks, rate limiting, and so forth on incoming requests and outgoing responses.
Key concepts:
- API Publisher is a role type for APIM, representing the user of the company in charge of designing and publishing the APIs.
- API is the representation of your backend inside APIM, and is composed of elements such as documentation, users, design, properties, etc.
- Plan is the access and service layer on top of an API for consumer Applications. Consumer Applications need to subscribe to a plan to be able to consume an API.
- Policy is a service or action that can be executed on an API request or response to perform checks, transformation or other services to it. As well as out of the box policies, you can also create your own.
🎬 Introducing our scenario
We are now going to introduce our simple scenario to walk through how to use Debug Mode.
All our examples will be done on a local installation. You can use this command to run APIM 3.17 with docker-compose
:
cd /tmp && mkdir -p apim && cd apim && curl -L https://raw.githubusercontent.com/gravitee-io/gravitee-docker/master/apim/3.x/docker-compose.yml -o "docker-compose.yml" && export APIM_VERSION=3.17.1 && docker-compose down -v && docker-compose pull && docker-compose up
To access the console, go to the following address: http://localhost:8084
.
The default credentials are:
- Login:
admin
- Password:
admin
If you are interested in a more detailed guide, you can check this awesome blog from Ljubica Lazarevic!
We will rely on an API called Library backend. You can import it thanks to its API definition here.
This backend will be accessible on http://localhost:8082/library-backend
and return mocked dataset:
{
"books": [
{
"title": "A tale of two cities",
"author": "Charles Dickens",
"_internalReference": "gio_b1"
},
{
"title": "The Lord of the Rings",
"author": "J.R.R Tolkien",
"_internalReference": "gio_b2"
},
{
"title": "The Hitchhiker's Guide to the Galaxy",
"author": "Douglas Adams",
"_internalReference": "gio_b3"
}
]
}
We will analyze this dataset later.
As an API Publisher, I want to design a “Library” API to access my Library backend.
You can create it following the documentation and follow this guide step by step or you can directly import the resulting API Definition.
First of all, I need to choose the type of plan to use for my API. We will choose two plans: a Keyless plan, called Free, and an API-Key one, called Premium.
Let’s firstly design the Free plan. This plan allows anybody to reach our APIs. To protect our backend systems, as well as improve performance, we want to manage the amount of traffic that will hit our API with this plan. We will limit the access thanks to the Rate Limit policy. One thing to note – as this is a free plan, consuming applications do not need to create a subscription.
As the API is going to be publicly accessible, we will want to hide away some private data in the response, the _internalReference
field. To do this, we’re going to use the JSON to JSON Transformation policy.
Here is the configuration of JSON To JSON Policy. Its purpose is to remove the _internalReference
field in the array of books
:
[
{
"operation": "remove",
"spec": {
"books": {
"*": {
"_internalReference": ""
}
}
}
}
]
Let’s deploy the API (you can do this by syncing the API when prompted) and call it with a cURL. Calling curl http://localhost:8082/library
will output:
{
"books": [
{
"title": "A tale of two cities",
"author": "Charles Dickens"
},
{
"title": "The Lord of the Rings",
"author": "J.R.R Tolkien"
},
{
"title": "The Hitchhiker's Guide to the Galaxy",
"author": "Douglas Adams"
}
]
}
Remember, the _internalReference
removal was done on the Freemium plan. Here, with the Internal plan, we keep the original data. We will use this opportunity to use the Assign Attributes policy to add an internal attribute in the context. This attribute will be the concatenation of the application’s ID followed by “-internal” and accessible through the “internal” key.
To do that, we configured the Assign Attribute policy to add an internal
attribute with the following value:
{#context.attributes["application"]}-internal
.
Now, calling the APIs with an API Key with curl http://localhost:8082/library?api-key=7cc755d1-378b-4099-9650-9ee9330be55c
will output:
{
"books": [
{
"title": "A tale of two cities",
"author": "Charles Dickens",
"_internalReference": "gio_b1"
},
{
"title": "The Lord of the Rings",
"author": "J.R.R Tolkien",
"_internalReference": "gio_b2"
},
{
"title": "The Hitchhiker's Guide to the Galaxy",
"author": "Douglas Adams",
"_internalReference": "gio_b3"
}
]
}
NOTE: You may have noticed that we cannot see the attribute we set. In fact, the attribute only lives inside the API Gateway for the time of the call. This is a behavior of the policy we are using, and it is internal to the gateway.
Finally, we will create a flow on the API using the JSON to JSON transformation again to apply discounts to a book based on certain conditions.
The discount will be applied if the response is either:
- Request is done with Header —
“X-Library-Discount”:”discount percentage”
. - Attribute
internal
is not empty.
By default, the discount value will be the value of the X-Library-Discount
header, or 10 percent by default.
To achieve that, we will firstly configure the condition of the policy:
{(#request.headeers['X-Library-Discount'] != null && #request.headers['X-Library-Discount'].size() > 0) || {#context.attributes['internal'] != null}}
Then, we will use this configuration for JSON to JSON Policy:
[
{
"operation": "shift",
"spec": {
"*": "&"
}
},
{
"operation": "default",
"spec": {
"discount": "{#request.headers['X-Library-Discount'] != null ? #request.headers['X-Library-Discount'][0] : 10}%"
}
}
]
Calling the API with curl http://localhost:8082/library?api-key=7cc755d1-378b-4099-9650-9ee9330be55c
will output:
{
"message":"Request failed unintentionally",
"http_status_code":500
}
We have some issues with our last flow, but what happened? It’s hard to say by just using cURL or Postman. As the API design complexity increases, the risk of problems also increases. How can we avoid deploying our API to our users until we’ve solved all of the outstanding issues?
The APIM team created the new Debug Mode to solve this exact challenge.
💥 Here comes the Debug Mode!
The Debug Mode feature provides a way to have a global understanding and overview of what happens inside the gateway. With this feature, we are now able to send a request using our HTTP client, and see exactly which policies have been executed, with their inputs and outputs.
In the following screenshot, you can see an example of a request flow being examined using the Debug Mode:
The request is using the same API key as before, and we can see the Response ended in 500 — Internal Server Error
.
Let’s take a look at what the Debug Mode feature is showing us. The first important element of this feature is the timeline. It represents each policy that was run, shaped as a card. A card is composed of several elements:
- The name of the policy (e.g Assign Attributes)
- Its stage (either: platform, security, plan, apis) and scope (either: headers, body): Plan > Header
- Its execution time (e.g. 0.227ms)
It represents the real order of execution of your policies, which may have changed from your initial design. You may notice that the execution could be different from what you expected!
If you’re a regular user of Gravitee, you are probably used to configuring policies and choosing one of the following scopes: REQUEST
, REQUEST_CONTENT
, RESPONSE
, RESPONSE_CONTENT
. For REQUEST
and RESPONSE
scopes, policy execution has no view or modification access on the content, contrary to REQUEST_CONTENT
and RESPONSE_CONTENT
.
We will not deep dive into this topic, but an important thing to note is that the gateway will first execute the header policies (REQUEST
/ RESPONSE
) and then the content policies (REQUEST_CONTENT
/ RESPONSE_CONTENT
).
Let’s click on the card to explore what information it provides.
Thanks to this differential view (we will call it a “diff”), we now can see and understand how the Assign Attributes works: it adds an attribute in the Attribute object of the internal context of the call.
So now, why did our request fail? Let’s scroll the timeline and find the failing policy.
Here it is! We can see two icons appearing on our JSON to JSON Transformation card. Looking at the Inspector, we can see it occurring on Api > Body.
- IF… means the policy is conditional: it is executed only if the condition is evaluated to true.
- ! means the policy has an error.
Looking at the diff, we can see the error message: “Request failed unintentionally”. This is what we saw and caught during the request processing. Unfortunately, in this case, it’s not very helpful.
The interesting thing, however, is the Condition block:
We can see that the condition is invalid. Reading the condition once more, it looks like we configured our policy using request.headeers
instead of request.headers
.
🛠 How does Debug Mode work ?
So how exactly does Debug Mode work? Let’s take a look under the hood.
When you send your Debug Request, the API Gateway detects the event, and it starts its processing.
It will deploy your current API in a particular state we call “Shadow Deployment”. This just means that the API is deployed, but not accessible to the public. This deployment is only used by Debug Mode, meaning there is no risk of your customers (or any other users!) accessing this version of your API!
You don’t need to deploy your API the regular way to be able to debug it. This can be really useful when you are doing some tests to find good tuning for a policy.
Let’s see it how it works:
- You send your request to the Debug Mode request form.
- API Management will then create a Debug Event and store in to the database
- The event’s ID is returned to the user
- Then, the Console UI will poll regularly from APIM REST API until the event status is “SUCCESS”.
- The Gateway will regularly sync with the database to check if there are some events with the status “TO_DEBUG”
- When an event is found, we deploy our API in shadow mode. It implies two things:
- This API is not accessible for any user, only by the Debug Mode and only for the lifetime of the Debug session.
- This API is built with a particular encapsulation, providing the ability to store the execution information during all the lifecycle of the API.
- An internal HTTP call with the content of the user’s debug request is made to this API. It has the advantage of exactly replicating what would happen if you tried to call your API with cURL or Postman.
- The gateway will process your request and transform data based on the configured policies, as usual. The only difference with a regular deployment is that information about headers, payload, context will be stored in the Debug context
- Once the request is finished, the API is undeployed, and debug information is saved to the event.
- Finally, the result is displayed to the user in the Console UI.
🧑💻 Back to business!
As we said earlier, the condition was misspelled, so we have now fixed request.headeers
to request.headers
in the JSON To JSON policy.
Let’s do the call once again in debug mode:
The response is 200 — OK
, and we can see the discount applied in the response body.
Debug Mode can also tell us if a policy is not executing due to an unmet condition.
If we do not call the API with an api-key or X-Library-Discount
, this JSON to JSON policy will not be applied, as you can see in the timeline:
📝 Wrap Up
We’ve just done a simple example of a failing APIs with policy issues, and how to troubleshoot it with Debug Mode. We were able to see and step through the flows to understand what was really happening and what was causing the problem. Also, we have looked at how exactly Debug Mode works under the hood.
We would love to hear your feedback, and how you’re getting on with Debug Mode. If the topic interests you and you have questions, do not hesitate to join our community forum. The team will be happy to discuss it with you!