From 37f7af4b9f1250e3c3439df03d43cf291a4d6f37 Mon Sep 17 00:00:00 2001
From: Diego Nehab <diego@tecgraf.puc-rio.br>
Date: Mon, 20 Jun 2005 04:51:55 +0000
Subject: 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.
 </p>
 
 <ul>
-<li> Improved: <tt>tcp{client}:send(data, i)</tt> now returns <tt>(i+sent-1)</tt>.  This is great for non-blocking I/O, but might break some code;
-<li> Improved: HTTP, SMTP, and FTP functions accept a new field
-<tt>connect</tt> that can be used to replace the function invoked to 
-create and connect the sockets used internally;
-<li> Fixed: <tt>url.absolute()</tt> was not working when <tt>base_url</tt> was 
-already parsed;
-<li> Fixed: <tt>http.request()</tt> was redirecting even when the location 
-header was empty (well, it shouldn't be empty);
-<li> Fixed: <tt>tcp{client}:shutdown()</tt> was checking for group instead of class;
-<li> Fixed: <tt>socket.try()</tt> can't be used in place of <tt>assert()</tt>. The manual and examples don't do it anymore; 
-<li> Improved: Get rid of <tt>require("base")</tt> kludge in <tt>package.loaded</tt>;
-<li> Fixedd: Parts of the manual referred to <tt>require("http")</tt> instead of
+<li> Improved: <tt>tcp:send(data, i, j)</tt> to return <tt>(i+sent-1)</tt>.  This is great for non-blocking I/O, but might break some code;
+<li> Improved: HTTP, SMTP, and FTP functions to accept a new field
+<tt>create</tt> that overrides the function used to create socket objects;
+<li> Fixed: <tt>url.absolute()</tt> to work when <tt>base_url</tt> is in
+parsed form;
+<li> Fixed: <tt>http.request()</tt> not to redirect when the location 
+header is empty (naughty servers...);
+<li> Fixed: <tt>tcp{client}:shutdown()</tt> to check for class instead of
+group;
+<li> Fixed: The manual to stop using <tt>socket.try()</tt> in place of
+<tt>assert()</tt>, since it can't; 
+<li> Improved: Got rid of <tt>package.loaded.base = _G</tt> kludge;
+<li> Fixed: Parts of the manual referred to <tt>require("http")</tt> instead of
 <tt>require("socket.http")</tt>;
-<li> Improved: Changed 'l' prefix in C libraries to 'c' to avoid clash with LHF
-libraries;
-<li> Improved: Using bundles in Mac OS X; 
-<li> Fixed: <tt>luasocket.h</tt> was exporting <tt>luaopen_socket</tt> 
-instead of <tt>luaopen_csocket</tt>;
-<li> Fixed: <tt>udp:setpeername()</tt> only worked for 
-<tt>udp{unconnected}</tt>. Now you can "disconnect" an <tt>UDP</tt> socket;
-<li> Fixed: bug in http.lua that caused some requests to fail (Florian
-Berger);
-<li> Fixed: bug in <tt>select.c</tt> that prevented sockets with descriptor 0 from working (Renato Maia);
-<li> Fixed: "bug" that caused <tt>dns.toip</tt> to crash under uLinux;
-<li> Fixed: "bug" that caused a crash in <tt>gethostbyname</tt> under VMS
+<li> Improved: Socket and MIME binaries are called 'core' each inside their
+directory (ex. "socket/core.dll"). The 'l' prefix was just a bad idea;
+<li> Improved: Using bundles in Mac OS X, instead of dylibs; 
+<li> Fixed: <tt>luasocket.h</tt> to export <tt>luaopen_socketcore</tt>;
+<li> Fixed: <tt>udp:setpeername()</tt> so you can "disconnect" an 
+<tt>UDP</tt> socket;
+<li> Fixed: A weird bug in HTTP support that caused some requests to 
+fail (Florian Berger);
+<li> Fixed: Bug in <tt>socket.select()</tt> that caused sockets 
+with descriptor 0 to be ignored (Renato Maia);
+<li> Fixed: "Bug" that caused <tt>dns.toip()</tt> to crash under uLinux
+(William Trenker);
+<li> Fixed: "Bug" that caused <tt>gethostbyname</tt> to crash under VMS
 (Renato Maia);
+<li> Fixed: <tt>tcp:send("")</tt> to return 0 bytes sent (Alexander Marinov);
 <li> Improved: <tt>socket.DEBUG</tt> and <tt>socket.VERSION</tt> became <tt>socket._DEBUGs</tt> and <tt>socket._VERSION</tt> for uniformity with other libraries.
 </ul>
 
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 {<url>}")
+    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)
-- 
cgit v1.2.3-55-g6feb