From 37f7af4b9f1250e3c3439df03d43cf291a4d6f37 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 20 Jun 2005 04:51:55 +0000 Subject: [PATCH] Added check-links-nb.lua that check links in a non-blocking way. --- FIX | 1 + config | 47 ++++++++ doc/index.html | 51 ++++---- etc/check-links-nb.lua | 262 +++++++++++++++++++++++++++++++++++++++++ samples/forward.lua | 2 +- src/buffer.c | 3 +- test/httptest.lua | 2 +- 7 files changed, 341 insertions(+), 27 deletions(-) create mode 100644 config create mode 100644 etc/check-links-nb.lua diff --git a/FIX b/FIX index 0ec76a1..3d0b3de 100644 --- a/FIX +++ b/FIX @@ -14,3 +14,4 @@ fixed a bug in select.c that prevented sockets with descriptor 0 from working (R fixed a "bug" that caused dns.toip to crash under uLinux fixed a "bug" that caused a crash in gethostbyname under VMS DEBUG and VERSION became _DEBUG and _VERSION +send returns the right value if input is "". Alexander Marinov diff --git a/config b/config new file mode 100644 index 0000000..dcc3955 --- /dev/null +++ b/config @@ -0,0 +1,47 @@ +#------ +# LuaSocket makefile configuration +# + +#------ +# Output file names +# +EXT=so +SOCKET_V=2.0.0 +MIME_V=1.0.0 +SOCKET_SO=socket-core.$(EXT).$(SOCKET_V) +MIME_SO=mime-core.$(EXT).$(MIME_V) +UNIX_SO=unix.$(EXT) + +#------ +# Lua includes and libraries +# +LUAINC= +LUALIB= + +#------ +# Compat-5.1 directory +# +COMPAT=compat-5.1r3 + +#------ +# Top of your Lua installation +# Relative paths will be inside src tree +# +INSTALL_TOP=/usr/local/share/lua/5.0 + +INSTALL_DATA=cp +INSTALL_EXEC=cp +INSTALL_LINK=ln + +#------ +# Compiler and linker settings +# +CC=gcc +DEF=-DLUASOCKET_DEBUG -DUNIX_HAS_SUN_LEN +CFLAGS= $(LUAINC) -I$(COMPAT) $(DEF) -pedantic -Wall -O2 +LDFLAGS=-bundle -undefined dynamic_lookup +LD=export MACOSX_DEPLOYMENT_TARGET="10.3"; gcc + +#------ +# End of makefile configuration +# diff --git a/doc/index.html b/doc/index.html index ee97e02..933fa5f 100644 --- a/doc/index.html +++ b/doc/index.html @@ -165,32 +165,35 @@ support.

diff --git a/etc/check-links-nb.lua b/etc/check-links-nb.lua new file mode 100644 index 0000000..7e8df1b --- /dev/null +++ b/etc/check-links-nb.lua @@ -0,0 +1,262 @@ +----------------------------------------------------------------------------- +-- Little program that checks links in HTML files, using coroutines and +-- non-blocking I/O. Thus, faster than simpler version of same program +-- LuaSocket sample files +-- Author: Diego Nehab +-- RCS ID: $$ +----------------------------------------------------------------------------- +local socket = require("socket") + +TIMEOUT = 10 + +-- we need to yield across calls to protect, so we can't use pcall +-- we borrow and simplify code from coxpcall to reimplement socket.protect +-- before loading http +function socket.protect(f) + return function(...) + local co = coroutine.create(f) + while true do + local results = {coroutine.resume(co, unpack(arg))} + local status = results[1] + table.remove(results, 1) + if not status then + return nil, results[1][1] + end + if coroutine.status(co) == "suspended" then + arg = {coroutine.yield(unpack(results))} + else + return unpack(results) + end + end + end +end + +local http = require("socket.http") +local url = require("socket.url") + +-- creates a new set data structure +function newset() + local reverse = {} + local set = {} + return setmetatable(set, {__index = { + insert = function(set, value) + if not reverse[value] then + table.insert(set, value) + reverse[value] = table.getn(set) + end + end, + remove = function(set, value) + local index = reverse[value] + if index then + reverse[value] = nil + local top = table.remove(set) + if top ~= value then + reverse[top] = index + set[index] = top + end + end + end + }}) +end + +local context = {} +local sending = newset() +local receiving = newset() +local nthreads = 0 + +-- socket.tcp() replacement for non-blocking I/O +-- implements enough functionality to be used with http.request +-- in Lua 5.1, we have coroutine.running to simplify things... +function newcreate(thread) + return function() + -- try to create underlying socket + local tcp, error = socket.tcp() + if not tcp then return nil, error end + -- put it in non-blocking mode right away + tcp:settimeout(0) + local trap = { + -- we ignore settimeout to preserve our 0 timeout + settimeout = function(self, mode, value) + return 1 + end, + -- send in non-blocking mode and yield on timeout + send = function(self, data, first, last) + first = (first or 1) - 1 + local result, error + while true do + result, error, first = tcp:send(data, first+1, last) + if error == "timeout" then + -- tell dispatcher we want to keep sending + sending:insert(tcp) + -- mark time we started waiting + context[tcp].last = socket.gettime() + -- return control to dispatcher + if coroutine.yield() == "timeout" then + return nil, "timeout" + end + else return result, error, first end + end + end, + -- receive in non-blocking mode and yield on timeout + receive = function(self, pattern) + local error, partial = "timeout", "" + local value + while true do + value, error, partial = tcp:receive(pattern, partial) + if error == "timeout" then + -- tell dispatcher we want to keep receiving + receiving:insert(tcp) + -- mark time we started waiting + context[tcp].last = socket.gettime() + -- return control to dispatcher + if coroutine.yield() == "timeout" then + return nil, "timeout" + end + else return value, error, partial end + end + end, + -- connect in non-blocking mode and yield on timeout + connect = function(self, host, port) + local result, error = tcp:connect(host, port) + if error == "timeout" then + -- tell dispatcher we will be able to write uppon connection + sending:insert(tcp) + -- mark time we started waiting + context[tcp].last = socket.gettime() + -- return control to dispatcher + if coroutine.yield() == "timeout" then + return nil, "timeout" + end + -- when we come back, check if connection was successful + result, error = tcp:connect(host, port) + if result or error == "already connected" then return 1 + else return nil, "non-blocking connect failed" end + else return result, error end + end, + close = function(self) + context[tcp] = nil + return tcp:close() + end + } + -- add newly created socket to context + context[tcp] = { + thread = thread, + trap = trap + } + return trap + end +end + +-- get the status of a URL, non-blocking +function getstatus(from, link) + local parsed = url.parse(link, {scheme = "file"}) + if parsed.scheme == "http" then + local thread = coroutine.create(function(thread, from, link) + local r, c, h, s = http.request{ + method = "HEAD", + url = link, + create = newcreate(thread) + } + if c == 200 then io.write('\t', link, '\n') + else io.write('\t', link, ': ', c, '\n') end + nthreads = nthreads - 1 + end) + nthreads = nthreads + 1 + assert(coroutine.resume(thread, thread, from, link)) + end +end + +-- dispatch all threads until we are done +function dispatch() + while nthreads > 0 do + -- check which sockets are interesting and act on them + local readable, writable = socket.select(receiving, sending, 1) + -- for all readable connections, resume their threads + for _, who in ipairs(readable) do + if context[who] then + receiving:remove(who) + assert(coroutine.resume(context[who].thread)) + end + end + -- for all writable connections, do the same + for _, who in ipairs(writable) do + if context[who] then + sending:remove(who) + assert(coroutine.resume(context[who].thread)) + end + end + -- politely ask replacement I/O functions in idle threads to + -- return reporting a timeout + local now = socket.gettime() + for who, data in pairs(context) do + if data.last and now - data.last > TIMEOUT then + assert(coroutine.resume(context[who].thread, "timeout")) + end + end + end +end + +function readfile(path) + path = url.unescape(path) + local file, error = io.open(path, "r") + if file then + local body = file:read("*a") + file:close() + return body + else return nil, error end +end + +function retrieve(u) + local parsed = url.parse(u, { scheme = "file" }) + local body, headers, code, error + local base = u + if parsed.scheme == "http" then + body, code, headers = http.request(u) + if code == 200 then + base = base or headers.location + end + if not body then + error = code + end + elseif parsed.scheme == "file" then + body, error = readfile(parsed.path) + else error = string.format("unhandled scheme '%s'", parsed.scheme) end + return base, body, error +end + +function getlinks(body, base) + -- get rid of comments + body = string.gsub(body, "%<%!%-%-.-%-%-%>", "") + local links = {} + -- extract links + body = string.gsub(body, '[Hh][Rr][Ee][Ff]%s*=%s*"([^"]*)"', function(href) + table.insert(links, url.absolute(base, href)) + end) + body = string.gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*'([^']*)'", function(href) + table.insert(links, url.absolute(base, href)) + end) + string.gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*(.-)>", function(href) + table.insert(links, url.absolute(base, href)) + end) + return links +end + +function checklinks(from) + local base, body, error = retrieve(from) + if not body then print(error) return end + local links = getlinks(body, base) + for _, link in ipairs(links) do + getstatus(from, link) + end +end + +arg = arg or {} +if table.getn(arg) < 1 then + print("Usage:\n luasocket check-links.lua {}") + exit() +end +for _, a in ipairs(arg) do + print("Checking ", a) + checklinks(url.absolute("file:", a)) +end +dispatch() diff --git a/samples/forward.lua b/samples/forward.lua index a53ab5d..548a753 100644 --- a/samples/forward.lua +++ b/samples/forward.lua @@ -2,7 +2,7 @@ local socket = require"socket" -- creates a new set data structure -function newset(a) +function newset() local reverse = {} local set = {} return setmetatable(set, {__index = { diff --git a/src/buffer.c b/src/buffer.c index 62211d8..1188fda 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -122,7 +122,8 @@ int buf_meth_receive(lua_State *L, p_buf buf) { if (p[0] == '*' && p[1] == 'l') err = recvline(buf, &b); else if (p[0] == '*' && p[1] == 'a') err = recvall(buf, &b); else luaL_argcheck(L, 0, 2, "invalid receive pattern"); - /* get a fixed number of bytes */ + /* get a fixed number of bytes (minus what was already partially + * received) */ } else err = recvraw(buf, (size_t) lua_tonumber(L, 2)-size, &b); /* check if there was an error */ if (err != IO_DONE) { diff --git a/test/httptest.lua b/test/httptest.lua index 86f14a4..3816b54 100644 --- a/test/httptest.lua +++ b/test/httptest.lua @@ -26,7 +26,7 @@ host = host or "localhost" -- "diego.student.princeton.edu" proxy = proxy or "http://localhost:3128" prefix = prefix or "/luasocket-test" cgiprefix = cgiprefix or "/luasocket-test-cgi" -index_file = "test/index.html" +index_file = "index.html" -- read index with CRLF convention index = readfile(index_file)