Lua http socket evaluation - lua

I use lua 5.1 and the luaSocket 2.0.2-4 to retrieve a page from a web server. I first check if the server is responding and then assign the web server response to lua variables.
local mysocket = require("socket.http")
if mysocket.request(URL) == nil then
print('The server is unreachable on:\n'..URL)
return
end
local response, httpCode, header = mysocket.request(URL)
Everything works as expected but the request is executed two times. I wonder if I could do Something like (which doesn't work obviously):
local mysocket = require("socket.http")
if (local response, httpCode, header = mysocket.request(URL)) == nil then
print('The server is unreachable on:\n'..URL)
return
end

Yes, something like this :
local mysocket = require("socket.http")
local response, httpCode, header = mysocket.request(URL)
if response == nil then
print('The server is unreachable on:\n'..URL)
return
end
-- here you do your stuff that's supposed to happen when request worked
Request will be sent only once, and function will exit if it failed.

Even better, when request fails, the second return is the reason:
In case of failure, the function returns nil followed by an error message.
(From the documentation for http.request)
So you can print the problem straight from the socket's mouth:
local http = require("socket.http")
local response, httpCode, header = http.request(URL)
if response == nil then
-- the httpCode variable contains the error message instead
print(httpCode)
return
end
-- here you do your stuff that's supposed to happen when request worked

Related

how to send https request from lua in haproxy before routing request?

I used haproxy Socket class as outlined here https://www.haproxy.com/blog/5-ways-to-extend-haproxy-with-lua/#actions to make http request to external service from lua code (See code below).
How can I make an https request to the service?
Is it possible to specify a domain name instead of IP address of the service to connect to?
Any help is appreciated.
local function http_request(txn, data)
local addr = <external-IP>
local port = 80
-- Set up a request to the service
local hdrs = {
[1] = string.format('host: %s:%s', addr, port),
[2] = 'accept: */*',
[3] = 'connection: close'
}
local req = {
[1] = string.format('GET %s HTTP/1.1', data.path),
[2] = table.concat(hdrs, '\r\n'),
[3] = '\r\n'
}
req = table.concat(req, '\r\n')
-- Use core.tcp to get an instance of the Socket class
local socket = core.tcp()
socket:settimeout(data.timeout)
-- Connect to the service and send the request
if socket:connect(addr, port) then
if socket:send(req) then
-- Skip response headers
while true do
local line, _ = socket:receive('*l')
if not line then break end
if line == '' then break end
end
-- Get response body, if any
local content = socket:receive('*a')
return content
else
core.Alert('Could not connect to server (send)')
end
socket:close()
else
core.Alert('Could not connect to server (connect)')
end
end
Recently while working on a problem I figured out that we cannot pass domain name. I was using http.lua lib. This http.lua lib uses Socket class as you are doing in your code.
Also after searching a lot I was unable to find a dns resolver lib. One was there something related to nginx lua, but it requires installing lots of different lua libs, so I skipped it.
The work around I did is, created my own dns resolver service http://127.0.0.1:53535 in HAProxy as below
listen lua_dns
bind 127.0.0.1:53535
http-request do-resolve(txn.dstip,mydns,ipv4) hdr(ResolveHost),lower
http-request return status 200 content-type text/plain lf-string OK hdr ResolvedIp "%[var(txn.dstip)]"
To this service I pass the domain name in request header ResolveHost and get the IP in response header ResolvedIp.
Now the lua functions to parse the domain from URL and call dns resolver service is as below
local function parse_domain(url)
local schema, host, _ = url:match("^(.*)://(.-)[?/](.*)$")
if not schema then
-- maybe path (request uri) is missing
schema, host = url:match("^(.*)://(.-)$")
if not schema then
core.Info("ERROR :: Could not parse URL: "..url)
return nil
end
end
return host
end
local function resolve_domain(domain)
local d = parse_domain(domain)
local r, msg = http.get{ url = "http://127.0.0.1:53535", headers={ResolveHost=d} }
if r == nil then
core.Info("ERROR: "..msg..". While resolving doamin: "..d)
return msg
end
return r.headers['resolvedip']
end
Now replace the resolved IP with the domain name in the URL using gsub()
url = string:gsub(domain_name, resolved_ip)
and then call your API using http.lua
local res, msg = http.get{ url=url, headers=headers }
Here the http.lua lib will handle HTTP and HTTPS urls.

Occasionally incomplete response from HTTP request

I'm downloading quite big JSON data with luasocket (request), but sometimes, data are incomplete. There is no pattern, if X runs, I have Y fails and Z successful downloads. My downloading code looks like this:
local response = {}
local one, code, headers, status = https.request {
url = url,
sink = ltn12.sink.table(response)
}
And btw, everytime it fails on character 3615. Where is the problem and how to fix it? Is it issue in my code, luasocket, or server?

Lua socket example, trying different sites

I am using this example. It prints the text based on this site http://www.google.com/robots.txt
local socket = require("socket")
client = socket.connect("google.com", 80)
client:send("GET /robots.txt HTTP/1.0\r\n\r\n")
while true do
s, status, partial = client:receive(1024)
print(s or partial)
if status == "closed" then
break
end
end
client:close()
I use:
local socket = require("socket")
client = socket.connect("www.lua.org", 80)
client:send("GET /pil/9.4.html HTTP/1.0\r\n\r\n")
while true do
s, status, partial = client:receive(1024)
print(s or partial)
if status == "closed" then
break
end
end
client:close()
But, as above, I try with this link, http://www.lua.org/pil/9.4.html and it doesn't work, saying "HTTP/1.0 302 Moved temporarily". Did same on many other sites, got similar results. Why is that so? Thanks a lot
You're receiving a redirect (HTTP 302 code) which should be handled by issuing a new request to the specified Location.
Rather than using raw sockets, you could use the high level HTTP module from the socket library, which provides a request method able to authomatically follow redirects.

Send Get to website with lua

i'm having a trouble with lua.
I need send a request to a website by GET and get the response from website.
Atm all i have is this:
local LuaSocket = require("socket")
client = LuaSocket.connect("example.com", 80)
client:send("GET /login.php?login=admin&pass=admin HTTP/1.0\r\n\r\n")
while true do
s, status, partial = client:receive('*a')
print(s or partial)
if status == "closed" then
break
end
end
client:close()
What should i do to get the response from server ?
I want to send some info to this website and get the result of page.
Any ideas ?
This probably won't work as *a will be reading until the connection is closed, but in this case the client doesn't know how much to read. What you need to do is to read line-by-line and parse the headers to find Content-Length and then after you see two line ends you read the specified number of bytes (as set in Content-Length).
Instead of doing it all yourself (reading and parsing headers, handling redirects, 100 continue, and all that), socket.http will take care of all that complexity for you. Try something like this:
local http = require("socket.http")
local body, code, headers, status = http.request("https://www.google.com")
print(code, status, #body)
I solved the problem passing the header
local LuaSocket = require("socket")
client = LuaSocket.connect("example.com", 80)
client:send("GET /login.php?login=admin&pass=admin HTTP/1.0\r\nHost: example.com\r\n\r\n")
while true do
s, status, partial = client:receive('*a')
print(s or partial)
if status == "closed" then
break
end
end
client:close()

Using bind() to create a server with Lua Socket

Working with LuaSocket, this code works:
local socket = require'socket'
local server = socket.bind('*',51423)
local client = server:accept()
but this code fails:
local socket = require 'socket'
local server = socket.tcp()
server:bind('*',51423)
local client = server:accept()
--> lua: /tmp/server.lua:4: calling 'accept' on bad self (tcp{server} expected, got userdata)
Yet the documentation for TCP bind implies that the latter should work, stating:
"Note: The function socket.bind is available and is a shortcut for the creation of server sockets."
How can I convert a generic "master" object into a server?
The motivation for this is the desire to add a timeout on the bind operation itself:
local socket = require'socket'
local server = socket.tcp()
server:settimeout(2/1000) -- Only wait 2ms when attempting to bind
server:bind('*',51423)
The answer is at the top of the same documentation page (oops):
"A master object can be transformed into a server … with the method listen (after a call to bind)"
It would seem that s = socket.bind(…) is actually equivalent to:
s = socket.tcp()
s:bind(…)
s:listen(32)
I'm not sure why they are split into two functions, but modifying the code to add listen() causes it to work:
local socket = require 'socket'
local server = socket.tcp()
server:bind('*',51423)
server:listen(32)
local client = server:accept()

Resources