Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

several changes #127

Merged
merged 9 commits into from
May 5, 2023
34 changes: 31 additions & 3 deletions example/app.lua
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
-- setup path to find the project source files of Pegasus
package.path = './src/?.lua;./src/?/init.lua;' .. package.path

-- For this example to work with the https version, you need LuaSec
-- to be installed, and you need to generate the test certificates from
-- its examples. Copy the 'A' certificates into this example directory
-- to make it work.
-- Then uncomment the TLS plugin section below.

local Pegasus = require 'pegasus'
local Compress = require 'pegasus.plugins.compress'
local Downloads = require 'pegasus.plugins.downloads'
-- local TLS = require 'pegasus.plugins.tls'

local server = Pegasus:new({
port='9090',
location='/example/root/',
plugins = { Compress:new() }
port = '9090',
location = '/example/root/',
plugins = {
-- TLS:new { -- the tls specific configuration
-- wrap = {
-- mode = "server",
-- protocol = "any",
-- key = "./example/serverAkey.pem",
-- certificate = "./example/serverA.pem",
-- cafile = "./example/rootA.pem",
-- verify = {"none"},
-- options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"},
-- },
-- sni = nil,
-- },

Downloads:new {
prefix = "downloads",
stripPrefix = true,
},

Compress:new(),
}
})

server:start(function(req)
Expand Down
12 changes: 8 additions & 4 deletions example/copas.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package.path = "./src/?.lua;./src/?/init.lua;"..package.path
local Handler = require 'pegasus.handler'
local copas = require('copas')
local socket = require('socket')
local Downloads = require 'pegasus.plugins.downloads'

--- Creates a new server within the Copas scheduler.
-- @tparam table opts options table.
Expand Down Expand Up @@ -57,9 +58,7 @@ assert(newPegasusServer{
location = nil,
callback = function(req, resp) -- just redirecting to the https one
local host = (req:headers()["Host"] or ""):match("^([^:]+)")
resp:addHeader("Location", "https://" .. host .. ":9091" .. req:path())
resp:statusCode(301)
resp:sendOnlyHeaders()
resp:redirect("https://" .. host .. ":9091" .. req:path())
end,
plugins = {},
})
Expand All @@ -83,7 +82,12 @@ assert(newPegasusServer{
},
location = '/example/root/',
callback = nil,
plugins = {},
plugins = {
Downloads:new {
prefix = "downloads",
stripPrefix = true,
},
},
})

