HTTPService to make 1 request for all Servers

Hi everyone! This is my first post on the DevForum.
So, you have the rare problem that, you are using HTTPService in your game, but you have a limited request amount and when every Server requests for themselves its gonna reach the limit soon, want one server to request the data and distribute it to everyone else? Well lucky for you i did this already, you can download the code here.

local UpdatePrices = {}
local HTTPService = game:GetService("HttpService")

function UpdatePrices.tick()
	local MessagingService = game:GetService("MessagingService")
	local Players = game:GetService("Players")
	local RunService = game:GetService("RunService")

	local joinTopic = "JoinTopic"
	local responseTopic = "ResponseTopic"
	local removeTopic = "RemoveTopic"
	local addTopic = "AddTopic"
	local informationTopic = "InformationTopic"
	local recieveInformationTopic = "RecieveInformationTopic"
	local refreshTopic = "RefreshTopic"

	local serverList = {}
	local isRootServer = false
-- Example of Data
	local recentData = {
		gold = 2200,
		silver = 36,
		platinum = 1080,
		palladium = 980,
		copper = 0.03,
		zinc = 0,
		lead = 0,
		aluminium = 0,
		nickel = 0
	}

	local exampleFormatServerTable = {
		{jobId = 132324, Row = 1},
		{jobId = 125432, Row = 2},
		{jobId = 532463, Row = 3},
		{jobId = 635435, Row = 4}
	}

	local function getCurrentRowByJobId(jobId)
		for _, server in ipairs(serverList) do
			if server.jobId == jobId then
				return server.Row
			end
		end
		return nil
	end

	local function respondCurrentData()
		MessagingService:SubscribeAsync(informationTopic, function(message)
			MessagingService:PublishAsync(recieveInformationTopic, recentData)
			print("gave recent Data to other")	
		end)
	end

	local function getCurrentData()
		MessagingService:SubscribeAsync(recieveInformationTopic, function(message)
			print("Got response, got recentData from Other Servers")
			recentData = message.Data
		end)
		MessagingService:PublishAsync(informationTopic, "reqData")
	end

	local function collectData()
		MessagingService:SubscribeAsync(refreshTopic, function(message)
			print("Recieved New Data from Root")
			recentData = message.Data
		end)
	end

	local function printTable(t)
		for key, value in pairs(t) do
			print(key, value)
		end
	end

	local function printTableSpecial(t)
		for i, innerTable in ipairs(t) do
			print("Row " .. i .. ":")
			for key, value in pairs(innerTable) do
				print("  " .. key .. ": " .. value)
			end
		end
	end
	
	local url = "Your_API_URL"

	local function requestData()
		local success, response = pcall(function()
			return HTTPService:GetAsync(url)
		end)

		if success then
			local jsonResponse = HTTPService:JSONDecode(response)
			if jsonResponse then
				recentData = {
					-- Override your old Data
				}
				print("Prices updated successfully.")
			else
				warn("Unexpected JSON response structure")
			end
		else
			warn("Failed to fetch prices: " .. response)
		end
	end


-- Example of a DataRefreshing Function
	local function getData()
		if getCurrentRowByJobId(game.JobId) == 1 then
			print("Updated Data first Index")
			requestData()
			printTable(recentData)
		end
		while wait(14400) do
			print("Rstep")
			printTableSpecial(serverList)
			printTable(recentData)
			if getCurrentRowByJobId(game.JobId) == 1 then
				isRootServer = true
				requestData()
				print("Root-Server: Data was refreshed.")
				MessagingService:PublishAsync(refreshTopic, recentData)
			end
			--updateData()
		end
	end

	local function requestServerTable()
		local gotresponse = false
		MessagingService:SubscribeAsync(responseTopic, function(message)
			local serverTable = message.Data
			serverList = serverTable
			gotresponse = true
			print("Got and used Response")
		end)
		MessagingService:PublishAsync(joinTopic,"reqTable")
		wait(1)
		MessagingService:SubscribeAsync(joinTopic, function(message)
			MessagingService:PublishAsync(responseTopic, serverList)
		end)
		if not gotresponse then
			print("No response, lone Server, setting as Root.")
			isRootServer = true
		end
		MessagingService:PublishAsync(addTopic, game.JobId)
	end

	local function findNextAvailableRow(tabler)
		local maxRow = 0
		for _, server in ipairs(tabler) do
			if server.Row > maxRow then
				maxRow = server.Row
			end
		end
		return maxRow + 1
	end

	local function addServer()
		MessagingService:SubscribeAsync(addTopic, function(message)
			local newRow = findNextAvailableRow(serverList)
			table.insert(serverList, {jobId = message.Data, Row = newRow})
			print("Sucessfully added Server: "..message.Data.." Row: "..newRow)
		end)
	end

	local function moveServersForward(removedRow)
		for _, server in ipairs(serverList) do
			if server.Row > removedRow then
				server.Row = server.Row - 1
			end
		end
	end

	local function removeServer()
		MessagingService:SubscribeAsync(removeTopic, function(message)
			local serverid = message.Data
			for i, server in ipairs(serverList) do
				if server.jobId == serverid then
					local row = server.Row
					table.remove(serverList, i)
					moveServersForward(row)
					print("Sucessfully removed server: "..serverid.." Row: "..row)
				end
			end
			print("Failed to remove server: "..serverid)
		end)
	end

	local function checkShutdown()
		if not game:GetService("RunService"):IsStudio() then
			MessagingService:PublishAsync(removeTopic, game.JobId)
		end
	end
	addServer()
	removeServer()
	requestServerTable()
	getCurrentData()
	respondCurrentData()
	collectData()
	getCurrentData()
	getData()
	game:BindToClose(checkShutdown)
