require("./libs/Response") local http = require("http") local https = require("https") local path = require("path") local fs = require('fs') local fse = require("./libs/fse") local Router = require('./libs/router') local mime = require('./libs/mime') local helpers = require('./libs/helpers') local querystring = require('querystring') local Cookie = require("./libs/cookie") require("./libs/ansicolors") d((" Hello "):bluebg(), (" World "):redbg(), (" from MoonCake "):yellowbg(), (" ! "):greenbg()) local Emitter = require("core").Emitter local getQueryFromUrl = function(url) local params = string.match(url, "?(.*)") or "" return querystring.parse(params) end local MoonCake = Emitter:extend() function MoonCake:initialize(options) options = options or {} self.options = options self.notFoundRequest = function(req, res) print(req.method, req.url) local content = "

Page Not Found

" res:send(content, 404) end self.notAuthorizedRequest = function(filePath, routePath, req, res) local fileList = fs.readdirSync(filePath) local parentPathList = helpers.split2(routePath, "/") if routePath:sub(#routePath) == "/" then table.remove(parentPathList) end table.remove(parentPathList) local parentPath = table.concat(parentPathList, "/") if parentPath == "" then parentPath = "/" end local data = {parentPath = parentPath, currentPath = routePath, files = {}} for idx, v in pairs(fileList) do local fstat = fs.statSync(path.join(filePath, v)) local vfilePath = path.join(routePath, v) if fstat.type ~= "file" then vfilePath = vfilePath .. "/" end local lastModifiedTime = os.date("%c", fstat.mtime.sec) data.files[idx] = { name = v, path = vfilePath, lastModified = lastModifiedTime, type = fstat.type, size = fstat.size } end res:render(path.resolve(module.dir, "./libs/directory.html"), data) end self.router = Router.new() self.isHttps = false if self.options.isHttps == true then self.isHttps = true self.keyPath = self.options.keyPath end return self end function MoonCake:listen (port) return self:start(port) end function MoonCake:start (port, host) host = host or "127.0.0.1" if port == nil then port = 8080 end self:genRoute() local fn = self.fn if self.isHttps == true then local keyConfig = { key = fs:readFileSync(path.join(self.keyPath, "key.pem")), cert = fs:readFileSync(path.join(self.keyPath, "cert.pem")) } https.createServer(keyConfig, fn):listen(port, host) else http.createServer(fn):listen(port, host) end d(("Moon"):redbg(),("Cake"):yellowbg()," Server Listening at http://localhost:" .. tostring(port) .. "/") end function MoonCake:use(fn) self._use = self._use or {} table.insert(self._use, fn) end function MoonCake:useit(req, res, callback) local funcArray = helpers.copy(self._use or {}) local function _useit(req, res) local next = table.remove(funcArray or {}) if next then next(req, res, function() _useit(req, res) end) else callback(req, res) end end _useit(req, res) end function MoonCake:genRoute () local that = self self.fn = function(req, res) local url = req.url local method = string.lower(req.method) res.req = req local params = {req, res } local querys = getQueryFromUrl(url) req.query = querys req.start_time = helpers.getTime() res:on("finish", function() helpers.log(req, res) end) if req.headers.cookie then local cookie = Cookie:parse(req.headers.cookie) req.cookie = cookie or {} else req.cookie = {} end if method ~= "get" then local body = "" local fileData = "" req:on("data", function(chunk) if string.find(req.headers['content-type'], "multipart/form-data", 1, true) then fileData = fileData..chunk else body = body..chunk end end) req:on("end", function() if string.find(req.headers['content-type'], "multipart/form-data", 1, true) then local boundary = string.match(fileData, "^([^\r?\n?]+)\n?\r?") local fileArray = helpers.split2(fileData,boundary) table.remove(fileArray) table.remove(fileArray, 1) req.files = {} req.body = {} for _, fileString in pairs(fileArray) do local header, headers = string.match(fileString, "^\r?\n(.-\r?\n\r?\n)"), {} local content = "" string.gsub(fileString, "^\r?\n(.-\r?\n\r?\n)(.*)", function(_,b) if b:sub(#b-1):find("\r?\n") then local _, n = b:sub(#b-1):find("\r?\n") content = b:sub(0,#b-n) end end) string.gsub(header, '%s?([^%:?%=?]+)%:?%s?%=?%"?([^%"?%;?%c?]+)%"?%;?%c?', function(k,v) headers[k] = v end) if headers["filename"] then local tempname = os.tmpname() fs.writeFileSync(tempname, content) req.files[headers["name"]] = {path = tempname, name = headers["filename"], ["Content-Type"] = headers["Content-Type"] } else req.body[headers["name"]] = content end end -- req.files[tempname] = { path = tempname } else local bodyObj = querystring.parse(body) req.body = bodyObj or {} if req.body._method then method = req.body._method:lower() end end req.body = req.body or {} req.files = req.files or {} that:useit(req, res, function(req, res) local result, err = that.router:execute(method, url, params) if not result then print(err) return that:notFound(req, res) end end) end) else req.body = {} that:useit(req, res, function(req, res) local result, err = that.router:execute(method, url, params) if not result then print(err) that:notFound(req, res) end end) end end end function MoonCake:match (method, path, fn) local routeFunc = function(params) local req, res = params[1], params[2] req.params = params fn(req, res, params) end self.router:match(method, path, routeFunc) end function MoonCake:get(path, fn) self:match("get", path, fn) end function MoonCake:post(path, fn) self:match("post", path, fn) end function MoonCake:put(path, fn) self:match("put", path, fn) end function MoonCake:delete(path, fn) self:match("delete", path, fn) end function MoonCake:all(path, fn) for _, method in pairs({"get", "post", "put", "delete"}) do self:match(method, path, fn) end end function MoonCake:route(routes) for method, route in pairs(routes) do for path, fn in pairs(route) do self:match(method, path, fn) end end end function MoonCake:notFound(req, res) local fn = self.notFoundFunc fn(req, res) end function MoonCake:static (fileDir, options) options = options or {} options.root = options.root or "/" print("Serving Directory:" .. fileDir) local headers = {} local maxAge = options.maxAge or 15552000 -- half a year headers["Cache-Control"] = "public, max-age=" .. tostring(maxAge) local routePath = path.join(options.root, ":file") return self:get(routePath, function(req, res) local trimdPath = req.params.file:match("([^?]*)(?*)(.*)") local filePath = path.resolve(fileDir, trimdPath) local trimedRoutePath = path.resolve(options.root, trimdPath) if fs.existsSync(filePath) then if fs.statSync(filePath).type == "file" then res:sendFile(filePath, headers) else self.notAuthorizedRequest(filePath, trimedRoutePath, req, res) end else self.notFoundRequest(req, res) end end) end module.exports = MoonCake return MoonCake