diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9ae2650 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +# Nginx/Lua on AlmaLinux + +FROM fabiocicerchia/nginx-lua:1.23.2-almalinux8.7-20221201 + +# The luajit package expects Lua 5.1, but this container has 5.3 so remove that. +# Then build Lua 5.1 and luarocks from source so you can install the luafilesystem module +RUN dnf remove -y lua \ + && dnf install -y cmake luajit readline-devel \ + && cd /tmp \ + && curl https://www.lua.org/ftp/lua-5.1.5.tar.gz -L -o lua-5.1.5.tar.gz \ + && tar -zxvf lua-5.1.5.tar.gz \ + && cd lua-5.1.5 \ + && CFLAGS=-DLUA_USE_LINUX make linux \ + && make install \ + && cd .. \ + && curl https://luarocks.org/releases/luarocks-3.8.0.tar.gz -L -o luarocks-3.8.0.tar.gz \ + && tar -zxvf luarocks-3.8.0.tar.gz \ + && cd luarocks-3.8.0 \ + && ./configure --with-lua-include=/usr/local/include \ + && make install \ + && cd .. \ + && luarocks install luafilesystem + +# Build the EmmyLuaDebugger from source for debugging Lua via IntelliJ IDEA +RUN curl https://github.com/EmmyLua/EmmyLuaDebugger/archive/refs/tags/1.0.16.tar.gz \ + -L -o EmmyLuaDebugger-1.0.16.tar.gz && \ + tar -xzvf EmmyLuaDebugger-1.0.16.tar.gz && \ + cd EmmyLuaDebugger-1.0.16 && \ + mkdir -p build && \ + cd build && \ + cmake -DCMAKE_BUILD_TYPE=Release ../ && \ + make install && \ + mkdir -p /usr/local/emmy && \ + cp install/bin/emmy_core.so /usr/local/emmy/ && \ + cd .. && \ + cd .. + +# Set the lua_shared_dict, set the nginx root to ./web and load the `htaccess.lua` script (without caching) +RUN sed -i "s@http {@http {\n lua_shared_dict htaccess 16m;\n@g" /etc/nginx/nginx.conf \ + && sed -i "s@root /usr/share/nginx/html;@root /docker/web;@g" /etc/nginx/conf.d/default.conf \ + && sed -i "s@server {@\nserver {\n lua_code_cache off;\n rewrite_by_lua_file /docker/htaccess.lua;\n@g" /etc/nginx/conf.d/default.conf \ No newline at end of file diff --git a/README.md b/README.md index 3267ad6..0214c0b 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,31 @@ Physical memory usage of this plugin is insanely low, under 10 KB for each nginx ## Installation -1. Install nginx with the [Lua module](https://github.com/openresty/lua-nginx-module) `libnginx-mod-http-lua` and the `luajit` package. - 1. Debian: `apt-get install nginx libnginx-mod-http-lua luajit` - 2. Fedora: `yum install nginx libnginx-mod-http-lua luajit` - +### Docker container + +A Docker container is included as a reference; simply start it via `docker compose up -d`. + +If you navigate to http://localhost it will process the sample `web/.htaccess`, which has a RewriteRule mapping http://localhost/go/ to http://localhost/redirect.html. + + +#### Remote debugging + +Using IntelliJ IDEA, you can remotely debug Lua scripts running in an nginx Docker container, using [these steps](https://dev.to/omervk/debugging-lua-inside-openresty-inside-docker-with-intellij-idea-2h95). In particular, this has been tested on a Windows 10 host running IntelliJ IDEA 2022.2.4 (Community Edition). + +It assumes you are mapping a host path of `C:\path\to\project\on\windows` to a path in the container volume of `/docker`. + +1. Install [IntelliJ IDEA](https://www.jetbrains.com/idea/download/#section=windows) +1. At the top of `htaccess.lua`, uncomment the debugging block and update the Windows path mapping. +1. In IDEA, create a Run/Debug configuration per the link above and then start the debugger + +When you request a URL that triggers the Lua script, it will pause on the `dbg.breakHere()` line so you can step through the code, watch variables, etc. + +### Manual installation + +1. Install nginx with the [Lua module](https://github.com/openresty/lua-nginx-module) `libnginx-mod-http-lua` and the `luajit` and `luarocks` packages. + 1. Debian: `apt-get install nginx libnginx-mod-http-lua luajit luarocks` + 2. Fedora: `dnf install nginx libnginx-mod-http-lua luajit luarocks` +1. Install the [LuaFileSystem](https://lunarmodules.github.io/luafilesystem/) module via `luarocks install luafilesystem` 1. Verify that the Lua module is properly installed by running: ```bash nginx -V 2>&1 | tr ' ' '\n' | grep lua @@ -67,6 +88,9 @@ Physical memory usage of this plugin is insanely low, under 10 KB for each nginx http { ... lua_shared_dict htaccess 16m; + + # This is useful for debugging but may cause performance problems + # lua_code_cache off; ... } ``` @@ -242,7 +266,7 @@ mod_negotiation | `ForceLanguagePriority` | No | mod_negotiation | `LanguagePriority` | No | mod_reflector | `*` | Never | Security reasons mod_rewrite | `RewriteBase` | Yes | -mod_rewrite | `RewriteCond` | Partial | Environment (E=) flag is unsupported, as are *CondPattern* integer comparisons and some file attribute tests listed in the [documentation](https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html) +mod_rewrite | `RewriteCond` | Partial | Environment (E=) flag is unsupported, as are *CondPattern* integer comparisons and some file attribute tests listed in the [documentation](https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html). mod_rewrite | `RewriteEngine` | Yes | mod_rewrite | `RewriteOptions` | No | mod_rewrite | `RewriteRule` | Yes | @@ -320,65 +344,4 @@ Variables not listed below are not supported. include snippets/htaccess.conf ... } - ``` - - -## Debugging Lua inside a Docker container - -Using IntelliJ IDEA, you can remotely debug Lua scripts like `htaccess.lua` running in an nginx Docker container, using [these steps](https://dev.to/omervk/debugging-lua-inside-openresty-inside-docker-with-intellij-idea-2h95). In particular, this has been tested on a Windows 10 host running IntelliJ IDEA 2022.2.4 (Community Edition), with the `fabiocicerchia/nginx-lua:1.23.2-almalinux8.7-20221201` Docker image from https://hub.docker.com/r/fabiocicerchia/nginx-lua. - -It assumes you are mapping a host path of `C:\path\to\project\on\windows` to a path in the container volume of `/path/to/project`. - -1. Install [IntelliJ IDEA](https://www.jetbrains.com/idea/download/#section=windows) -1. The container needs to forward port 9966 to allow for Lua debugging. If you are using `docker-compose.yml` it will look something like this: - ```yml - version: '2.4' - services: - nginx-lua: - build: - context: . - container_name: nginx-lua - ports: - # For nginx requests over HTTP - - 80:80 - # If you support nginx requests over HTTPS - - 443:443 - # For Lua debugging - - 9966:9966 - volumes: - - ./relative/path/to/project/on/windows/:/path/to/project/ - ``` -1. In the `Dockerfile` for the container, run the following command to build the EmmyLuaDebugger from source: - ```bash - RUN dnf install -y cmake && \ - curl https://github.com/EmmyLua/EmmyLuaDebugger/archive/refs/tags/1.0.16.tar.gz \ - -L -o EmmyLuaDebugger-1.0.16.tar.gz && \ - tar -xzvf EmmyLuaDebugger-1.0.16.tar.gz && \ - cd EmmyLuaDebugger-1.0.16 && \ - mkdir -p build && \ - cd build && \ - cmake -DCMAKE_BUILD_TYPE=Release ../ && \ - make install && \ - mkdir -p /usr/local/emmy && \ - cp install/bin/emmy_core.so /usr/local/emmy/ && \ - cd .. && \ - cd .. && \ - rm -rf EmmyLuaDebugger-1.0.16 EmmyLuaDebugger-1.0.16.tar.gz - ``` -1. Start the container -1. At the top of your Lua script to debug, add the following: - ```lua - _G.emmy = {} - _G.emmy.fixPath = function(path) - return string.gsub(path, '/path/to/project/', 'C:/path/to/project/on/windows') - end - - package.cpath = package.cpath .. ';/usr/local/emmy/?.so' - local dbg = require('emmy_core') - dbg.tcpListen('localhost', 9966) - dbg.waitIDE() - dbg.breakHere() - ``` -1. In IDEA, create a Run/Debug configuration per the link and then start the debugger - -When you request a URL that triggers the Lua script, it will pause on the `dbg.breakHere()` line so you can step through the code, watch variables, etc. + ``` \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..170ad62 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: '2.4' +services: + nginx-lua: + build: + context: . + ports: + # For nginx requests over HTTP + - 80:80 + # For Lua debugging + - 9966:9966 + volumes: + - ./:/docker/ + # Edit for your environment + #- ./relative/path/to/project/on/windows/:/path/to/project/ \ No newline at end of file diff --git a/htaccess.lua b/htaccess.lua index fbbbc16..998314c 100644 --- a/htaccess.lua +++ b/htaccess.lua @@ -6,6 +6,19 @@ -- TODO: Sometimes code is executed 4 times for each request due to the way nginx handles requests. Make sure it is cached accordingly. +-- Uncomment the following to enable remote debugging +-- Note that if the container volume path contains dashes, they will need to be escaped - e.g., /path/to/htaccess%-for%-nginx +-- _G.emmy = {} +-- _G.emmy.fixPath = function(path) +-- return path:gsub('/docker/', 'C:/path/to/project/on/windows') +-- end + +-- package.cpath = package.cpath .. ';/usr/local/emmy/?.so' +-- local dbg = require('emmy_core') +-- dbg.tcpListen('localhost', 9966) +-- dbg.waitIDE() +-- dbg.breakHere() + -- Error function, returns HTTP 500 and logs an error message local fail = function(msg) if msg then @@ -137,6 +150,27 @@ local path_exists = function(filepath, soft_fail) return ok end +-- Get the type of a file system object +-- @param filepath .... the filename +-- @return file_type .. One of (directory|link|file), or nil if the path is invalid +local get_file_type = function(filepath) + local lfs = require "lfs" + local file_type = nil + if (lfs.symlinkattributes (filepath) ~= nil) then + local attr = lfs.symlinkattributes (filepath); + assert (type(attr) == "table") + if attr.mode == "directory" then + file_type = 'directory' + elseif attr['target'] ~= nil then + -- print ("*** symlink found "..attr['target']) + file_type = 'link' + else + file_type = 'file' + end + end + return file_type +end + -- Read contents of any file local get_file_contents = function(name) ensure_doc_root(name) -- Security: enforce document root @@ -770,7 +804,7 @@ local replace_server_vars = function(str, track_used_headers) replace = os.date('%w') end elseif whitelist[svar] then - replace = ngx.var[svar] + replace = ngx.var[svar] or '' elseif svar == 'request_uri' then -- %{REQUEST_URI} -- Use ngx.var['uri'] to match the Apache convention since it doesn't contain the query string replace = ngx.var['uri'] @@ -1088,10 +1122,16 @@ if get_cdir('rewrite') and #parsed_rewriterules > 0 then fail('RewriteCond expressions ("expr ...") are unsupported') -- We don't support expr style conditions due to their weird complexity and redundancy elseif cond_pattern:sub(1,1) == '-' then -- File attribute tests or integer comparisons (case sensitive) local filepath = cond_test:gsub('/$','',1) + local file_type = get_file_type(filepath) + + cond_matches = false + if cond_pattern == '-d' then -- is directory - cond_matches = path_exists(filepath..'/') + cond_matches = file_type == 'directory' elseif cond_pattern == '-f' or cond_pattern == '-F' then -- is file - cond_matches = path_exists(filepath) and not path_exists(filepath..'/') + cond_matches = file_type == 'file' + elseif cond_pattern == '-l' or cond_pattern == '-L' then -- is symlink + cond_matches = file_type == 'link' else fail('RewriteCond pattern unsupported: '..cond_pattern) end diff --git a/web/.htaccess b/web/.htaccess new file mode 100644 index 0000000..103ab41 --- /dev/null +++ b/web/.htaccess @@ -0,0 +1,7 @@ +# This is a sample .htaccess file + + +RewriteEngine On +RewriteBase / +RewriteRule ^go /redirect.html [L] + diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..bafafe7 --- /dev/null +++ b/web/index.html @@ -0,0 +1,5 @@ + + + This is the htaccess-for-nginx webroot + + \ No newline at end of file diff --git a/web/redirect.html b/web/redirect.html new file mode 100644 index 0000000..facaea7 --- /dev/null +++ b/web/redirect.html @@ -0,0 +1,5 @@ + + + This is redirect.html + + \ No newline at end of file