HttpQueue - Forget about rate limiting, focus on the rest!

Roblox Http Queue

A library to queue Roblox HTTP Requests for all your different external services.

Current Version: 1.0.0

Disclaimer: This project is currently in a Release Candidate status. This means it should work, but is not completely stable yet. Make sure to report me bugs!


Writing code to make requests is simple, and maybe fun. Writing code that gracefully handles everything that can go wrong in a request… Well, that’s a boring thing to do.

This library is intended to help easing this by, in particular, handling servers that impose rate limits. Writing code to handle that and make sure every request we make is accepted* by the server and is not lost.

This project is powered by evaera’s Promise implementation and Osyris’ t typechecking library.

The library is available at GitHub and you can use it according to the terms of the MIT license.

* For accepted I mean “not rate-limited”. I cannot make guarantees that the service will not refuse to process the request due to, for example, invalid tokens or permissions.

Installation

Just grab the .rbxmx file from the releases page and drop into your project - as simple as that!

roblox-ts

You can use this library on your roblox-ts project via the @rbxts/http-queue package.

Documentation

The library already features built-in API documentation that can be read using ProbableAI’s Documentation Reader plugin. Happy coding!

Usage

Require the module:

local Http = require(game:GetService("ServerScriptService").HttpQueue)

We can start by doing a simple task:
Create a request and send it - this is pretty much the same behavior of using HttpService directly:

local request = Http.HttpRequest.new("https://some.website.com/", "GET", nil, {auth = "im very cool", cool = true})
-- Actual Request URL is https://some.website.com/?auth=im very cool&cool=true
-- The :Send() method returns a Promise that resolves to a response!
request:Send():andThen(function(response)
    print(response.Body)
end):catch(function(err)
    print("ERROR!", err)
end)

-- Do some work while we wait for the response to arrive

-- If you want to yield the script until the response arrives
local response = request:AwaitSend()

This is cool and all, but we can make this more interesting. Let’s say you want to use Trello in your application. Unfortunately, the rate limiting of Trello is very tight (10 requests per 10 seconds per token for Roblox clients), but you also want to send 1000 requests without dealing with 429’s. How do we do it?

Instead of worrying about it yourself, you can delegate the responsibility of dealing with the rate limits to a queue.

local TrelloQueue = Http.HttpQueue.new({
    retryAfter = {cooldown = 10} -- If rate limited, retry in 10 seconds
    maxSimultaneousSendOperations = 10 -- Don't send more than 10 requests at a time: This value shouldn't be higher than the rate limit itself
})

-- Let's change the name to a Trello board, 1000 times (don't do this at home!)

for i = 1, 1000 do

    local request = Http.HttpRequest.new("https://api.trello.com/1/boards/5d6f8ec6764c2112a27e3d12", "PUT", nil, {
        key = "Your developer key",
        token = "Your developer token",
        name = "Your board's new name (" .. tostring(i) ..")"
    }))

    TrelloQueue:Push(request):andThen(function(response)
        -- This will never print "429 Too Many Requests"
        print(response.StatusMessage)
    end)

end

-- Do some work while we wait for the response to arrive

-- If you want to yield the script until the response comes in:
local response = TrelloQueue:AwaitPush(request)

When pushed to a queue, requests that are throttled are tried until accepted, and then the promise resolves.


The queue works on a “first come, first serve” basis. This means that requests being pushed first will be dealt with first by the queue. (HOWEVER, this doesn’t mean the responses will arrive in order!)

You can override that behavior by passing a priority parameter to the :Push() or :AwaitPush() methods. There are three options available:

HttpRequestPriority.Normal - the default priority. The request is pushed to the back of the regular queue.

HttpRequestPriority.Prioritary - The request is pushed to the back of the prioritary queue, that is done by the queue runner before the regular queue.

HttpRequestPriority.First - The request is pushed to the front of the prioritary queue.

NOTE: The priority features should be used sparingly.

Example:

TrelloQueue:Push(request, Http.HttpRequestPriority.Prioritary)

Type Guards

This library also comes with type guard functions that allow you to check whether a value is actually what you want:

isHttpRequest(value)

isHttpRequestPriority(value)

isHttpResponse(value)

isHttpQueue(value)

These return true if the value is a and false otherwise. They might be handful if you’re writing your own library.


And that’s it! Happy coding!

Props to @VoidedBIade for literally helping me fix some issues while developing this!

Changelog

Changelog
Version Changes
1.0.0-rc.1 Initial pre-release
1.0.0-rc.2 Fixed roblox-ts typings; updated to latest Promise implementation
1.0.0-rc.3 The queue is now also aware of throttling from the HttpService itself. URL’s that do not have a “/” in the end will automatically have one appended (this is important if you also have query options)
1.0.0 Fixed Url normalization. The “/” at the end will only be appended when an Url path does not exist already.
1.1.0 Now you can determine the cooldown period of a queue using a callback, which allows you to manually inspect the response. (Useful if the service you’re using has a very specific way of determining the cooldown period)
56 Likes

Looks pretty promising! May use it on one of my projects.

2 Likes

Release Candidate 3

This release candidate brings two new “features”:

  • The queue will now also automatically handle cases where you blow through the HttpService limit of 500 requests/minute.
  • If you provide an URL that doesn’t end in a forward-slash This: /, one will be automatically appended. For example: https://example.org becomes https://example.org/
    • This is especially important if you also pass query parameters to the request. For example, https://example.org?robloxiscool=true is not a valid URL, but https://example.org/?robloxiscool=true is!

Hopefully this will be the last release candidate before going stable!

You can get the new version either from GitHub Releases, or directly from Roblox as a free model:

HttpQueue has been filtered :sob:

5 Likes

Stable Release

After a stable period with no major issues, 1.0.0 has been released.

  • I have fixed the Url normalizing. It will still coerce https://example.com into https://example.com/, but no longer https://example.com/page into https://example.com/page/. This should solve some complaints I’ve received about this.

Getting the module

Enjoy! And sorry for the bump

1 Like

1.1.0

  • You can now use a callback to inspect a rate-limited request’s response in order to determine how long the queue should stall.
    • This feature is used when creating the queue. The function receives the response to a rate-limited request (this is, to which the response had a status code of 429) and should return the number of seconds the queue should stall. Example:
local myQueue = HttpQueue.new({
    retryAfter = {callback = function(response)
        -- Do something
    end}
})

Getting the module



It’s also worth noting that there’s a related feature that was shipped in earlier versions but not actually documented - You can also pass an header to the queue - when it gets rate-limited, it will look into that header on the response and use that value as the cooldown period, in seconds, like this:

local myQueue = HttpQueue.new({
    retryAfter = {header = "x-rate-limit-cooldown"}
})
2 Likes

May be a bit late but this is very underappreciated!

I commend you for making this!
very useful! :smiley:

just wanted to thank you!

1 Like

I dont know if your taking questions but.
How would I go about making my webhook go under the rate limit?
e.g I want to send a limit of 5 messages every minute anything over that gets thrown into a queue.
How would I go about doing this? I tried making a queue but it didn’t listen for some reason

We’re planning to use this module on a large-scale game for effectively using HTTP requests. However, I highly advise you to update this module to use the new task library. Is this module also going to be maintained incase of bugs, future support?

1 Like