Synchronous HTTP requests (SOLVED)

I am making successful http calls and understand the callbacks. I also do a lot of async javascript programming so my mind is stuck in that.

I’d like to make an “api” module that calls my rest service synchronously. I’d also like to return the data from the response. So something like this (psuedocode):

function get_user()
response = http.request(‘my_api_url’, ‘get’)
return json.parse(reponse.response)
end

Is it possible to do something like this, or are the callbacks absolutely required?

Lua coroutines are perfect for this. They will allow you to wait for an asynchronous operation to finish and then continue execution in a synchronous looking fashion. I would do like this:

local function http_request(url, method, headers, post_data, options)
	local co = coroutine.running()
	assert(co, "You must call this function from within a coroutine")
	http.request(url, method, function(self, id, response)
		coroutine.resume(co, response)
	end, headers, post_data, options)
	return coroutine.yield()
end

local function get_user()
	response = http_request('my_api_url', 'get')
	return json.parse(reponse.response)
end

local co = coroutine.create(function()
	local user = get_user()
	-- do stuff with user
end)
coroutine.resume(co)

The above snippet of code is not very useful on its own, but if you wish to do multiple http.request, one after the other, each depending on the result of the previous call, then you’d normally end up with a deeply nested callback structure. If running inside a coroutine and making use of the http_request method in the example you’d get nice synchronous looking code out of the what actually is several asynchronous calls.

Awesome, I’m new to Lua so things like this helps. I won’t be doing one request after another, usually just one at a time. Thank you.

1 Like

Hi, i was trying out this http.request method with coroutine but i am having problems with sending it with the POST method. When call comes to server it cant find the post parameters, can someone show me an example of how its supposed to look like when using POST with post_data?

post_data should be a string, but that’s about it. There is nothing else you should have to do. Maybe you need to send a content type header? It really depends on the code you have on the server. Could you tell us a bit more about the server?

Ok, the server .php file is working for a AndroidStudio project already, but for example it looks like this.


<?php $servername = "localhost"; $username = "XXXX"; $password = "XXXXX"; $dbname = $_POST["database"] ? $_POST["database"] : "gifts"; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); $conn->set_charset("utf8"); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } error_log("email:".$_POST["email"]); ... ------------------------------------------------------------------------------------------ and the error from server says : ==> /var/log/apache2/error.log <== [Wed Nov 23 07:38:17.166081 2016] [:error] [pid 19716] [client 82.99.54.98:27954] PHP Notice: Undefined index: database in /var/www/html/gifts/get.php on line 6 [Wed Nov 23 07:38:17.166484 2016] [:error] [pid 19716] [client 82.99.54.98:27954] PHP Notice: Undefined index: email in /var/www/html/gifts/get.php on line 17 [Wed Nov 23 07:38:17.166516 2016] [:error] [pid 19716] [client 82.99.54.98:27954] email: [Wed Nov 23 07:38:17.166530 2016] [:error] [pid 19716] [client 82.99.54.98:27954] PHP Notice: Undefined index: email in /var/www/html/gifts/get.php on line 19 So the post data should look like this for example: local post_data = "email=geidnert@gmail.com&database=test"

Hmm, ok, and are you setting the content-type header to “application/x-www-form-urlencoded” when you do your POST?

Like this:

local url = "http://www.foobar.com"
local method = "POST"
local headers = {
	["Content-Type"] = "application/x-www-form-urlencoded",
}
local post_data = "email=geidnert@gmail.com&database=test"
local options = {}

http.request(url, method, nil, headers, post_data, options)

Btw, Don’t you have to URL encode the ‘@’ sign?

Yay, that header fixed the issue, thanks a lot. But another came up that seems to be related to the amount of data that is being returned.

After i have called a .php API page from the server i am just printing the result in the console, and that works when fetching 1 user, but when the server returns x amount of users it does not print


local function request(host, page, parameters)
	response = http_request("http://"..host.."/gifts/".. page .. ".php", "POST", parameters)	
	return response.response
end

function ServerApi.getUsersInGroup(host, groupname)
	co = coroutine.create(function()		
		local usersJson = request(host, "getAllUsers", "groupname=" .. groupname)
		print("Users: " .. usersJson)
	end)
			
	coroutine.resume(co)	
