Source

luaeio / eio.lua

Full commit
local libeio = require("libeio")

local http = {}
http.__index = http

--
-- Create new HTTP server
-- 
function libeio.http_server(port, handlers)
	local f = libeio.listen(port)
	if f < 0 then error("listen(): "..f) end

	local s = {}
	s.fd = f
	s.fds = {}
	s.crs = {}
	s.handlers = handlers
	setmetatable(s, http)
	return s
end

--
-- Dispatch HTTP clients in an endless loop
--
function http:serve()
	while true do 
		local n
		local rfds, wfds = {}, {}
		-- add listening socket
		table.insert(self.fds, self.fd, {fd=self.fd, r=true})

		-- add other file descriptors to rfds/wfds
		for _, fd in pairs(self.fds) do
			if fd.r then 
				table.insert(rfds, fd.fd)
			end
			if fd.w then
				table.insert(wfds, fd.fd)
			end
		end

		-- wait for the events
		n, rfds, wfds = libeio.select(rfds, wfds)
		if n < 0 then error("select(): "..n) end

		-- special case: read on listening socket (accept event)
		if rfds[self.fd] ~= nil then
			self:accept()
		end

		-- dispatch the rest of the events
		for fd, cr in pairs(self.crs) do
			self.fds[fd].can_read = (rfds[fd] ~= nil)
			self.fds[fd].can_write = (wfds[fd] ~= nil)
			coroutine.resume(cr)
			if coroutine.status(cr) == 'dead' then
				libeio.close(fd)
				self.crs[fd] = nil
				self.fds[fd] = nil
			end
		end
	end
end

--
-- Process new HTTP connection
--
function http:accept()
	local fd = libeio.accept(self.fd)
	print('accept')

	self.fds[fd] = {fd = fd}

	local read_header = function()
		local request = ""

		while request:find("\r\n\r\n") == nil do
			local n, buf = self:read(fd, 1)
			if n > 0 then 
				request = request..tostring(buf)
			elseif n == 0 then
				return -- connection was closed
			end
		end

		local r = {
			req = request,
			read = function(self,n) return self:read(fd, n) end,
			write = function(self,s) return libeio.write(fd, s) end
		}
		print(request)

		local handler = function(r) r:write("404 Not Found\r\n") end
		for uri, h in pairs(self.handlers) do
			if uri == "/" then
				handler = h
				break
			end
		end

		handler(r)
	end

	self.crs[fd] = coroutine.create(read_header)
end

--
-- Read from HTTP client socket in co-routine
--
function http:read(fd, size)
	if self.fds[fd].can_read then
		self.fds[fd].can_read = false
		self.fds[fd].r = false
		return libeio.read(fd, size)
	else
		self.fds[fd].r = true
		coroutine.yield()
		return -1, nil
	end
end

return libeio