Fix. `multi.setopt` supports socket callback. Change. `socket_acttion` without args means timeout action. Change. cURLv3 multi supports options in callback. Add. `multi-uv` example.
163 lines
3.8 KiB
Lua
163 lines
3.8 KiB
Lua
local curl = require "cURL"
|
|
local uv = require "lluv"
|
|
|
|
local fprintf = function(f, ...) f:write((string.format(...))) end
|
|
local printf = function(...) fprintf(io.stdout, ...) end
|
|
|
|
local stderr = io.stderr
|
|
|
|
local timeout, curl_handle
|
|
|
|
local ACTION_NAMES = {
|
|
[curl.POLL_IN ] = "POLL_IN";
|
|
[curl.POLL_INOUT ] = "POLL_INOUT";
|
|
[curl.POLL_OUT ] = "POLL_OUT";
|
|
[curl.POLL_NONE ] = "POLL_NONE";
|
|
[curl.POLL_REMOVE ] = "POLL_REMOVE";
|
|
}
|
|
local EVENT_NAMES = {
|
|
[ uv.READABLE ] = "READABLE";
|
|
[ uv.WRITABLE ] = "WRITABLE";
|
|
[ uv.READABLE + uv.WRITABLE ] = "READABLE + WRITABLE";
|
|
}
|
|
local FLAGS = {
|
|
[ uv.READABLE ] = curl.CSELECT_IN;
|
|
[ uv.WRITABLE ] = curl.CSELECT_OUT;
|
|
[ uv.READABLE + uv.WRITABLE ] = curl.CSELECT_IN + curl.CSELECT_OUT;
|
|
|
|
}
|
|
|
|
local trace = function() end or print
|
|
|
|
local FILES, CONTEXT = {}, {}
|
|
|
|
function create_curl_context(sockfd)
|
|
local context = {
|
|
sockfd = sockfd;
|
|
poll_handle = uv.poll_socket(sockfd);
|
|
}
|
|
context.poll_handle.data = context
|
|
|
|
return context
|
|
end
|
|
|
|
function destroy_curl_context(context)
|
|
context.poll_handle:close()
|
|
end
|
|
|
|
function add_download(url, num)
|
|
local filename = tostring(num) .. ".download"
|
|
local file = io.open(filename, "w")
|
|
if not file then
|
|
fprintf(stderr, "Error opening %s\n", filename)
|
|
return
|
|
end
|
|
|
|
local handle = curl.easy{
|
|
url = url;
|
|
writefunction = file;
|
|
}
|
|
|
|
FILES[handle] = file
|
|
|
|
curl_handle:add_handle(handle)
|
|
fprintf(stderr, "Added download %s -> %s\n", url, filename);
|
|
end
|
|
|
|
function check_multi_info()
|
|
while true do
|
|
local easy, ok, err = curl_handle:info_read()
|
|
if not easy then curl_handle:close() error(err) end
|
|
if easy == 0 then break end
|
|
|
|
local context = CONTEXT[e]
|
|
if context then destroy_curl_context(context) end
|
|
local file = FILES[easy]
|
|
if file then FILES[easy] = nil, file:close() end
|
|
local done_url = easy:getinfo_effective_url()
|
|
easy:close()
|
|
if ok then
|
|
printf("%s DONE\n", done_url);
|
|
elseif data == "error" then
|
|
printf("%s ERROR - %s\n", done_url, tostring(err));
|
|
end
|
|
end
|
|
end
|
|
|
|
function curl_perform(handle, err, events)
|
|
-- calls by libuv --
|
|
trace("UV::POLL", handle, err, EVENT_NAMES[events] or events)
|
|
|
|
local flags = assert(FLAGS[events], ("unknown event:" .. events))
|
|
|
|
context = handle.data
|
|
|
|
curl_handle:socket_action(context.sockfd, flags)
|
|
|
|
check_multi_info()
|
|
end
|
|
|
|
function on_timeout(timer)
|
|
-- calls by libuv --
|
|
trace("UV::TIMEOUT", timer)
|
|
|
|
local running_handles, err = curl_handle:socket_action()
|
|
|
|
check_multi_info()
|
|
end
|
|
|
|
function start_timeout(timeout_ms)
|
|
-- calls by curl --
|
|
trace("CURL::TIMEOUT", timeout_ms)
|
|
|
|
-- 0 means directly call socket_action, but we'll do it in a bit
|
|
if timeout_ms <= 0 then timeout_ms = 1 end
|
|
|
|
timeout:stop():start(timeout_ms, 0, on_timeout)
|
|
end
|
|
|
|
local handle_socket = function(...)
|
|
local ok, err = pcall(handle_socket_impl, ...)
|
|
if not ok then uv.defer(function() error(err) end) end
|
|
end
|
|
|
|
function handle_socket_impl(easy, s, action)
|
|
-- calls by curl --
|
|
|
|
trace("CURL::SOCKET", easy, s, ACTION_NAMES[action] or action)
|
|
|
|
local curl_context = CONTEXT[easy] or create_curl_context(s)
|
|
CONTEXT[easy] = curl_context
|
|
|
|
assert(curl_context.sockfd == s)
|
|
|
|
if action == curl.POLL_IN then
|
|
curl_context.poll_handle:start(uv.READABLE, curl_perform)
|
|
elseif action == curl.POLL_OUT then
|
|
curl_context.poll_handle:start(uv.WRITABLE, curl_perform)
|
|
elseif action == curl.POLL_REMOVE then
|
|
CONTEXT[easy] = nil
|
|
destroy_curl_context(curl_context)
|
|
end
|
|
end
|
|
|
|
timeout = uv.timer()
|
|
|
|
curl_handle = curl.multi{
|
|
socketfunction = handle_socket;
|
|
timerfunction = start_timeout;
|
|
}
|
|
|
|
curl_handle = curl.multi{
|
|
socketfunction = handle_socket;
|
|
timerfunction = start_timeout;
|
|
}
|
|
|
|
for i = 1, math.huge do
|
|
local url = arg[i]
|
|
if not url then break end
|
|
add_download(url, i)
|
|
end
|
|
|
|
uv.run(loop, UV_RUN_DEFAULT)
|