An easy way to integrate the HTTP 429 feature using RetrofitRetry - Lemberg Solutions
7 minutes

An Easy Way to Integrate the HTTP 429 Feature Using RetrofitRetry

Every developer knows the value of rate limiting in the app they work on. This is a crucial point that has the power to make a client's business really take off or fall apart. Without a steadily working app, users won't be able to use any online services, whether it's a food delivery app or an e-commerce platform.

So what exactly is rate limiting? This strategy ensures that, after a server receives a specific number of requests, the app stops knocking at that door and consequently manages to avoid throttling. This method of controlling network traffic helps to reduce excessive load on the server. If the number of requests exceeds the established volume, the app will show an HTTP 429 error status (provided it was configured accordingly). If the app wasn’t set up for the HTTP 429 feature, it will continue sending requests to the server, which can lead to its disruption. This scenario is hardly appealing to business owners, since a pause in their service will most likely cause financial losses. 

Therefore, rate limiting is a vital part of development, requiring special attention and effort. The good news is, there are means to make it less troublesome. 

As a rule, Android developers turn to the Retrofit library during the networking stage of the project, as well as for the implementation of rate limiting support. If an Android app uses the Retrofit library, this task will result in smooth sailing. This approach also brings another important advantage to the table — security of a customer’s server and, thereby, their business. The Retrofit library can be effectively employed for rate limiting, which inhibits overloading your server with requests and reduces denial of service (DoS) attacks. DoS attacks impede legitimate users from accessing your service, while rate limiting controls the flow of requests to your server and allows it to operate consistently. 

However, I discovered and developed an even more convenient way to implement rate limits — via the RetrofitRetry library, which in turn depends on the Retrofit library.

In this article, I will provide practical examples and code snippets on how to integrate the HTTP 429 feature into your app using my RetrofitRetry library created in my role as an Android developer at Lemberg Solutions.

More about rate limits

When an HTTP client runs more requests than an HTTP server can serve, it can lead to a server’s breakdown. That is to say, an HTTP server is configured to apply specific limits to either balance the load, restrict unusual server usage (which can be malicious), or limit clients according to business plans.

More often than not, rate limiting leans on monitoring the IP addresses of the apps sending requests and estimating the time frames between the requests. If an app assigned to one IP address outruns the normal number of requests, rate limiting will block the execution of the requests for a while.

For social media platforms, rate limiting is based on API configurations. However, it’s not the platforms themselves that have to deal with HTTP 429, but instead the third-party apps that in turn connect to them. As a rule, the direct use of Instagram, Twitter, or any other social media app doesn't knock users off with rate limiting.

Here are some references to existing Rate Limits implementations:

Rate Limits - Graph API
Throttle API requests for better throughput - Amazon API Gateway
Microsoft Graph Throttling Guidance

Below, I will explain how to easily get support for HTTP “Rate Limits” in Retrofit with RetrofitRetry.

RetrofitRetry basic usage

The Retrofit library serves as a REST client, supporting Android and Java and allowing for JSON retrieval and uploads. Let's consider the Retrofit’s main advantages for software developers:

  1. In contrast to other network libraries, Retrofit allows you to configure your code quickly.
  2. It is type-safe. 
  3. Retrofit supports Kotlin coroutines.
  4. This library shows good extensibility. 

Now, I will provide my practical experience of applying the RetrofitRetry library, which depends on Retrofit, for building reliable software. The latest version of the library, when this article was written, was 1.0.0. If a newer version is available to you presently, you can still apply my examples.

To apply the library, add the jitpack.io repository to the root build.gradle of your project:

allprojects {
    repositories {
        // google(), jcenter()...
        maven { url 'https://jitpack.io' }
    }
}

Add the library dependency to your module build.gradle:

dependencies {
    implementation "com.lembergsolutions:retrofitretry:1.0.0"
    // ...
}

Now, introduce a specific Call Adapter Factory into your Retrofit API builder:

private val api = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addCallAdapterFactory(RetrofitRetryCallAdapterFactory.createCoroutineAdapter())
    .build()
    .create(ApiInterface::class.java)

Finally, add an annotation to your API interface:

interface ApiInterface {
    @RetryOnError
    @GET("/news")
    suspend fun fetchNews(): Response<ResponseData>
}

As a result, when you call the fetchNews() method and the server returns the HTTP 429 status, the retry handler will automatically schedule a request retry after a delay specified in the response.

The delay can be specified in seconds:

Retry-After: 5

or by date:

Retry-After: Thu, 24 Feb 2022 04:30:00 EET

When a subsequent request is completed, its result will be delivered to your code. So, from the code side, the request will look like it has been running for a long time.