end

coroutine.resume(coroutine.create(UpdatePrices.tick))

return UpdatePrices

This Module is currently being used in one of my new games, which revolves around this Modulescript, check it out if you want: Buy Gold but for its actual value game

Yeah so if you got any Questions on how to use this, then reply!
Have a nice day.

7 Likes

based, this will help more beginners on their way!
waiting for more guides from you

1 Like

Lets not forget MessagingService has rate limits and it can’t always send info to all existing servers (for example when your game got too many players)

1 Like

yeah its intented for any kind of Data that is refreshed every >30min, below that i havent tested it so idk, although i have been able to get a singluar refresh rate of 6s

We need to find its limits so we can be sure.
Also, what chooses the main server?

I mean, lets say the 1st server that run this closed (somehow crashed or any other way)

  • Which one would use this next?
  • How does it chooses the main server to run this? (I mean won’t all servers will run this on start)

Also I tried Github and etc. for Global Update in-live but they all have limits
the best one i was able to find is:

  • Update Module Script from Roblox
  • Make servers request it every 1 min
  • take the loaded module and copy paste its contents to Server’s Main Module script for Prices/Weapon Damages/etc.

it seems to have more limit than dealing with github etc.
I ran it at 0.05 while true do
and it didn’t stop for a moment for whole 10 mins weirdly.

So this script already includes the entire system obviously, im sorry i forgot to include a explanation of the system, so when lets say our server is made, a player joins, first the server sends a message to everyone and listens for a response, if there is no response it will set itself as the root server, putting itself in the server table that includes ServerTable = { {jobId, Row} }, the server that is at row 1 is always the root server, now when a new server is added to the complex, it sends this same message as the earlier server did, but this time it gets a response, it knows there are some other servers so it A: requests the current servertable to see all online servers and their rows, and to B: get the current Data standpoint to update its own data on the server, after recievement it sends a addrequest to all servers so everyone adds it in their servertables, servers are always added in the schematic: 1 then 2 then 3… you get the point, so our server in this case would get their job id assigned with the row index 2, now lets say our root server looses all players Oh No! when the server shutdowns the script uses game:BindToClose to quickly message all servers to remove it from their server tables, then it goes offline, all other servers now look in their server tables, and remove it, the remaining servers will be pushed one down according to the removed servers row and their row, so if the removed server had the row 1 like in this case it was our root server, the other servers would push the server at row 2 to 1 and this would become the new root server, how does one know it is the root server? well every time a server is removed it actually checks if its in row 1 and also when the data is about to get refreshed, it also checks if its at row 1, the root server at row 1 is always the only one checking for the prices.

1 Like

but due to a lack of testing of this, there could be some bugs/problems, so if you find any please let me know, its very hard to test since i cant really run 2 servers that easiely

1 Like

This will be very helpful. Thanks for making this module.

Funnily enough, I spent the last few weeks working on something eerily similar (similar protocol and names, and it was actually the reason I wrote MessagingService Emulator: Solving Hacky Cross-Server Messaging Test Methods for testing).

Nice work open sourcing this; I’m sure this will be useful to anyone working this use-case, because it’s a major pain to do from scratch!!

wow thats great for testing thanks ill use it