end

but when i exit the application it prints out some of the results?

print() will only print the first couple of hundred characters of the string. I’m uncertain of the length, but if the string is long it will not print all of it. If it’s json then why don’t you decode it first (using json.decode) and then iterate over the table and print each value?

it doesnt seem to matter what i do with the returning result userJson, as soon as i try to use it in any way nothing more happens. even if i in the request() method try to json.decode(response.response) it will never end up in the starting coroutine. Touching the response will “hang” the system.

Didnt manage to get ZeroBrain to work for debugging this eather… i never get into those coroutine methods.

What do you mean? Are you trying to decode usersJson?

yes, for example.

I noticed that you don’t have a local keyword in front of co. If you do multiple simultaneous requests you’ll probably end up with an error.

Here’s a complete example of how to use http.request() with a coroutine to get the current stable version of Defold, including json.decode(). Put this in the init function of a script and run:

local function http_request(url, method, headers, post_data, options)
	local co = coroutine.running()
	assert(co, "You must call this function from within a coroutine")
	http.request(url, method, function(self, id, response)
		coroutine.resume(co, response)
	end, headers, post_data, options)
	return coroutine.yield()
end

local function get_defold_version(channel)
	local response = http_request("http://d.defold.com/" .. channel .. "/info.json", "GET")
	if response.status >= 200 or response.status < 300 then
		local info = json.decode(response.response)
		return info.version
	else
		return nil, "Unable to get Defold version"
	end
end

local co = coroutine.create(function()
	local version, err = get_defold_version("stable")
	if version then
		print("Current stable version of Defold is " .. version)
	else
		print(err)
	end
end)
local ok, err = coroutine.resume(co)
if not ok then
	print(err)
end
1 Like

looks good but my data does not work for some reason when its to much data returning. Could you try these 2 urls and post_data and se if you can get it to work?

less returning data:
http://solidparts.se/test/getAllUsers.php with post_data=“groupname=test”

more returning data:
http://solidparts.se/test/getAllUsers.php with post_data=“groupname=test2”

I have no problem reading and parsing your data, but it’s a bit oddly formatted with quite a few nested objects. This is the json from “groupname=test”:

{
  "users": [
    {
      "user": {
        "id": "28",
        "gcm_regid": "eWuPthLZKMk:APA91bENXnADkD4b9nQFYqKASlIGUNNiMwNN6HcQ4_ZRDlA02RrhTE08Rih3UsxTSFzW2JzZ13_QLJLhELhAEZOLgNfwbDo9eOyTkH8LAkAyuvTbdNtEjDeGCR_OSMoBsXVUU7j7-Nr5",
        "email": "test4@",
        "firstname": "Test4",
        "lastname": "Test4",
        "groupname": "test"
      }
    }
  ]
}

This will correspond to a Lua table with a field named users containing another table. Each entry into this inner table will have a single field named user containing the table with the actual user data…

Here’s the code I use to read and parse your data:

local function http_request(url, method, headers, post_data, options)
	local co = coroutine.running()
	assert(co, "You must call this function from within a coroutine")
	http.request(url, method, function(self, id, response)
		coroutine.resume(co, response)
	end, headers, post_data, options)
	return coroutine.yield()
end

local function get_users()
	local response = http_request("http://solidparts.se/test/getAllUsers.php", "POST", { ["Content-Type"] = "application/x-www-form-urlencoded" }, "groupname=test2")
	if response.status >= 200 or response.status < 300 then
		return json.decode(response.response)
	else
		return nil, "Error getting users"
	end
end

local co = coroutine.create(function()
	local users, err = get_users()
	if users then
		for _,v in pairs(users.users) do
			pprint(v.user)
			print(v.user and v.user.firstname or "")
		end
	else
		print(err)
	end
end)
local ok, err = coroutine.resume(co)
if not ok then
	print(err)
end

And it successfully reads and parses the “groupname=test2” values.

1 Like

I do not really know whats the difference from before, but i rewrote the code, and now i get the result back as well. Thanks for the quick answers.

1 Like