Skip to content
Browse files

Move modules out into their own modules

  • Loading branch information...
1 parent 294f847 commit e17de4e0f46547983aa664b42630e1482b0291ef @creationix committed
View
15 .gitmodules
@@ -0,0 +1,15 @@
+[submodule "modules/web"]
+ path = modules/web
+ url = https://github.com/luvit/web.git
+[submodule "modules/web-static"]
+ path = modules/web-static
+ url = https://github.com/luvit/web-static.git
+[submodule "modules/web-log"]
+ path = modules/web-log
+ url = https://github.com/luvit/web-log.git
+[submodule "modules/web-autoheaders"]
+ path = modules/web-autoheaders
+ url = https://github.com/luvit/web-autoheaders.git
+[submodule "modules/continuable"]
+ path = modules/continuable
+ url = https://github.com/luvit/continuable.git
1 modules/continuable
@@ -0,0 +1 @@
+Subproject commit b4e37268864bbe83f528f5e388b734f362fdf8e6
View
266 modules/continuable/continuable.lua
@@ -1,266 +0,0 @@
-local native = require('uv_native')
-local Object = require('core').Object
-
-local function noop() end
-local uv = {}
-
-local Queue = Object:extend()
-uv.Queue = Queue
-
-function Queue:initialize()
- self.first = 1
- self.last = 0
- self.length = 0
-end
-
-function Queue:push(item)
- self.last = self.last + 1
- self.length = self.length + 1
- self[self.last] = item
-end
-
-function Queue:shift()
- -- Ignore the call if the queue is empty. Return
- if self.length == 0 then
- return
- end
-
- -- Get the first item
- local item = self[self.first]
- self[self.first] = nil
- self.length = self.length - 1
-
- if self.first == self.last then
- -- If it was the last item, reset the queue
- self:initialize()
- else
- -- Otherwise enqueue the next item.
- self.first = self.first + 1
- end
-
- return item
-end
-
-local ReadableStream = Object:extend()
-uv.ReadableStream = ReadableStream
-
--- If there are more than this many buffered input chunks, readStop the source
-ReadableStream.highWaterMark = 1
--- If there are less than this many buffered chunks, readStart the source
-ReadableStream.lowWaterMark = 1
-
-function ReadableStream:initialize()
- self.inputQueue = Queue:new()
- self.readerQueue = Queue:new()
-end
-
-function ReadableStream:read() return function (callback)
- self.readerQueue:push(callback)
- self:processReaders()
-end end
-
-function ReadableStream:processReaders()
- while self.inputQueue.length > 0 and self.readerQueue.length > 0 do
- local chunk = self.inputQueue:shift()
- local reader = self.readerQueue:shift()
- reader(nil, chunk)
- end
- local watermark = self.inputQueue.length - self.readerQueue.length
- if watermark > self.highWaterMark and not self.paused then
- self.paused = true
- self:pause()
- elseif watermark < self.lowWaterMark and self.paused then
- self.paused = false
- self:resume()
- end
-end
-
-
-local fs = {}
-uv.fs = fs
-
-function fs.open(path, flags, mode) return function (callback)
- -- TODO: register this resource with the resource cleaner
- native.fsOpen(path, flags, mode or "0644", callback or noop)
-end end
-
-function fs.read(fd, offset, size) return function (callback)
- native.fsRead(fd, offset, size, callback or noop)
-end end
-
-function fs.write(fd, offset, chunk) return function (callback)
- native.fsWrite(fd, offset, chunk, callback or noop)
-end end
-
-function fs.close(fd) return function (callback)
- -- TODO: free this resource from the resource cleaner
- native.fsClose(fd, callback or noop)
-end end
-
-function fs.stat(path) return function (callback)
- native.fsStat(path, callback or noop)
-end end
-
-function fs.fstat(fd) return function (callback)
- native.fsFstat(fd, callback or noop)
-end end
-
-function fs.lstat(path) return function (callback)
- native.fsLstat(path, callback or noop)
-end end
-
-function fs.readdir(path) return function (callback)
- native.fsReaddir(path, callback or noop)
-end end
-
-fs.ReadStream = Object:extend()
-
-fs.ReadStream.chunkSize = 65536
-
-function fs.ReadStream:initialize(fd)
- self.fd = fd
- self.offset = 0
-end
-
-function fs.ReadStream:read() return function (callback)
- fs.read(self.fd, self.offset, self.chunkSize)(function (err, chunk)
- -- In case of error, close the fd and emit the error
- if err then
- fs.close(self.fd)()
- return callback(err)
- end
- local length = #chunk
- -- In case of data, move the offset and emit the chunk.
- if length > 0 then
- self.offset = self.offset + length
- return callback(nil, chunk)
- end
- -- Otherwise, it's EOF. Close the fd and emit end.
- fs.close(self.fd)()
- callback()
- end)
-end end
-
-fs.WriteStream = Object:extend()
-
-function fs.WriteStream:initialize(fd)
- self.fd = fd
- self.offset = 0
-end
-
-function fs.WriteStream:write(chunk) return function (callback)
- -- on eof, close the file
- if not chunk then
- return fs.close(self.fd)(callback)
- end
- -- Otherwise write the chunk
- fs.write(self.fd, self.offset, chunk)(function (err)
- -- On error, close the file and emit the error
- if err then
- fs.close(self.fd)()
- return callback(err)
- end
- callback()
- end)
-end end
-
-local handle = {}
-uv.handle = handle
-
-function handle:close() return function (callback)
- native.close(self, callback)
-end end
-
-function handle:setHandler(name, handler)
- native.setHandler(self, name, handler)
-end
-
-local stream = setmetatable({}, {__index = handle})
-uv.stream = stream
-
-function stream:write(chunk) return function (callback)
- return native.write(self, chunk, callback)
-end end
-
-function stream:shutdown() return function (callback)
- native.shutdown(self, callback)
-end end
-
-function stream:readStart()
- return native.readStart(self)
-end
-
-function stream:readStop()
- return native.readStop(self)
-end
-
-function stream:listen(onConnection)
- return native.listen(self, onConnection)
-end
-
-function stream:accept(client)
- return native.accept(self, client)
-end
-
-stream.Stream = ReadableStream:extend()
-
-
-function stream.Stream:initialize(handle)
- self.handle = handle
- -- Readable stuff
- ReadableStream.initialize(self)
- uv.handle.setHandler(handle, "data", function (chunk)
- self.inputQueue:push(chunk)
- self:processReaders()
- end)
- uv.handle.setHandler(handle, "end", function ()
- self.inputQueue:push()
- self:processReaders()
- end)
- uv.stream.readStart(handle)
-end
-
-function stream.Stream:pause()
- uv.stream.readStop(self.handle)
-end
-
-function stream.Stream:resume()
- uv.stream.readStart(self.handle)
-end
-
-function stream.Stream:write(chunk)
- if chunk then
- return uv.stream.write(self.handle, chunk)
- end
- return uv.stream.shutdown(self.handle)
-end
-
-local tcp = setmetatable({}, {__index=stream})
-uv.tcp = tcp
-
-function tcp:bind(host, port)
- return native.tcpBind(self, host, port)
-end
-
-function tcp.new()
- return native.newTcp()
-end
-
-function tcp:getsockname()
- return native.tcpGetsockname(self)
-end
-
-function tcp.createServer(host, port, onConnection)
- local server = tcp.new()
- tcp.bind(server, host, port)
- tcp.listen(server, function ()
- local client = tcp.new()
- tcp.accept(server, client)
- onConnection(tcp.Stream:new(client))
- end)
- return server
-end
-
-uv.fiber = require('./fiber.lua')
-
-return uv
View
127 modules/continuable/fiber.lua
@@ -1,127 +0,0 @@
-local coroutine = require('coroutine')
-local debug = require('debug')
-
-local fiber = {}
-
--- Map of managed coroutines
-local fibers = {}
-
-local function formatError(co, err)
- local stack = debug.traceback(co, tostring(err))
- if type(err) == "table" then
- err.message = stack
- return err
- end
- return stack
-end
-
-local function check(co, success, ...)
- local fiber = fibers[co]
-
- if not success then
- local err = formatError(co, ...)
- if fiber and fiber.callback then
- return fiber.callback(err)
- end
- error(err)
- end
-
- -- Abort on non-managed coroutines.
- if not fiber then
- return ...
- end
-
- -- If the fiber is done, pass the result to the callback and cleanup.
- if not fiber.paused then
- fibers[co] = nil
- if fiber.callback then
- fiber.callback(nil, ...)
- end
- return ...
- end
-
- fiber.paused = false
-end
-
--- Create a managed fiber as a continuable
-function fiber.new(fn, ...)
- local args = {...}
- local nargs = select("#", ...)
- return function (callback)
- local co = coroutine.create(fn)
- local fiber = {
- callback = callback
- }
- fibers[co] = fiber
-
- check(co, coroutine.resume(co, unpack(args, 1, nargs)))
- end
-end
-
--- Wait in this coroutine for the continuation to complete
-function fiber.wait(continuation)
-
- if type(continuation) ~= "function" then
- error("Continuation must be a function.")
- end
-
- -- Find out what thread we're running in.
- local co, isMain = coroutine.running()
-
- -- When main, Lua 5.1 `co` will be nil, lua 5.2, `isMain` will be true
- if not co or isMain then
- error("Can't wait from the main thread.")
- end
-
- local fiber = fibers[co]
-
- -- Execute the continuation
- local async, ret, nret
- continuation(function (...)
-
- -- If async hasn't been set yet, that means the callback was called before
- -- the continuation returned. We should store the result and wait till it
- -- returns later on.
- if not async then
- async = false
- ret = {...}
- nret = select("#", ...)
- return
- end
-
- -- Callback was called we can resume the coroutine.
- -- When it yields, check for managed coroutines
- check(co, coroutine.resume(co, ...))
-
- end)
-
- -- If the callback was called early, we can just return the value here and
- -- not bother suspending the coroutine in the first place.
- if async == false then
- return unpack(ret, 1, nret)
- end
-
- -- Mark that the contination has returned.
- async = true
-
- -- Mark the fiber as paused if there is one.
- if fiber then fiber.paused = true end
-
- -- Suspend the coroutine and wait for the callback to be called.
- return coroutine.yield()
-end
-
--- This is a wrapper around wait that strips off the first result and
--- interprets is as an error to throw.
-function fiber.await(...)
- -- TODO: find out if there is a way to count the number of return values from
- -- fiber.wait while still storing the results in a table.
- local results = {fiber.wait(...)}
- local nresults = sel
- if results[1] then
- error(results[1])
- end
- return unpack(results, 2)
-end
-
-return fiber
View
6 modules/continuable/package.lua
@@ -1,6 +0,0 @@
-return {
- name = "continuable",
- description = "A continuable interface to luvit's core features",
- version = "0.0.0",
- main = "continuable.lua"
-}
1 modules/web
@@ -0,0 +1 @@
+Subproject commit d76ac5404b6a1100322da1477bf42bfd9185069e
1 modules/web-autoheaders
@@ -0,0 +1 @@
+Subproject commit bf1eb794d6e863e49fdd8ca116c9b42ff8687519
1 modules/web-log
@@ -0,0 +1 @@
+Subproject commit b951aa2eb4507082bda871b679041d43f2c0e751
1 modules/web-static
@@ -0,0 +1 @@
+Subproject commit 3a616da31f2e52b298a150da8060f60b8307721d
View
58 modules/web/middleware/cleanup.lua
@@ -1,58 +0,0 @@
-local ReadableStream = require('continuable').ReadableStream
-local stringFormat = require('string').format
-local osDate = require('os').date
-
-return function (app)
- return function (req, res)
- app(req, function (code, headers, body)
- local hasDate = false
- local hasServer = false
- local hasContentLength = false
- local hasTransferEncoding = false
- for name in pairs(headers) do
- name = name:lower()
- if name == "date" then hasDate = true end
- if name == "server" then hasServer = true end
- if name == "content-length" then hasContentLength = true end
- if name == "transfer-encoding" then hasTransferEncoding = true end
- end
- if not hasDate then
- headers['Date'] = osDate("!%a, %d %b %Y %H:%M:%S GMT")
- end
- if not hasServer then
- headers['Server'] = "Luvit " .. process.version
- end
- if body and (not hasContentLength) and (not hasTransferEncoding) then
- if type(body) == "string" then
- headers["Content-Length"] = #body
- hasContentLength = true
- elseif type(body) == "table" then
- headers["Transfer-Encoding"] = "chunked"
- hasTransferEncoding = true
- local originalStream = body
- body = { done = false }
- function body:read() return function (callback)
- if self.done then
- return callback()
- end
- originalStream:read()(function (err, chunk)
- if err then return callback(err) end
- if chunk then
- return callback(nil, stringFormat("%X\r\n%s\r\n", #chunk, chunk))
- end
- self.done = true
- callback(nil, "0\r\n\r\n\r\n")
- end)
- end end
- end
- end
- if req.should_keep_alive and (hasContentLength or hasTransferEncoding or code == 304) then
- headers["Connection"] = "keep-alive"
- else
- headers["Connection"] = "close"
- req.should_keep_alive = false
- end
- res(code, headers, body)
- end)
- end
-end
View
8 modules/web/middleware/log.lua
@@ -1,8 +0,0 @@
-return function (app)
- return function (req, res)
- app(req, function (code, headers, body)
- print(req.method .. ' ' .. req.url.path .. ' ' .. code)
- res(code, headers, body)
- end)
- end
-end
View
171 modules/web/middleware/static.lua
@@ -1,171 +0,0 @@
-
-local fs = require('continuable').fs
-local pathJoin = require('path').join
-local urlParse = require('url').parse
-local getType = require('mime').getType
-local osDate = require('os').date
-local ReadableStream = require('continuable').ReadableStream
-
-local floor = require('math').floor
-local table = require 'table'
-
--- For encoding numbers using bases up to 64
-local digits = {
- "0", "1", "2", "3", "4", "5", "6", "7",
- "8", "9", "A", "B", "C", "D", "E", "F",
- "G", "H", "I", "J", "K", "L", "M", "N",
- "O", "P", "Q", "R", "S", "T", "U", "V",
- "W", "X", "Y", "Z", "a", "b", "c", "d",
- "e", "f", "g", "h", "i", "j", "k", "l",
- "m", "n", "o", "p", "q", "r", "s", "t",
- "u", "v", "w", "x", "y", "z", "_", "$"
-}
-local function numToBase(num, base)
- local parts = {}
- repeat
- table.insert(parts, digits[(num % base) + 1])
- num = floor(num / base)
- until num == 0
- return table.concat(parts)
-end
-
-local function calcEtag(stat)
- return (not stat.is_file and 'W/' or '') ..
- '"' .. numToBase(stat.ino or 0, 64) ..
- '-' .. numToBase(stat.size, 64) ..
- '-' .. numToBase(stat.mtime, 64) .. '"'
-end
-
-local function createDirStream(path, options)
- local stream = ReadableStream:new()
- fs.readdir(path)(function (err, files)
- if err then
- stream:emit("error", err)
- end
- local html = {
- '<!doctype html>',
- '<html>',
- '<head>',
- '<title>' .. path .. '</title>',
- '</head>',
- '<body>',
- '<h1>' .. path .. '</h1>',
- '<ul><li><a href="../">..</a></li>'
- }
- for i, file in ipairs(files) do
- html[#html + 1] =
- '<li><a href="' .. file .. '">' .. file .. '</a></li>'
- end
- html[#html + 1] = '</ul></body></html>\n'
- html = table.concat(html)
- stream.inputQueue:push(html)
- stream.inputQueue:push()
- stream:processReaders()
- end)
- return stream
-end
-
-
-return function (app, options)
- if not options.root then error("options.root is required") end
- local root = options.root
-
- return function (req, res)
- -- Ignore non-GET/HEAD requests
- if not (req.method == "HEAD" or req.method == "GET") then
- return app(req, res)
- end
-
- local function serve(path, fallback)
- fs.open(path, "r")(function (err, fd)
- if err then
- if err.code == 'ENOENT' or err.code == 'ENOTDIR' then
- if fallback then return serve(fallback) end
- if err.code == 'ENOTDIR' and path:sub(#path) == '/' then
- return res(302, {
- ["Location"] = req.url.path:sub(1, #req.url.path - 1)
- })
- end
- return app(req, res)
- end
- return res(500, {}, tostring(err) .. "\n" .. require('debug').traceback() .. "\n")
- end
-
- fs.fstat(fd)(function (err, stat)
- if err then
- -- This shouldn't happen often, forward it just in case.
- fs.close(fd)()
- return res(500, {}, tostring(err) .. "\n" .. require('debug').traceback() .. "\n")
- end
-
- local etag = calcEtag(stat)
- local code = 200
- local headers = {
- ['Last-Modified'] = osDate("!%a, %d %b %Y %H:%M:%S GMT", stat.mtime),
- ['ETag'] = etag
- }
- local stream
-
- if etag == req.headers['if-none-match'] then
- code = 304
- end
-
- if path:sub(#path) == '/' then
- -- We're done with the fd, createDirStream opens it again by path.
- fs.close(fd)()
-
- if not options.autoIndex then
- -- Ignore directory requests if we don't have autoIndex on
- return app(req, res)
- end
-
- if not stat.is_directory then
- -- Can't autoIndex non-directories
- return res(302, {
- ["Location"] = req.url.path:sub(1, #req.url.path - 1)
- })
- end
-
- if code ~= 304 then
- headers["Content-Type"] = "text/html"
- end
- -- Create the index stream
- if not (req.method == "HEAD" or code == 304) then
- stream = createDirStream(path, options.autoIndex)
- end
- else
- if stat.is_directory then
- -- Can't serve directories as files
- fs.close(fd)()
- return res(302, {
- ["Location"] = req.url.path .. "/"
- })
- end
-
- if code ~= 304 then
- headers["Content-Type"] = getType(path)
- headers["Content-Length"] = stat.size
- end
-
- if not (req.method == "HEAD" or code == 304) then
- stream = fs.ReadStream:new(fd)
- else
- fs.close(fd)()
- end
- end
- res(code, headers, stream)
- end)
- end)
- end
-
- local path = pathJoin(options.root, req.url.path)
-
- if options.index and path:sub(#path) == '/' then
- serve(pathJoin(path, options.index), path)
- else
- serve(path)
- end
-
- end
-end
-
View
6 modules/web/package.lua
@@ -1,6 +0,0 @@
-return {
- name = "web",
- description = "A fast http system for moonslice",
- version = "0.0.0",
- main = "web.lua"
-}
View
163 modules/web/web.lua
@@ -1,163 +0,0 @@
-local newHttpParser = require('http_parser').new
-local tcp = require('continuable').tcp
-local table = require('table')
-local parseUrl = require('http_parser').parseUrl
-local ReadableStream = require('continuable').ReadableStream
-
-local web = {
- cleanup = require('./middleware/cleanup.lua'),
- log = require('./middleware/log.lua'),
- static = require('./middleware/static.lua')
-}
-
-local STATUS_CODES = {
- [100] = 'Continue',
- [101] = 'Switching Protocols',
- [102] = 'Processing', -- RFC 2518, obsoleted by RFC 4918
- [200] = 'OK',
- [201] = 'Created',
- [202] = 'Accepted',
- [203] = 'Non-Authoritative Information',
- [204] = 'No Content',
- [205] = 'Reset Content',
- [206] = 'Partial Content',
- [207] = 'Multi-Status', -- RFC 4918
- [300] = 'Multiple Choices',
- [301] = 'Moved Permanently',
- [302] = 'Moved Temporarily',
- [303] = 'See Other',
- [304] = 'Not Modified',
- [305] = 'Use Proxy',
- [307] = 'Temporary Redirect',
- [400] = 'Bad Request',
- [401] = 'Unauthorized',
- [402] = 'Payment Required',
- [403] = 'Forbidden',
- [404] = 'Not Found',
- [405] = 'Method Not Allowed',
- [406] = 'Not Acceptable',
- [407] = 'Proxy Authentication Required',
- [408] = 'Request Time-out',
- [409] = 'Conflict',
- [410] = 'Gone',
- [411] = 'Length Required',
- [412] = 'Precondition Failed',
- [413] = 'Request Entity Too Large',
- [414] = 'Request-URI Too Large',
- [415] = 'Unsupported Media Type',
- [416] = 'Requested Range Not Satisfiable',
- [417] = 'Expectation Failed',
- [418] = 'I\'m a teapot', -- RFC 2324
- [422] = 'Unprocessable Entity', -- RFC 4918
- [423] = 'Locked', -- RFC 4918
- [424] = 'Failed Dependency', -- RFC 4918
- [425] = 'Unordered Collection', -- RFC 4918
- [426] = 'Upgrade Required', -- RFC 2817
- [500] = 'Internal Server Error',
- [501] = 'Not Implemented',
- [502] = 'Bad Gateway',
- [503] = 'Service Unavailable',
- [504] = 'Gateway Time-out',
- [505] = 'HTTP Version not supported',
- [506] = 'Variant Also Negotiates', -- RFC 2295
- [507] = 'Insufficient Storage', -- RFC 4918
- [509] = 'Bandwidth Limit Exceeded',
- [510] = 'Not Extended' -- RFC 2774
-}
-
-
-function web.socketHandler(app) return function (client)
-
- local currentField, headers, url, request, done
- local parser = newHttpParser("request", {
- onMessageBegin = function ()
- headers = {}
- end,
- onUrl = function (value)
- url = parseUrl(value)
- end,
- onHeaderField = function (field)
- currentField = field
- end,
- onHeaderValue = function (value)
- headers[currentField:lower()] = value
- end,
- onHeadersComplete = function (info)
- request = setmetatable(info, ReadableStream.meta)
- request:initialize()
- request.url = url
- request.headers = headers
- request.parser = parser
- app(request, function (statusCode, headers, body)
- local reasonPhrase = STATUS_CODES[statusCode] or 'unknown'
- if not reasonPhrase then error("Invalid response code " .. tostring(statusCode)) end
-
- local head = {"HTTP/1.1 " .. tostring(statusCode) .. " " .. reasonPhrase .. "\r\n"}
- for key, value in pairs(headers) do
- table.insert(head, key .. ": " .. value .. "\r\n")
- end
- table.insert(head, "\r\n")
- if type(body) == "string" then
- table.insert(head, body)
- end
- client:write(table.concat(head))()
- if type(body) ~= "table" then
- done(info.should_keep_alive)
- else
-
- -- Assume it's a readable stream and pipe it to the client
- local function onRead(err, chunk)
- if err then
- return client:write(tostring(err))(function ()
- done(false)
- end)
- end
- if chunk then
- client:write(chunk)()
- return body:read()(onRead)
- end
- done(info.should_keep_alive)
- end
- body:read()(onRead)
-
- end
- end)
- end,
- onBody = function (chunk)
- request.inputQueue:push(chunk)
- request:processReaders()
- end,
- onMessageComplete = function ()
- request.inputQueue:push()
- request:processReaders()
- end
- })
-
- done = function(keepAlive)
- if keepAlive then
- parser:reinitialize("request")
- else
- client:write()(function (err)
- if (err) then error(err) end
- tcp.close(client.handle)()
- end)
- end
- end
-
- -- Consume the tcp stream and send it to the HTTP parser
- local function onRead(err, chunk)
- if (err) then error(err) end
- if chunk then
- if #chunk > 0 then
- local nparsed = parser:execute(chunk, 0, #chunk)
- -- TODO: handle various cases here
- end
- return client:read()(onRead)
- end
- parser:finish()
- end
- client:read()(onRead)
-
-end end
-
-return web
View
2 test-web-hello.lua
@@ -9,7 +9,7 @@ local app = function (req, res)
}, "Hello World\n")
end
-app = web.cleanup(app)
+app = require('web-autoheaders')(app)
-- Serve the HTTP web app on a TCP server
createServer("127.0.0.1", 8080, web.socketHandler(app))
View
8 test-web.lua
@@ -16,13 +16,13 @@ local app = function (req, res)
end
-- Wrap it in some useful middleware modules
-app = web.static(app, {
+app = require('web-static')(app, {
root = __dirname .. "/public",
index = "index.html",
autoIndex = true
})
-app = web.log(app)
-app = web.cleanup(app)
+app = require('web-log')(app)
+app = require('web-autoheaders')(app)
-- Serve the HTTP web app on a TCP server
-createServer("127.0.0.1", 8080, web.socketHandler(app))
+createServer("0.0.0.0", 8080, web.socketHandler(app))

0 comments on commit e17de4e

Please sign in to comment.
Something went wrong with that request. Please try again.