Note: The library uses Kotlin coroutines for retries scheduling and does not use blocking waits; thus, it doesn’t stopple IO threads.

RetrofitRetry advanced usage

The RetryOnError annotation allows to specify a custom retry handler and a maximum retries count:

interface ApiInterface {
    @RetryOnError(maxRetries = 10, handlerClass = MyCustomRetryHandler::class)
    @GET("/news")
    suspend fun fetchNews(): Response<ResponseData>
}

MyCustomRetryHandler should implement the RetryHandler interface with a single method:

/**
 * Get the delay to wait before retrying next request
 * @param request request object
 * @param result request result containing response or error
 * @param retryCount current retry count
 * @param maxRetries maximum retries set in annotation
 * @return >= 0 delay before retry or -1 to cancel retrying
 */
fun getRetryDelay(request: Request, result: Result<retrofit2.Response<out Any?>>, retryCount: Int, maxRetries: Int): Long

The RetryHandler class is instantiated for every API method declaration. Here is an example of a default handler in the RetrofitRetry that implements the HTTP 429 flow:

class Http429RetryHandler : RetryHandler {
    private val httpDateFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US)

    override fun getRetryDelay(request: Request, result: Result<retrofit2.Response<out Any?>>, retryCount: Int, maxRetries: Int): Long {
        if (result.isSuccess) {
            result.getOrNull()?.let { response ->
                if (!response.isSuccessful && response.code() == HTTP_TOO_MANY_REQUESTS_STATUS) {
                    val delayMillis = response.headers()[RETRY_AFTER_HEADER]?.let { tryParseDelayBySeconds(it) ?: tryParseDelayByHttpDate(it) }
                    if (delayMillis != null) return delayMillis
                }
            }
        }
        // don't repeat
        return -1
    }

    private fun tryParseDelayBySeconds(str: String): Long? {
        return try {
            TimeUnit.SECONDS.toMillis(str.toLong())
        } catch (e: Exception) {
            null
        }
    }

    private fun tryParseDelayByHttpDate(str: String): Long? {
        val repeatTimeMillis = tryParseHttpDate(str) ?: return null
        val currentTimeMillis = Calendar.getInstance().timeInMillis
        if (repeatTimeMillis >= currentTimeMillis) return repeatTimeMillis - currentTimeMillis
        return null
    }

    private fun tryParseHttpDate(str: String): Long? {
        return try {
            httpDateFormat.parse(str).time
        } catch (e: Exception) {
            null
        }
    }

    companion object {
        const val HTTP_TOO_MANY_REQUESTS_STATUS = 429
        const val RETRY_AFTER_HEADER = "Retry-After"
    }
}

If getRetryDelay returns a positive value, the library schedules a retry of a request using the supplied DelayRunner. This interface allows for scheduling a delayed run of a function:

interface DelayRunner {
    /**
     * Schedule function execution after specified delay.
     * Any previously scheduled function will be cancelled.
     * @param millis delay in milliseconds
     * @param fn function to call
     */
    fun scheduleDelayedRun(millis: Long, fn: () -> Unit)

    /**
     * Cancel previously scheduled delayed run.
     */
    fun cancelDelayedRun()
}

The default implementation uses the coroutines framework, but you can supply a custom DelayRunner:

private val api = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addCallAdapterFactory(RetrofitRetryCallAdapterFactory.createCustomAdapter(myDelayRunner))
    .build()
    .create(ApiInterface::class.java)

At this point, you developed a code that tracks the number of requests reaching the server.

Wrapup

By applying practical recommendations and examples from this article, you can access an easier way to make the HTTP 429 feature a part of your app. With my code snippets and detailed explanations, you will ensure the stability of a customer's service. Rate limiting is key to protecting a server from an exceeding number of requests that can disrupt its performance. This article shows how to build in a reliable HTTP 429 feature effortlessly and quickly, using the RetrofitRetry library, which depends on the Retrofit library.

Support for HTTP “Rate Limits” can be implemented through the RetrofitRetry library, which I’ve analyzed above. RetrofitRetry is a solution to the application of Retrofit API builder. The HTTP 429 feature can be created via the RetrofitRetry, counting the requests and handling them correctly – without damaging a customer's service. I believe that the Retrofit library is one of the most convenient and efficient ways to make your HTTP 429 work! Please let me know if you think otherwise.

And if you happen to be someone looking for professional Android developers to build your app, Lemberg Solutions is here to help. We create secure and effective solutions in the IoT, digital experiences, and AI realms. Use our contact form to start building your unique service. 

Article Contents: