├── LICENSE ├── README.md ├── examples └── sailor │ ├── controllers │ └── remy_demo.lua │ └── views │ └── remy_demo │ └── index.lp ├── rockspecs ├── remy-0.2.10-1.rockspec ├── remy-0.2.11-1.rockspec ├── remy-0.2.12-1.rockspec ├── remy-0.2.13-1.rockspec ├── remy-0.2.14-1.rockspec └── remy-current-1.rockspec └── src ├── remy.lua └── remy ├── cgilua.lua ├── file_obj.lua ├── lwan.lua ├── mod_magnet.lua ├── mod_plua.lua └── nginx.lua /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Felipe Daragon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Remy # 2 | 3 | Remy (the R emulator) is a simple mod_lua emulator, allowing to run web applications built for mod_lua in alternative environments that allow to run server-side Lua code. As such, it is able to support and emulate the mod_lua API, the request_rec structure and some of its built-in functions. 4 | 5 | This is a work in progress, an may not be suitable for production environments yet. 6 | 7 | Remy was developed as part of [Sailor](https://github.com/Etiene/sailor), a Lua-based MVC framework which originally uses mod_lua, and is already able to run mod_lua apps in a variety of environments (listed below). 8 | 9 | ## Supported Environments # 10 | 11 | * Any web server with [CGILua](https://github.com/keplerproject/cgilua) Tested with: 12 | * Apache 13 | * [Civetweb](https://github.com/bel2125/civetweb) 14 | * [Mongoose](https://github.com/cesanta/mongoose) 15 | * Untested: IIS 16 | * Apache with [mod_lua](http://www.modlua.org/) 17 | * Apache with [mod_plua](https://github.com/Humbedooh/mod_pLua) 18 | * Lighttp with [mod_magnet](http://redmine.lighttpd.net/projects/1/wiki/Docs_ModMagnet) 19 | * [Lwan Web Server](http://lwan.ws/) 20 | * Nginx with [ngx_lua](https://github.com/nginx/nginx) (HttpLuaModule) 21 | 22 | ## Planned Environments # 23 | 24 | * IIS with [LuaScript](http://na-s.jp/LuaScript/) 25 | 26 | ## Usage # 27 | 28 | ``` lua 29 | local remy = require "remy" 30 | 31 | function handle(r) 32 | r.content_type = "text/plain" 33 | 34 | if r.method == 'GET' then 35 | r:puts("Hello Lua World!\n") 36 | for k, v in pairs( r:parseargs() ) do 37 | r:puts( string.format("%s: %s\n", k, v) ) 38 | end 39 | elseif r.method == 'POST' then 40 | r:puts("Hello Lua World!\n") 41 | for k, v in pairs( r:parsebody() ) do 42 | r:puts( string.format("%s: %s\n", k, v) ) 43 | end 44 | end 45 | return apache2.OK 46 | end 47 | 48 | remy.init() 49 | remy.contentheader("text/plain") 50 | remy.run(handle) 51 | ``` 52 | 53 | ## License # 54 | 55 | Remy is licensed under the MIT license (http://opensource.org/licenses/MIT) 56 | 57 | (c) Felipe Daragon, 2014-2015 -------------------------------------------------------------------------------- /examples/sailor/controllers/remy_demo.lua: -------------------------------------------------------------------------------- 1 | local remy_demo = {} 2 | 3 | -- This page will work with mod_lua, mod_pLua & CGILua 4 | function remy_demo.index(page) 5 | local t = {} 6 | t.server = page.r.banner 7 | t.ip = page.r.useragent_ip 8 | page:render('index',{server=t.server,ip=t.ip}) 9 | end 10 | 11 | -- This one will work only with CGILua 12 | function remy_demo.cgi(page) 13 | local t = {} 14 | t.server = cgilua.servervariable('SERVER_SOFTWARE') 15 | t.ip = cgilua.servervariable('REMOTE_ADDR') 16 | page:render('index',{server=t.server,ip=t.ip}) 17 | end 18 | 19 | return remy_demo -------------------------------------------------------------------------------- /examples/sailor/views/remy_demo/index.lp: -------------------------------------------------------------------------------- 1 |

2 | This server is running: 3 |

4 | Your IP address is: 5 |

-------------------------------------------------------------------------------- /rockspecs/remy-0.2.10-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "remy" 2 | version = "0.2.10-1" 3 | source = { 4 | url = "git://github.com/sailorproject/remy", 5 | tag = "v0.2.10-alpha" 6 | } 7 | description = { 8 | summary = "An abstraction of web servers supporting Lua", 9 | detailed = [[ 10 | Remy (the R emulator) is a simple mod_lua emulator, allowing to run web applications built for mod_lua in alternative environments that allow to run server-side Lua code 11 | ]], 12 | license = "MIT" 13 | } 14 | dependencies = { 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | remy = "src/remy.lua", 20 | ['remy.cgilua'] = "src/remy/cgilua.lua", 21 | ['remy.mod_magnet'] = "src/remy/mod_magnet.lua", 22 | ['remy.mod_plua'] = "src/remy/mod_plua.lua", 23 | ['remy.nginx'] = "src/remy/nginx.lua", 24 | ['remy.lwan'] = "src/remy/lwan.lua", 25 | ['remy.nginx'] = "src/remy/nginx.lua", 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rockspecs/remy-0.2.11-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "remy" 2 | version = "0.2.11-1" 3 | source = { 4 | url = "git://github.com/sailorproject/remy", 5 | tag = "v0.2.11-alpha" 6 | } 7 | description = { 8 | summary = "An abstraction of web servers supporting Lua", 9 | detailed = [[ 10 | Remy (the R emulator) is a simple mod_lua emulator, allowing to run web applications built for mod_lua in alternative environments that allow to run server-side Lua code 11 | ]], 12 | license = "MIT" 13 | } 14 | dependencies = { 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | remy = "src/remy.lua", 20 | ['remy.cgilua'] = "src/remy/cgilua.lua", 21 | ['remy.mod_magnet'] = "src/remy/mod_magnet.lua", 22 | ['remy.mod_plua'] = "src/remy/mod_plua.lua", 23 | ['remy.nginx'] = "src/remy/nginx.lua", 24 | ['remy.lwan'] = "src/remy/lwan.lua", 25 | ['remy.nginx'] = "src/remy/nginx.lua", 26 | ['remy.file_obj'] = "src/remy/file_obj.lua", 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rockspecs/remy-0.2.12-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "remy" 2 | version = "0.2.12-1" 3 | source = { 4 | url = "git://github.com/sailorproject/remy", 5 | tag = "v0.2.12-alpha" 6 | } 7 | description = { 8 | summary = "An abstraction of web servers supporting Lua", 9 | detailed = [[ 10 | Remy (the R emulator) is a simple mod_lua emulator, allowing to run web applications built for mod_lua in alternative environments that allow to run server-side Lua code 11 | ]], 12 | license = "MIT" 13 | } 14 | dependencies = { 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | remy = "src/remy.lua", 20 | ['remy.cgilua'] = "src/remy/cgilua.lua", 21 | ['remy.mod_magnet'] = "src/remy/mod_magnet.lua", 22 | ['remy.mod_plua'] = "src/remy/mod_plua.lua", 23 | ['remy.nginx'] = "src/remy/nginx.lua", 24 | ['remy.lwan'] = "src/remy/lwan.lua", 25 | ['remy.nginx'] = "src/remy/nginx.lua", 26 | ['remy.file_obj'] = "src/remy/file_obj.lua", 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rockspecs/remy-0.2.13-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "remy" 2 | version = "0.2.13-1" 3 | source = { 4 | url = "git://github.com/sailorproject/remy", 5 | tag = "v0.2.13-alpha" 6 | } 7 | description = { 8 | summary = "An abstraction of web servers supporting Lua", 9 | detailed = [[ 10 | Remy (the R emulator) is a simple mod_lua emulator, allowing to run web applications built for mod_lua in alternative environments that allow to run server-side Lua code 11 | ]], 12 | license = "MIT" 13 | } 14 | dependencies = { 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | remy = "src/remy.lua", 20 | ['remy.cgilua'] = "src/remy/cgilua.lua", 21 | ['remy.mod_magnet'] = "src/remy/mod_magnet.lua", 22 | ['remy.mod_plua'] = "src/remy/mod_plua.lua", 23 | ['remy.nginx'] = "src/remy/nginx.lua", 24 | ['remy.lwan'] = "src/remy/lwan.lua", 25 | ['remy.nginx'] = "src/remy/nginx.lua", 26 | ['remy.file_obj'] = "src/remy/file_obj.lua", 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rockspecs/remy-0.2.14-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "remy" 2 | version = "0.2.14-1" 3 | source = { 4 | url = "git://github.com/sailorproject/remy", 5 | tag = "v0.2.14-alpha" 6 | } 7 | description = { 8 | summary = "An abstraction of web servers supporting Lua", 9 | detailed = [[ 10 | Remy (the R emulator) is a simple mod_lua emulator, allowing to run web applications built for mod_lua in alternative environments that allow to run server-side Lua code 11 | ]], 12 | license = "MIT" 13 | } 14 | dependencies = { 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | remy = "src/remy.lua", 20 | ['remy.cgilua'] = "src/remy/cgilua.lua", 21 | ['remy.mod_magnet'] = "src/remy/mod_magnet.lua", 22 | ['remy.mod_plua'] = "src/remy/mod_plua.lua", 23 | ['remy.nginx'] = "src/remy/nginx.lua", 24 | ['remy.lwan'] = "src/remy/lwan.lua", 25 | ['remy.nginx'] = "src/remy/nginx.lua", 26 | ['remy.file_obj'] = "src/remy/file_obj.lua", 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rockspecs/remy-current-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "remy" 2 | version = "current-1" 3 | source = { 4 | url = "git://github.com/sailorproject/remy", 5 | } 6 | description = { 7 | summary = "An abstraction of web servers supporting Lua", 8 | detailed = [[ 9 | Remy (the R emulator) is a simple mod_lua emulator, allowing to run web applications built for mod_lua in alternative environments that allow to run server-side Lua code 10 | ]], 11 | license = "MIT" 12 | } 13 | dependencies = { 14 | } 15 | build = { 16 | type = "builtin", 17 | modules = { 18 | remy = "src/remy.lua", 19 | ['remy.cgilua'] = "src/remy/cgilua.lua", 20 | ['remy.mod_magnet'] = "src/remy/mod_magnet.lua", 21 | ['remy.mod_plua'] = "src/remy/mod_plua.lua", 22 | ['remy.nginx'] = "src/remy/nginx.lua", 23 | ['remy.lwan'] = "src/remy/lwan.lua", 24 | ['remy.nginx'] = "src/remy/nginx.lua", 25 | ['remy.file_obj'] = "src/remy/file_obj.lua", 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/remy.lua: -------------------------------------------------------------------------------- 1 | -- Remy 0.2.11 2 | -- Copyright (c) 2014-2015 Felipe Daragon 3 | -- License: MIT (http://opensource.org/licenses/mit-license.php) 4 | -- 5 | -- Remy runs Lua-based web applications in alternative web server 6 | -- environments that allow to run Lua code. 7 | 8 | local remy = { 9 | MODE_AUTODETECT = nil, 10 | MODE_CGILUA = 0, 11 | MODE_MOD_PLUA = 1, 12 | MODE_NGINX = 2, 13 | MODE_LWAN = 3, 14 | MODE_LIGHTTPD = 4 15 | } 16 | 17 | local forced_mode = nil 18 | 19 | local emu = {} 20 | 21 | -- The values below will be updated during runtime 22 | remy.config = { 23 | banner = "Apache/2.4.7 (Unix)", 24 | hostname = "localhost", 25 | uri = "/index.lua" 26 | } 27 | 28 | remy.responsetext = nil 29 | 30 | -- HTTPd Package constants 31 | remy.httpd = { 32 | -- Internal constants from include/httpd.h 33 | OK = 0, 34 | DECLINED = -1, 35 | DONE = -2, 36 | version = remy.config.banner, 37 | -- Other HTTP status codes are not yet implemented in mod_lua 38 | HTTP_MOVED_TEMPORARILY = 302, 39 | -- Internal constants used by mod_proxy 40 | PROXYREQ_NONE = 0, 41 | PROXYREQ_PROXY = 1, 42 | PROXYREQ_REVERSE = 2, 43 | PROXYREQ_RESPONSE = 3, 44 | -- Internal constants used by mod_authz_core 45 | AUTHZ_DENIED = 0, 46 | AUTHZ_GRANTED = 1, 47 | AUTHZ_NEUTRAL = 2, 48 | AUTHZ_GENERAL_ERROR = 3, 49 | AUTHZ_DENIED_NO_USER = 4 50 | } 51 | 52 | -- mod_lua's request_rec 53 | -- The values below will be updated during runtime 54 | local request_rec_fields = { 55 | allowoverrides = " ", 56 | ap_auth_type = nil, 57 | args = nil, 58 | assbackwards = false, 59 | auth_name = "", 60 | banner = remy.config.banner, 61 | basic_auth_pw = "", 62 | canonical_filename = nil, 63 | content_encoding = nil, 64 | content_type = nil, 65 | context_prefix = nil, 66 | context_document_root = nil, 67 | document_root = nil, 68 | err_headers_out = {}, 69 | filename = nil, 70 | handler = "lua-script", 71 | headers_in = {}, -- request headers 72 | headers_out = {}, -- response headers 73 | hostname = remy.config.hostname, 74 | is_https = false, 75 | is_initial_req = true, 76 | limit_req_body = 0, 77 | log_id = nil, 78 | method = "GET", 79 | notes = {}, 80 | options = "Indexes FollowSymLinks ", 81 | path_info = "", 82 | port = 80, 83 | protocol = "HTTP/1.1", 84 | proxyreq = "PROXYREQ_NONE", 85 | range = nil, 86 | remaining = 0, 87 | server_built = "Nov 26 2013 15:46:56", 88 | server_name = remy.config.hostname, 89 | some_auth_required = false, 90 | subprocess_env = {}, 91 | started = 1393508507, 92 | status = 200, 93 | the_request = "GET "..remy.config.uri.." HTTP/1.1", 94 | unparsed_uri = remy.config.uri, 95 | uri = remy.config.uri, 96 | user = nil, 97 | useragent_ip = "127.0.0.1" 98 | } 99 | 100 | function remy.init(mode, native_request) 101 | remy.responsetext = nil 102 | if mode == remy.MODE_AUTODETECT then 103 | mode = remy.detect(native_request) 104 | end 105 | if mode == remy.MODE_CGILUA then 106 | emu = require "remy.cgilua" 107 | elseif mode == remy.MODE_NGINX then 108 | emu = require "remy.nginx" 109 | elseif mode == remy.MODE_MOD_PLUA then 110 | emu = require "remy.mod_plua" 111 | elseif mode == remy.MODE_LIGHTTPD then 112 | emu = require "remy.mod_magnet" 113 | elseif mode == remy.MODE_LWAN then 114 | emu = require "remy.lwan" 115 | end 116 | apache2 = remy.httpd 117 | emu.init(native_request) 118 | return mode 119 | end 120 | 121 | -- Sets the value of the Content Type header field 122 | function remy.contentheader(content_type) 123 | emu.contentheader(content_type) 124 | end 125 | 126 | -- Detects the Lua environment 127 | function remy.detect(native_request) 128 | local mode = nil 129 | if forced_mode then return forced_mode end 130 | if package.loaded.cgilua ~= nil then 131 | mode = remy.MODE_CGILUA 132 | elseif package.loaded.ngx ~= nil then 133 | mode = remy.MODE_NGINX 134 | elseif getEnv ~= nil then 135 | local env = getEnv() 136 | if env["pLua-Version"] ~= nil then 137 | mode = remy.MODE_MOD_PLUA 138 | end 139 | -- Note: lighty is not a package so package.loaded.lighty ~= nil will not work 140 | elseif lighty ~= nil then 141 | mode = remy.MODE_LIGHTTPD 142 | elseif native_request ~= nil and type(native_request.query_param) == "function" then 143 | mode = remy.MODE_LWAN 144 | end 145 | return mode 146 | end 147 | 148 | -- Handles the return code 149 | function remy.finish(code) 150 | emu.finish(code) 151 | end 152 | 153 | -- Load the default request_rec fields 154 | function remy.loadrequestrec(r) 155 | for k,v in pairs(request_rec_fields) do r[k] = v end 156 | return r 157 | end 158 | 159 | -- Temporarily stores the printed content (needed by CGILua mode) 160 | function remy.print(str) 161 | remy.responsetext = remy.responsetext or "" 162 | remy.responsetext = remy.responsetext..str 163 | end 164 | 165 | -- Runs the mod_lua handle function 166 | function remy.run(handlefunc) 167 | local code = handlefunc(emu.request) 168 | remy.finish(code) 169 | end 170 | 171 | function remy.splitstring(s, delimiter) 172 | local result = {} 173 | for match in (s..delimiter):gmatch("(.-)"..delimiter) do 174 | table.insert(result, match) 175 | end 176 | return result 177 | end 178 | 179 | function remy.sha1(str) 180 | local sha1 = require "sha1" 181 | return sha1(str) 182 | end 183 | 184 | return remy 185 | -------------------------------------------------------------------------------- /src/remy/cgilua.lua: -------------------------------------------------------------------------------- 1 | -- Remy - CGI-Lua compatibility 2 | -- Copyright (c) 2014 Felipe Daragon 3 | -- License: MIT 4 | 5 | local base64 = require "base64" 6 | local cgilua = require "cgilua" 7 | local remy = require "remy" 8 | 9 | -- TODO: implement all functions from mod_lua's request_rec 10 | local request = { 11 | -- ENCODING/DECODING FUNCTIONS 12 | base64_decode = function(_,...) return base64.decode(...) end, 13 | base64_encode = function(_,...) return base64.encode(...) end, 14 | escape = function(_,...) return cgilua.urlcode.escape(...) end, 15 | unescape = function(_,...) return cgilua.urlcode.unescape(...) end, 16 | sha1 = function(_,...) return remy.sha1(...) end, 17 | -- REQUEST PARSING FUNCTIONS 18 | parseargs = function(_) return cgilua.QUERY, {} end, 19 | parsebody = function(_) return cgilua.POST, {} end, 20 | -- REQUEST RESPONSE FUNCTIONS 21 | --puts = function(_,...) cgilua.put(...) end, 22 | --write = function(_,...) cgilua.print(...) end 23 | puts = function(_,...) remy.print(...) end, 24 | write = function(_,...) remy.print(...) end 25 | } 26 | 27 | local M = { 28 | mode = "cgilua", 29 | request = request 30 | } 31 | 32 | function M.init() 33 | local r = request 34 | local query = cgilua.servervariable("QUERY_STRING") 35 | local port = cgilua.servervariable("SERVER_PORT") 36 | local server_name = cgilua.servervariable("SERVER_NAME") 37 | local path_info = M.getpathinfo() 38 | apache2.version = cgilua.servervariable("SERVER_SOFTWARE") 39 | r = remy.loadrequestrec(r) 40 | r.ap_auth_type = cgilua.servervariable("AUTH_TYPE") 41 | if query ~= nil and query ~= '' then 42 | r.args = query 43 | end 44 | r.banner = apache2.version 45 | r.canonical_filename = cgilua.script_path 46 | r.content_type = "text/html" -- CGILua needs a default content_type 47 | r.context_document_root = cgilua.script_pdir 48 | r.document_root = cgilua.script_pdir 49 | r.filename = cgilua.script_path 50 | r.hostname = server_name 51 | r.method = cgilua.servervariable("REQUEST_METHOD") 52 | r.path_info = path_info 53 | if port ~= nil then 54 | r.port = tonumber(port) 55 | if r.port == 443 then 56 | r.is_https = true 57 | end 58 | end 59 | r.protocol = cgilua.servervariable("SERVER_PROTOCOL") 60 | r.range = cgilua.servervariable("HTTP_RANGE") 61 | r.server_name = server_name 62 | r.started = os.time() 63 | r.the_request = r.method..' '..M.getunparseduri()..' '..r.protocol 64 | r.unparsed_uri = M.getunparseduri() 65 | r.uri = path_info 66 | r.user = cgilua.servervariable("REMOTE_USER") 67 | r.useragent_ip = cgilua.servervariable("REMOTE_ADDR") 68 | end 69 | 70 | function M.getpathinfo() 71 | local p = cgilua.servervariable("PATH_INFO") 72 | --Xavante compatibility fix (Etiene) 73 | if cgilua.servervariable("SERVER_SOFTWARE"):match('^Xavante')then 74 | p = cgilua.urlpath 75 | end 76 | if p == nil then 77 | p = cgilua.servervariable("SCRIPT_NAME") 78 | end 79 | return p 80 | end 81 | 82 | function M.getunparseduri() 83 | local uri = M.getpathinfo() 84 | local query = cgilua.servervariable("QUERY_STRING") 85 | if query ~= nil and query ~= '' then 86 | uri = uri..'?'..query 87 | end 88 | return uri 89 | end 90 | 91 | function M.contentheader(content_type) 92 | local r = request 93 | r.content_type = content_type 94 | end 95 | 96 | -- TODO: better handle the return code 97 | function M.finish(code) 98 | local r = request 99 | 100 | -- Handle page redirect 101 | local location = r.headers_out['Location'] 102 | if location ~= nil and r.status == 302 then 103 | if location:match('^https?://') then 104 | cgilua.redirect(location) 105 | else 106 | -- CGILua needs a full URL 107 | if r.is_https then 108 | location = 'https://'..cgilua.servervariable("HTTP_HOST")..location 109 | else 110 | location = 'http://'..cgilua.servervariable("HTTP_HOST")..location 111 | end 112 | cgilua.redirect(location) 113 | end 114 | end 115 | 116 | -- add support for custom headers 117 | for header, value in pairs(r.headers_out) do 118 | -- skip Location header. 119 | if not header == "Location" then 120 | cgilua.header(header, value) 121 | end 122 | end 123 | 124 | -- Prints the response text (if any) 125 | if r.content_type == "text/html" then 126 | cgilua.htmlheader() 127 | else 128 | local header_sep = "/" 129 | local header_type = remy.splitstring(r.content_type,header_sep)[1] 130 | local header_subtype = remy.splitstring(r.content_type,header_sep)[2] 131 | cgilua.contentheader(header_type,header_subtype) 132 | end 133 | 134 | if remy.responsetext ~= nil then 135 | cgilua.print(remy.responsetext) 136 | end 137 | end 138 | 139 | return M 140 | -------------------------------------------------------------------------------- /src/remy/file_obj.lua: -------------------------------------------------------------------------------- 1 | -- Remy - File object 2 | -- By m1cr0man 3 | -- License: MIT 4 | 5 | local File = { 6 | name = nil, 7 | path = nil, 8 | content_type = nil, 9 | move_to = nil, 10 | handle = nil 11 | } 12 | 13 | -- Efficiency function for saving the temp file 14 | -- somewhere else instead of reading & writing it 15 | function File:move_to(path) 16 | local success, err = os.rename(self.path, path) 17 | if not success then return success, err end 18 | self.path = path 19 | return true 20 | end 21 | 22 | function File.new(name, content_type) 23 | local new_file = { 24 | name = name, 25 | path = os.tmpname(), 26 | content_type = content_type 27 | } 28 | 29 | -- We will want a handle to write to 30 | -- when we create the file 31 | new_file.handle = io.open(new_file.path, "w") 32 | setmetatable(new_file, {__index = File}) 33 | 34 | return new_file 35 | end 36 | 37 | return File 38 | -------------------------------------------------------------------------------- /src/remy/lwan.lua: -------------------------------------------------------------------------------- 1 | -- Remy - Lwan compatibility 2 | -- Copyright (c) 2014 Leandro Pereira 3 | -- License: MIT 4 | 5 | local base64 = require "base64" 6 | local remy = require "remy" 7 | 8 | local lwan = {} 9 | lwan.query_param_table = function(req) 10 | return setmetatable({}, { 11 | __index = function(tbl, key) 12 | return req.native_request:query_param(key) 13 | end 14 | }) 15 | end 16 | 17 | lwan.post_param_table = function(req) 18 | return setmetatable({}, { 19 | __index = function (tbl, key) 20 | return req.native_request:post_param(key) 21 | end 22 | }) 23 | end 24 | 25 | local request = { 26 | -- ENCODING/DECODING FUNCTIONS 27 | base64_decode = function(_,...) return base64.decode(...) end, 28 | base64_encode = function(_,...) return base64.encode(...) end, 29 | -- REQUEST PARSING FUNCTIONS 30 | parseargs = function(...) return lwan.query_param_table(...), {} end, 31 | parsebody = function(...) return lwan.post_param_table(...), {} end, 32 | requestbody = function(_,...) return nil end, 33 | -- REQUEST RESPONSE FUNCTIONS 34 | puts = function(req, ...) req.native_request:say(...) end, 35 | write = function(req, ...) req.native_request:say(...) end, 36 | } 37 | 38 | local M = { 39 | mode = "lwan", 40 | request = request 41 | } 42 | 43 | function M.init(native_request) 44 | -- FIXME: Most of these constants are hardcoded to possibly wrong values. 45 | local filename = "./index.lua" 46 | local uri = "/index.lua" 47 | apache2.version = M.mode 48 | 49 | local r = request 50 | r = remy.loadrequestrec(r) 51 | 52 | r.native_request = native_request 53 | 54 | r.headers_out = {} 55 | r.headers_in = {} 56 | 57 | r.banner = M.mode 58 | r.server_name = r.hostname 59 | 60 | r.started = 0 61 | r.args = {} 62 | 63 | r.canonical_filename = filename 64 | r.context_document_root = "/" 65 | r.document_root = "/" 66 | r.filename = filename 67 | 68 | r.hostname = "127.0.0.1" 69 | r.useragent_ip = "127.0.0.1" 70 | r.port = "8080" 71 | 72 | r.range = nil 73 | 74 | r.method = "GET" 75 | r.protocol = "HTTP/1.1" 76 | r.the_request = "GET / HTTP/1.1" 77 | 78 | r.unparsed_uri = uri 79 | r.uri = uri 80 | 81 | -- This is handled by Lwan itself and is not exposed to the API 82 | r.user = nil 83 | r.basic_auth_pw = nil 84 | end 85 | 86 | function M.contentheader(content_type) 87 | request.content_type = content_type 88 | end 89 | 90 | function M.finish(code) 91 | request.native_request:set_headers(request.headers_out) 92 | end 93 | 94 | return M 95 | -------------------------------------------------------------------------------- /src/remy/mod_magnet.lua: -------------------------------------------------------------------------------- 1 | -- Remy - Lighttpd's mod_magnet compatibility 2 | -- Copyright (c) 2015 Felipe Daragon 3 | -- License: MIT 4 | 5 | local base64 = require "base64" 6 | local remy = require "remy" 7 | 8 | local utils = {} 9 | 10 | utils.parseargs = function() 11 | local get = {} 12 | if (lighty.env["uri.query"]) then 13 | -- split the query-string 14 | for k, v in string.gmatch(lighty.env["uri.query"], "(%w+)=(.+)") do 15 | get[k] = v 16 | end 17 | end 18 | return get 19 | end 20 | 21 | -- TODO: implement all functions from mod_lua's request_rec 22 | local request = { 23 | -- ENCODING/DECODING FUNCTIONS 24 | base64_decode = function(_,...) return base64.decode(...) end, 25 | base64_encode = function(_,...) return base64.encode(...) end, 26 | sha1 = function(_,...) return remy.sha1(...) end, 27 | -- REQUEST PARSING FUNCTIONS 28 | parseargs = function(_) return utils.parseargs(), {} end, 29 | parsebody = function(_) return {}, {} end, 30 | -- REQUEST RESPONSE FUNCTIONS 31 | puts = function(_,...) remy.print(...) end, 32 | write = function(_,...) remy.print(...) end 33 | } 34 | 35 | local M = { 36 | mode = "lighttpd", 37 | request = request 38 | } 39 | 40 | function M.init() 41 | local r = request 42 | local filename = lighty.env["physical.path"] 43 | local uri = lighty.env["uri.path"] 44 | apache2.version = M.mode 45 | r = remy.loadrequestrec(r) 46 | r.headers_out = lighty.header 47 | r.headers_in = lighty.request 48 | local auth = base64.decode((r.headers_in["Authorization"] or ""):sub(7)) 49 | local _,_,user,pass = auth:find("([^:]+)%:([^:]+)") 50 | r.method = lighty.env["request.method"] 51 | r.args = lighty.env["uri.query"] 52 | --r.args = remy.splitstring(lighty.env['request.uri'],'?') 53 | r.banner = M.mode 54 | r.basic_auth_pw = pass 55 | r.canonical_filename = filename 56 | r.context_document_root = lighty.env["physical.doc-root"] 57 | r.document_root = lighty.env["physical.doc-root"] 58 | r.filename = filename 59 | r.hostname = lighty.env["uri.authority"] 60 | --r.port = ??? 61 | r.protocol = lighty.env["request.protocol"] 62 | r.range = r.headers_in["Range"] 63 | r.server_name = r.hostname 64 | r.the_request = r.method.." "..lighty.env["request.uri"].." "..r.protocol 65 | r.unparsed_uri = lighty.env["request.orig-uri"] 66 | r.uri = uri 67 | r.user = user 68 | r.useragent_ip = lighty.env['request.remote-ip'] 69 | end 70 | 71 | function M.contentheader(content_type) 72 | request.content_type = content_type 73 | lighty.header["Content-Type"] = content_type 74 | end 75 | 76 | function M.finish(code) 77 | lighty.content = { remy.responsetext } 78 | end 79 | 80 | return M 81 | -------------------------------------------------------------------------------- /src/remy/mod_plua.lua: -------------------------------------------------------------------------------- 1 | -- Remy - mod_pLua compatibility 2 | -- Copyright (c) 2014 Felipe Daragon 3 | -- License: MIT 4 | 5 | local remy = require "remy" 6 | 7 | -- TODO: implement all functions from mod_lua's request_rec 8 | local request = { 9 | -- ENCODING/DECODING FUNCTIONS 10 | base64_decode = function(_,...) return string.decode64(...) end, 11 | base64_encode = function(_,...) return string.encode64(...) end, 12 | md5 = function(_,...) return string.md5(...) end, 13 | -- REQUEST PARSING FUNCTIONS 14 | parseargs = function(_) return parseGet(), {} end, 15 | parsebody = function(_) return parsePost(), {} end, 16 | requestbody = function(_,...) return getRequestBody(...) end, 17 | -- REQUEST RESPONSE FUNCTIONS 18 | sendfile = function(_,...) return file.send(...) end, 19 | puts = function(_,...) echo(...) end, 20 | write = function(_,...) echo(...) end 21 | } 22 | 23 | local M = { 24 | mode = "mod_plua", 25 | request = request 26 | } 27 | 28 | function M.init() 29 | local env = getEnv() 30 | local r = request 31 | local auth = string.decode64((env["Authorization"] or ""):sub(7)) 32 | local _,_,user,pass = auth:find("([^:]+)%:([^:]+)") 33 | local filename = env["Filename"] 34 | apache2.version = env["Server-Banner"] 35 | r = remy.loadrequestrec(r) 36 | r.method = env["Request-Method"] 37 | r.args = remy.splitstring(env["Unparsed-URI"],'?') 38 | r.banner = apache2.version 39 | r.basic_auth_pw = pass 40 | r.canonical_filename = filename 41 | r.context_document_root = env["Working-Directory"] 42 | r.document_root = r.context_document_root 43 | r.filename = filename 44 | r.hostname = env["Host"] 45 | r.path_info = env["Path-Info"] 46 | r.range = env["Range"] 47 | r.server_name = r.hostname 48 | r.the_request = env["Request"] 49 | r.unparsed_uri = env["Unparsed-URI"] 50 | r.uri = env["URI"] 51 | r.user = user 52 | r.useragent_ip = env["Remote-Address"] 53 | end 54 | 55 | function M.contentheader(content_type) 56 | request.content_type = content_type 57 | setContentType(content_type) 58 | end 59 | 60 | function M.finish(code) 61 | -- mod_pLua uses text/html as default content type 62 | if request.content_type ~= nil then 63 | setContentType(request.content_type) 64 | end 65 | setReturnCode(code) 66 | end 67 | 68 | return M 69 | -------------------------------------------------------------------------------- /src/remy/nginx.lua: -------------------------------------------------------------------------------- 1 | -- Remy - Nginx compatibility 2 | -- Copyright (c) 2014 Felipe Daragon 3 | -- License: MIT 4 | 5 | local ngx = require "ngx" 6 | local remy = require "remy" 7 | local file = require "remy.file_obj" 8 | local output_buffer = {} 9 | local files = {} 10 | 11 | -- Settings for streaming multipart/form-data 12 | local chunk_size = 16384 -- Bytes 13 | local socket_timeout = 5000 -- Milliseconds 14 | 15 | -- Buffer output to allow changing the header up until M.finish is called 16 | -- See https://github.com/openresty/lua-nginx-module#ngxheaderheader 17 | local function buffered_print(_, ...) 18 | table.insert(output_buffer, {...}) 19 | end 20 | 21 | -- Manually parses the request body to handle files 22 | -- Buffers any files to disk 23 | local function load_req_body() 24 | 25 | -- Nginx lower cases all the keys 26 | local header = ngx.req.get_headers() 27 | 28 | -- Check the header for a boundary 29 | local boundary = (header["content-type"] or ""):match("boundary=(.+)$") 30 | 31 | -- If there's no boundary, don't worry about using a socket 32 | -- Parse the body the normal way 33 | if not boundary then 34 | ngx.req.read_body() 35 | return ngx.req.get_post_args(), {} 36 | end 37 | 38 | -- Otherwise try to open a socket 39 | local socket, err = ngx.req.socket() 40 | if not socket then 41 | ngx.say("Failed to open socket: ", err) 42 | ngx.exit(500) 43 | end 44 | socket:settimeout(socket_timeout) 45 | 46 | -- CRLF is the standard for HTTP requests 47 | local stream, err = socket:receiveuntil("\r\n--" .. boundary) 48 | if not stream then 49 | ngx.say("Failed to open stream: ", err) 50 | ngx.exit(500) 51 | end 52 | 53 | local POST = {} 54 | 55 | -- Read the data 56 | while true do 57 | 58 | -- Read a line 59 | local header = socket:receive() 60 | if not header then 61 | break 62 | end 63 | local key = header:match("name=\"(.-)\"") 64 | 65 | -- If this is just a boundary, it will ignore it 66 | if key then 67 | local value = "" 68 | 69 | -- Check if it's a file 70 | local filename = header:match("filename=\"(.-)\"") 71 | if filename then 72 | local content_type = socket:receive():match("Content%-Type: ?(.+)$") 73 | 74 | -- Build the file object 75 | value = file.new(filename, content_type) 76 | end 77 | 78 | -- Stream the data, skipping the blank line 79 | socket:receive() 80 | while true do 81 | local data, err = stream(chunk_size) 82 | if err then 83 | ngx.say("Failed to read data stream: ", err) 84 | ngx.exit(500) 85 | elseif not data then 86 | break 87 | end 88 | 89 | -- Write to file, or save to variable 90 | if type(value) == "table" then 91 | 92 | -- Convert the line endings respective to host OS 93 | value.handle:write(value.path:match("/") and data:gsub("\r\n", "\n") or data) 94 | else 95 | value = value .. data 96 | end 97 | end 98 | 99 | -- Close the file 100 | if type(value) == "table" then 101 | value.handle:close() 102 | value.handle = nil 103 | end 104 | 105 | -- Append POST & files list if necessary 106 | -- Mimics behaviour of ngx.req.get_post_args() 107 | if POST[key] and (type(POST[key]) ~= "table" or POST[key].name) then 108 | POST[key] = {POST[key], value} 109 | elseif type(POST[key]) == "table" then 110 | table.insert(POST[key], value) 111 | else 112 | POST[key] = value 113 | end 114 | if type(value) == "table" then table.insert(files, value) end 115 | end 116 | end 117 | 118 | return POST, {} 119 | end 120 | 121 | -- TODO: implement all functions from mod_lua's request_rec 122 | local request = { 123 | -- ENCODING/DECODING FUNCTIONS 124 | base64_decode = function(_,...) return ngx.decode_base64(...) end, 125 | base64_encode = function(_,...) return ngx.encode_base64(...) end, 126 | escape = function(_,...) return ngx.escape_uri(...) end, 127 | unescape = function(_,...) return ngx.unescape_uri(...) end, 128 | md5 = function(_,...) return ngx.md5(...) end, 129 | -- REQUEST PARSING FUNCTIONS 130 | parseargs = function(_) return ngx.req.get_uri_args(), {} end, 131 | parsebody = load_req_body, 132 | requestbody = function(_,...) 133 | -- Make sure the request body has been read 134 | ngx.req.read_body() 135 | return ngx.req.get_body_data() 136 | end, 137 | -- REQUEST RESPONSE FUNCTIONS 138 | puts = buffered_print, 139 | write = buffered_print 140 | } 141 | 142 | local M = { 143 | mode = "nginx", 144 | request = request 145 | } 146 | 147 | function M.init() 148 | local r = request 149 | local filename = debug.getinfo(4).source 150 | local uri = ngx.var.uri 151 | apache2.version = M.mode.."/"..ngx.var.nginx_version 152 | r = remy.loadrequestrec(r) 153 | r.headers_out = ngx.resp.get_headers() 154 | r.headers_in = ngx.req.get_headers() 155 | local auth = ngx.decode_base64((r.headers_in["Authorization"] or ""):sub(7)) 156 | local _,_,user,pass = auth:find("([^:]+)%:([^:]+)") 157 | r.started = ngx.req.start_time 158 | r.method = ngx.var.request_method 159 | r.args = remy.splitstring(ngx.var.request_uri,'?') 160 | r.banner = M.mode.."/"..ngx.var.nginx_version 161 | r.basic_auth_pw = pass 162 | r.canonical_filename = filename 163 | r.context_document_root = ngx.var.document_root 164 | r.document_root = r.context_document_root 165 | r.filename = filename 166 | r.hostname = ngx.var.hostname 167 | r.port = ngx.var.server_port 168 | r.protocol = ngx.var.server_protocol 169 | r.range = r.headers_in["Range"] 170 | r.server_name = r.hostname 171 | r.the_request = r.method.." "..ngx.var.request_uri.." "..r.protocol 172 | r.unparsed_uri = uri 173 | r.uri = uri 174 | r.user = user 175 | r.useragent_ip = ngx.var.remote_addr 176 | end 177 | 178 | function M.contentheader(content_type) 179 | request.content_type = content_type 180 | ngx.header.content_type = content_type 181 | end 182 | 183 | function M.finish(code) 184 | -- Set status code 185 | ngx.status = code 186 | 187 | -- Set the headers 188 | if request.content_type and not ngx.header.content_type then 189 | ngx.header["Content-Type"] = request.content_type 190 | end 191 | for k, v in pairs(request.headers_out) do 192 | ngx.header[k] = v 193 | end 194 | 195 | -- Print the data & Clear the buffer 196 | ngx.print(output_buffer) 197 | output_buffer = {} 198 | 199 | -- Delete temporary files 200 | for _, file in pairs(files) do 201 | os.remove(file.path) 202 | end 203 | files = {} 204 | 205 | -- TODO: translate request_rec's exit code and call ngx.exit(code) 206 | end 207 | 208 | return M 209 | --------------------------------------------------------------------------------