-- Start
Expand Down
4 changes: 3 additions & 1 deletion example/root/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
<link href="style.css" media="all" rel="stylesheet">
</head>
<body>
<img src="1.jpg">
<a href="downloads/1.jpg">
<img src="1.jpg" />
</a><p>Click the image to download it!</p>
<form method="POST">
Name: <input type="text" name="name">
Age: <input type="text" name="age">
Expand Down
2 changes: 2 additions & 0 deletions rockspecs/pegasus-dev-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@ build = {
['pegasus.response'] = 'src/pegasus/response.lua',
['pegasus.compress'] = 'src/pegasus/compress.lua',
['pegasus.plugins.compress'] = 'src/pegasus/plugins/compress.lua',
['pegasus.plugins.downloads'] = 'src/pegasus/plugins/downloads.lua',
['pegasus.plugins.tls'] = 'src/pegasus/plugins/tls.lua',
}
}
2 changes: 1 addition & 1 deletion spec/unit/request_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('require', function()

test('headers', function()
local request = getInstance(
{ 'GET /Makefile?a=b&c=d&e=1&e=2 HTTP/1.1', 'a: A', 'b: B', 'c: X', 'c: Y', '', 'C=3', '' }
{ 'GET /Makefile?a=b&c=d&e=1&e=2 HTTP/1.1', 'a:A', 'b: \t B \t ', 'c: X', 'c: Y', '', 'C=3', '' }
)

assert.are.same(
Expand Down
30 changes: 23 additions & 7 deletions src/pegasus/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ local Response = require 'pegasus.response'
local mimetypes = require 'mimetypes'
local lfs = require 'lfs'

local function ternary(condition, t, f)
if condition then return t else return f end
end

local Handler = {}
Handler.__index = Handler

Expand All @@ -33,6 +29,18 @@ function Handler:pluginsAlterRequestResponseMetatable()
end
end

function Handler:pluginsNewConnection(client)
for _, plugin in ipairs(self.plugins) do
if plugin.newConnection then
client = plugin:newConnection(client)
if not client then
return false
Tieske marked this conversation as resolved.
Show resolved Hide resolved
Tieske marked this conversation as resolved.
Show resolved Hide resolved
end
end
end
return client
end

function Handler:pluginsNewRequestResponse(request, response)
for _, plugin in ipairs(self.plugins) do
if plugin.newRequestResponse then
Expand Down Expand Up @@ -96,8 +104,12 @@ function Handler:processBodyData(data, stayOpen, response)
end

function Handler:processRequest(port, client, server)
local request = Request:new(port, client, server)
client = self:pluginsNewConnection(client)
if not client then
return false
end

local request = Request:new(port, client, server)
if not request:method() then
client:close()
return
Expand All @@ -112,7 +124,10 @@ function Handler:processRequest(port, client, server)
end

if request:path() and self.location ~= '' then
local path = ternary(request:path() == '/' or request:path() == '', 'index.html', request:path())
local path = request:path()
if path == '/' or path == '' then
path = 'index.html'
end
local filename = '.' .. self.location .. path

if not lfs.attributes(filename) then
Expand All @@ -129,12 +144,13 @@ function Handler:processRequest(port, client, server)

if file then
response:writeFile(file, mimetypes.guess(filename or '') or 'text/html')
stop = true
else
response:statusCode(404)
end
end

if self.callback then
if self.callback and not stop then
response:statusCode(200)
response.headers = {}
response:addHeader('Content-Type', 'text/html')
Expand Down
23 changes: 13 additions & 10 deletions src/pegasus/plugins/compress.lua
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
local zlib = require "zlib"
local ZlibStream = {} do
local zlib = require "zlib"

local function zlib_name(lib)
if lib._VERSION and string.find(lib._VERSION, 'lua-zlib', nil, true) then
return 'lua-zlib'
end
local function zlib_name(lib)
if lib._VERSION and string.find(lib._VERSION, 'lua-zlib', nil, true) then
return 'lua-zlib'
end

if lib._VERSION and string.find(lib._VERSION, 'lzlib', nil, true) then
return 'lzlib'
if lib._VERSION and string.find(lib._VERSION, 'lzlib', nil, true) then
return 'lzlib'
end
end
end

local z_lib_name = assert(zlib_name(zlib), 'Unsupported zlib Lua binding')
local z_lib_name = assert(zlib_name(zlib), 'Unsupported zlib Lua binding')


local ZlibStream = {} do
ZlibStream.__index = ZlibStream

ZlibStream.NO_COMPRESSION = zlib.NO_COMPRESSION or 0
Expand Down Expand Up @@ -69,6 +70,8 @@ local ZlibStream = {} do
end
end



local Compress = {} do
Compress.__index = Compress

Expand Down
64 changes: 64 additions & 0 deletions src/pegasus/plugins/downloads.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
--- A plugin that allows to download files via a browser.
local Downloads = {}
Downloads.__index = Downloads

--- Creates a new plugin instance.
-- The plugin will only respond to `GET` requests. The files will be served from the
-- same `location` setting as defined in the `Handler`. The `prefix` is a virtual folder
-- that triggers the plugin, but will be removed from the filepath if `stripPrefix` is truthy.
-- If `stripPrefix` is falsy, then it should be a real folder.
-- @tparam options table the options table with the following fields;
-- @tparam[opt="downloads/"] options.prefix string the path prefix that triggers the plugin
-- @tparam options.stripPrefix bool whether to strip the prefix from the file path when looking
-- for the file in the filesystem. Defaults to `false`, unless `options.prefix` is omitted,
-- then it defaults to `true`.
-- @return the new plugin
function Downloads:new(options)
options = options or {}
local plugin = {}

if not options.prefix then
plugin.prefix = "downloads/"
if options.stripPrefix == nil then
plugin.stripPrefix = true
else
plugin.stripPrefix = not not options.stripPrefix
end
else
plugin.prefix = options.prefix
plugin.stripPrefix = not not options.stripPrefix
end

plugin.prefix = "/" .. plugin.prefix .. "/"
while plugin.prefix:find("//") do
plugin.prefix = plugin.prefix:gsub("//", "/")
end

setmetatable(plugin, Downloads)

return plugin
end

function Downloads:newRequestResponse(request, response)
local stop = false
if request:method() ~= "GET" then
return stop -- we only handle GET requests
end

local path = request:path()
if path:find(self.prefix, nil, true) ~= 1 then
return stop -- doesn't match our prefix
end

local location = response._writeHandler.location or ""
local filename = path
if self.stripPrefix then
filename = path:sub(#self.prefix + 1, -1)
end

stop = not not response:sendFile('.' .. location .. filename)
return stop
end


return Downloads
55 changes: 55 additions & 0 deletions src/pegasus/plugins/tls.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
--- A plugin that enables TLS (https).
Tieske marked this conversation as resolved.
Show resolved Hide resolved
-- This plugin should not be used with Copas. Since Copas has native TLS support
-- and can handle simultaneous `http` and `https` connections. See the Copas example
-- to learn how to set that up.
local ssl = require("ssl")


local TLS = {}
TLS.__index = TLS


--- Creates a new plugin instance.
-- IMPORTANT: this must be the first plugin to execute before the client-socket is accessed!
-- @tparam sslparams table the data-structure that contains the properties for the luasec functions.
-- The structure is set up to mimic the LuaSec functions for the handshake.
-- @return the new plugin
-- @usage
-- local sslparams = {
-- wrap = table | context, -- parameter to LuaSec 'wrap()'
-- sni = { -- parameters to LuaSec 'sni()'
-- names = string | table -- 1st parameter
-- strict = bool -- 2nd parameter
-- }
-- }
-- local tls_plugin = require("pegasus.plugins.tls"):new(sslparams)
function TLS:new(sslparams)
sslparams = sslparams or {}
assert(sslparams.wrap, "'sslparam.wrap' is a required option")

return setmetatable({
sslparams = sslparams
}, TLS)
end

function TLS:newConnection(client)
local params = self.sslparams

-- wrap the client socket and replace it
client = assert(ssl.wrap(client, params.wrap))

if params.sni then
assert(client:sni(params.sni.names, params.sni.strict))
end

if not client:dohandshake() then
print"handshake failed"
return false
Tieske marked this conversation as resolved.
Show resolved Hide resolved
Tieske marked this conversation as resolved.
Show resolved Hide resolved
end
print"running TLS"

return client
end


return TLS
5 changes: 3 additions & 2 deletions src/pegasus/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ Request.PATTERN_PROTOCOL = '(HTTP%/%d%.%d)'
Request.PATTERN_REQUEST = (Request.PATTERN_METHOD ..
Request.PATTERN_PATH ..Request.PATTERN_PROTOCOL)
Request.PATTERN_QUERY_STRING = '([^=]*)=([^&]*)&?'
Request.PATTERN_HEADER = '([%w-]+): ([%w %p]+=?)'
Request.PATTERN_HEADER = '([%w-]+):[ \t]*([%w \t%p]*)'

function Request:new(port, client, server)
local obj = {}
obj.client = client
obj.server = server
obj.port = port
obj.ip = client:getpeername()
obj.ip = (client.getpeername or function() end)(client) -- luasec doesn't support this method
obj.querystring = {}
obj._firstLine = nil
obj._method = nil
Expand Down Expand Up @@ -151,6 +151,7 @@ function Request:headers()

if key and value then
key = key:lower()
value = value:gsub("%s+$", "") -- trim trailing whitespace
local v = headers[key]
if not v then
headers[key] = value
Expand Down
Loading