Last active
May 1, 2019 13:28
-
-
Save leafo/6788652 to your computer and use it in GitHub Desktop.
Making nonblocking http requests with lua-ev and LuaSocket
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
bit = require "bit" | |
ev = require "ev" | |
ltn12 = require "ltn12" | |
socket = require "socket" | |
-- make protect and newtry coroutine friendly | |
socket.protect = (fn) -> fn | |
socket.newtry = (finalizer) -> | |
(...) -> | |
unless ... | |
finalizer select 2, ... | |
... | |
http = require "socket.http" | |
-- creates a wrapped socket that yields on timeout | |
make_non_blocking = do | |
local nonblocking_mt | |
nonblocking_mt = { | |
connect: (...) => | |
status, err = @sock\connect ... | |
if err == "timeout" | |
coroutine.yield "both" | |
1 | |
else | |
status, err | |
send: (...) => | |
while true | |
byte, err, partial = @sock\send ... | |
if err == "timeout" | |
if partial and partial > 0 | |
return partial | |
else | |
coroutine.yield "write" | |
else | |
return byte, err, partial | |
receive: (...) => | |
while true | |
msg, err, partial = @sock\receive ... | |
if err == "timeout" | |
if partial and #partial > 0 | |
return partial | |
else | |
coroutine.yield "read" | |
else | |
return msg, err, partial | |
settimeout: => | |
true -- can't change the timeout | |
__index: (name) => | |
-- try mt | |
if val = nonblocking_mt[name] | |
return val | |
-- cache bound method | |
@[name] = (...) => | |
@sock[name] @sock, ... | |
@[name] | |
} | |
-> | |
sock = socket.tcp! | |
sock\settimeout 0 | |
setmetatable { :sock }, nonblocking_mt | |
-- this function does the request by creating a non-blocking socket in a | |
-- coroutine and resumes it when the file descriptor is ready to be read/written | |
request = (url, finished_fn, loop=ev.Loop.default) -> | |
local push_callback | |
sock = make_non_blocking! | |
thread = coroutine.create -> | |
t = {} | |
ok, status, headers = http.request { | |
url: url | |
sink: ltn12.sink.table t | |
create: -> sock | |
} | |
finished_fn status, table.concat(t), headers | |
"done" | |
ev_callback = (loop, io, io_status) -> | |
status, msg = coroutine.resume thread | |
unless status | |
io\stop loop if io | |
print "Error", msg | |
return | |
push_callback msg | |
local ev_io, last_bitmask | |
push_callback = (needed) -> | |
bitmask = switch needed | |
when "both" | |
bit.bor ev.READ, ev.WRITE | |
when "read" | |
ev.READ | |
when "write" | |
ev.WRITE | |
when "done" | |
if ev_io | |
ev_io\stop loop | |
return | |
else | |
error "unknown message #{needed}" | |
return if last_bitmask == bitmask | |
last_bitmask = bitmask | |
if ev_io | |
ev_io\stop loop | |
ev_io = ev.IO.new ev_callback, sock\getfd!, bitmask | |
ev_io\start loop | |
ev_callback! -- initiate request | |
-- and here we try it out | |
request "http://leafo.net", (status, body, headers) -> | |
print "ok", status, #body | |
loop = ev.Loop.default | |
loop\loop! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
hey leafo for usage with LOVE would you recommend using coroutines or threads for http?