Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Integrating Lua Scripts into OpenResty: A Comprehensive Guide

Tech 2

Introduction to Lua Integration in OpenResty

OpenResty provides multiple mechanisms for embedding Lua code directly into the Nginx configuration. This article explores the various methods available for incorporating Lua logic into your OpenResty applications.


Methods for Embedding Lua in OpenResty

Direct String Execution with content_by_lua

The most straightforward approach uses inline Lua code as a string parameter:

worker_processes 1;

events {
    worker_connections 1024;
}

http {
    include       mime.types;
    default_type  text/html;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       8080;
        server_name  localhost;

        location /hello {
            content_by_lua 'ngx.say("greetings from lua")';
        }
    }
}

While simple, this approach becomes unwieldy for complex logic due to string escaping requirements.

Code Block Execution with content_by_lua_block

For more readable inline Lua code, the block syntax eliminates string quoting issues:

location /demo {
    content_by_lua_block {
        local message = "execution phase test"
        ngx.say("Result: " .. message)
    }
}

External File Loading with content_by_lua_file

For substantial Lua codebases, external files provide better maintainability:

location /script {
    content_by_lua_file /opt/app/lua handlers/main.lua;
}

Create the Lua file at the specified path:

-- handlers/main.lua
ngx.say("external script loaded successfully")

Live Reload Configuration

By default, Nginx caches compiled Lua code. During development, disable caching to reflect changes immediately:

http {
    lua_code_cache off;
    
    server {
        listen 8080;
        # ... configuration
    }
}

Important: Always enable lua_code_cache on in production environments. Disabling caching significantly impacts performance.

Lua Module Path Configuration

The lua_package_path directive specifies directories where OpenResty searches for Lua modules:

http {
    lua_package_path "/opt/app/lua/?.lua;/lib/resty/?.lua;;";
}

The double semicolon ;; represents the default module path.


Core Output Operations

Response Header and Body Management

location /output-demo {
    content_by_lua_block {
        -- Set custom response headers
        ngx.header.content_type = "application/json"
        ngx.header.x_request_id = "req-abc123"
        
        -- Output content (with newline)
        ngx.say("First line")
        ngx.say("Second line")
        
        -- Output content (without newline)
        ngx.print("No newline here")
        ngx.print(" - continuing")
        
        -- Terminate with specific status
        return ngx.exit(ngx.HTTP_OK)
    }
}

Key functions:

  • ngx.header: Manipulate response headers
  • ngx.print(): Output response body
  • ngx.say(): Output response body with trailing newline
  • ngx.exit(): Terminate request with specified status code

Accessing Nginx Variables

Built-in Nginx Variables

OpenResty exposes numerous Nginx variables through ngx.var:

Variable Description
$remote_addr Client IP address
$remote_port Client port number
$request_method HTTP method (GET, POST, etc.)
$query_string URL query parameters
$uri Request URI path
$request_uri Full request URI with parameters
$http_user_agent Client browser identification
$http_cookie Cookie data
$server_addr Server IP address
$server_port Server port number

Retrieving Variables via ngx.var

location /variable-test {
    set $custom_param "100";
    
    content_by_lua_block {
        -- Parse query parameter 'value'
        local incoming = tonumber(ngx.var.arg_value) or 0
        
        -- Access custom Nginx variable
        local configured = tonumber(ngx.var.custom_param) or 0
        
        local result = incoming + configured
        ngx.say("Calculated: " .. result)
        
        -- Access server information
        ngx.say("Server Address: " .. ngx.var.server_addr)
    }
}

Extracting Captured Groups from Regex Locations

When using regex in location blocks, capture groups become accessible:

location ~ ^/api/user/(\d+)/profile$ {
    content_by_lua_block {
        local user_id = ngx.var[1]
        ngx.say("User ID extracted: " .. user_id)
    }
}

Accessing /api/user/5421/profile would yield "User ID extracted: 5421".


Request Processing APIs

Reading Request Headers

-- main.lua
local request_headers = ngx.req.get_headers()

ngx.say("=== Request Headers ===", "<br/>")
ngx.say("Host: ", request_headers["Host"], "<br/>")
ngx.say("User-Agent: ", request_headers.user_agent, "<br/>")

-- Iterate through all headers
for header_name, header_value in pairs(request_headers) do
    if type(header_value) == "table" then
        ngx.say(header_name .. ": " .. table.concat(header_value, ", "), "<br/>")
    else
        ngx.say(header_name .. ": " .. header_value, "<br/>")
    end
end

Processing Query Parameters

-- Extract GET parameters
local uri_params = ngx.req.get_uri_args()

ngx.say("=== URI Parameters ===", "<br/>")
for param_key, param_value in pairs(uri_params) do
    if type(param_value) == "table" then
        ngx.say(param_key .. ": [" .. table.concat(param_value, ", ") .. "]", "<br/>")
    else
        ngx.say(param_key .. ": " .. param_value, "<br/>")
    end
end

Processing POST Request Body

-- Must read body before accessing POST data
ngx.req.read_body()

local post_data = ngx.req.get_post_args()

ngx.say("=== POST Data ===", "<br/>")
for field_name, field_value in pairs(post_data) do
    if type(field_value) == "table" then
        ngx.say(field_name .. ": [" .. table.concat(field_value, ", ") .. "]", "<br/>")
    else
        ngx.say(field_name .. ": " .. field_value, "<br/>")
    end
end

-- Alternatively, get raw body string
local raw_body = ngx.req.get_body_data()

Comparison: ngx.var.arg vs ngx.req.get_uri_args

Both methods retrieve URL parameters, but with key differences:

-- URL: /compare?item=first&item=second&item=third

-- Returns first value only
local single = ngx.var.arg_item  -- "first"

-- Returns table containing all values
local multiple = ngx.req.get_uri_args()["item"]  -- {"first", "second", "third"}

Encoding and Decoding Utilities

URI Encoding/Decoding

local original_uri = ngx.var.request_uri

ngx.say("Original: ", original_uri, "<br/>")

local encoded = ngx.escape_uri(original_uri)
ngx.say("Encoded: ", encoded, "<br/>")

local decoded = ngx.unescape_uri(encoded)
ngx.say("Decoded: ", decoded, "<br/>")

Parameter Table Encoding

local request_uri = ngx.var.request_uri
local question_index = string.find(request_uri, '?')

if question_index then
    local query_string = string.sub(request_uri, question_index + 1)
    local decoded_params = ngx.decode_args(query_string)
    
    for key, value in pairs(decoded_params) do
        ngx.say(key .. " = " .. value, "<br/>")
    end
    
    -- Modify and re-encode parameters
    if decoded_params.userId then
        local modified_id = tonumber(decoded_params.userId) + 5000
        decoded_params.userId = modified_id
        ngx.say("Modified: " .. ngx.encode_args(decoded_params), "<br/>")
    end
end

Base64 Operasions

local input = "sensitive data here"
local encoded = ngx.encode_base64(input)
local decoded = ngx.decode_base64(encoded)

Cryptographic Functions

-- MD5 hash (hexadecimal output)
local hash_result = ngx.md5("password123")
ngx.say("MD5: ", hash_result, "<br/>")

-- Binary MD5
local bin_hash = ngx.md5_bin("password123")

-- SHA1 binary
local sha1_result = ngx.sha1_bin("content")

Time APIs

OpenResty provides high-performance time retrieval functions that use Nginx's internal cache:

location /time-demo {
    content_by_lua_block {
        -- Second-level precision timestamp
        local current_seconds = ngx.time()
        ngx.say("Unix timestamp: ", current_seconds, "<br/>")
        
        -- Millisecond-level precision
        local current_ms = ngx.now()
        ngx.say("Precise timestamp: ", current_ms, "<br/>")
        
        -- Demonstrate cached time behavior
        local start = ngx.now()
        -- Simulate processing delay
        for i = 1, 500000 do
            local _ = math.random()
        local finish = ngx.now()
        
        ngx.say("Start: ", start, " | Finish: ", finish, "<br/>")
        
        -- Force cache refresh
        ngx.update_time()
        local refreshed = ngx.now()
        ngx.say("After refresh: ", refreshed)
    }
}

Note: ngx.now() returns cached timestamps for performance. Use ngx.update_time() to refresh the cache when needed.


Regular Expression Operations

Pattern Matching with ngx.re.match

local test_string = "Product ID: 98765"

-- Simple numeric extraction
local match_result, match_error = ngx.re.match(test_string, "[0-9]+")

if match_result then
    ngx.say("Full match: ", match_result[0], "<br/>")
else
    if match_error then
        ngx.log(ngx.ERR, "Match error: ", match_error)
    end
    ngx.say("No match found")
end

-- Using capture groups
local capture_result = ngx.re.match("Price: $199.99", "(\$\\d+\\.\\d+)")
if capture_result then
    ngx.say("Complete match: ", capture_result[0], "<br/>")  -- $199.99
    ngx.say("Group 1: ", capture_result[1], "<br/>")           -- $199.99
end

Other regex functions:

  • ngx.re.sub(): Replace first occurrence
  • ngx.re.gsub(): Replace all occurrences
  • ngx.re.find(): Find position without extraction
  • ngx.re.gmatch(): Iterate over matches

Logging System

Writing to Error Log

error_log logs/application.log info;

location /log-demo {
    content_by_lua_block {
        local numeric_value = 42
        local text_value = "configuration"
        local nil_object = nil
        
        -- Different log levels
        ngx.log(ngx.ERR, "Error occurred with value: ", numeric_value)
        ngx.log(ngx.WARN, "Warning: unexpected input")
        ngx.log(ngx.INFO, "Processing: ", text_value)
        
        -- Using print (maps to NOTICE level)
        print("Debug output via print function")
        
        ngx.log(ngx.ERR, "Nil reference: ", tostring(nil_object))
        
        return ngx.exit(ngx.HTTP_OK)
    }
}

Log level hierarchy (ascending severity):

  • DEBUG < INFO < NOTICE < WARN < ERR < CRIT < ALERT < EMERG

Request Redirection

Performing Redirects

location = /original-path {
    content_by_lua_block {
        ngx.say("This is the target location")
    }
}

location = /redirect-source {
    rewrite_by_lua_block {
        return ngx.redirect("/original-path")
    end
}

-- External redirect example
location = /external {
    rewrite_by_lua_block {
        return ngx.redirect("https://example.com/new-location", ngx.HTTP_MOVED_TEMPORARILY)
    end
}

Cross-Phase Data Sharing with ngx.ctx

Request-Scoped Context Table

The ngx.ctx table maintains data throughout a single request's lifecycle across different Nginx phases:

location /context-demo {
    set $stage_marker "initial";
    
    rewrite_by_lua_block {
        ngx.ctx.request_data = {
            stage = "rewrite",
            sequence = 1,
            tracking_id = "req-" .. ngx.var.request_id
        }
        ngx.ctx.compute_value = 100
    }
    
    access_by_lua_block {
        -- Access data from rewrite phase
        ngx.ctx.compute_value = ngx.ctx.compute_value + 50
        
        ngx.ctx.request_data.stage = "access"
        ngx.ctx.request_data.sequence = 2
    }
    
    content_by_lua_block {
        ngx.say("Stage: ", ngx.ctx.request_data.stage, "<br/>")
        ngx.say("Sequence: ", ngx.ctx.request_data.sequence, "<br/>")
        ngx.say("Tracking: ", ngx.ctx.request_data.tracking_id, "<br/>")
        ngx.say("Computed: ", ngx.ctx.compute_value)
    }
}

Subrequest Context Isolation

Each request and subrequest maintains its own ngx.ctx table:

location = /sub-context {
    content_by_lua_block {
        ngx.say("Sub - Before: ", ngx.ctx.shared_value)
        ngx.ctx.shared_value = 999
        ngx.say("Sub - After: ", ngx.ctx.shared_value)
    }
}

location = /main-context {
    content_by_lua_block {
        ngx.ctx.shared_value = 111
        ngx.say("Main - Before subrequest: ", ngx.ctx.shared_value)
        
        local subresponse = ngx.location.capture("/sub-context")
        ngx.print(subresponse.body)
        
        ngx.say("Main - After subrequest: ", ngx.ctx.shared_value)
    end
}

Output demonstrates isolation:

Main - Before subrequest: 111
Sub - Before: nil
Sub - After: 999
Main - After subrequest: 111

Performance Note: ngx.ctx uses metatable operations, making it slower than passing data through function arguments. Use judiciously in performance-critical paths.


Additional Nginx Lua APIs Reference

Core Directive Aliases

Directive Alias Function
ngx.arg[n] Access directive parameters
ngx.var.VARIABLE_NAME Reference Nginx variables
ngx.ctx Request-scoped context table
ngx.header.HEADER_NAME Manipulate response headers
ngx.status Get/set response status code

Response Handling

ngx.send_headers()           -- Explicitly send headers
ngx.headers_sent             -- Check if headers sent
ngx.flush(true)              -- Flush output buffer
ngx.eof()                    -- Signal end of output

Request Information

local http_version = ngx.req.http_version()        -- e.g., 1.1
local method = ngx.req.get_method()                -- GET, POST, etc.
local raw_headers = ngx.req.raw_header()           -- Full header string
local start_time = ngx.req.start_time()            -- Request start timestamp

URL Manipulation

ngx.req.set_uri("/new/path")                      -- Rewrite URI
ngx.req.set_uri_args("a=1&b=2")                   -- Set query string
ngx.req.set_method(ngx.HTTP_POST)                  -- Override method

Hash Functions

local crc = ngx.crc32_short("data")               -- CRC32 hash
local hmac = ngx.hmac_sha1("key", "data")        -- HMAC-SHA1

Date/Time Formattting

local current_date = ngx.today()                   -- YYYY-MM-DD
local timestamp = ngx.time()                       -- Unix timestamp
local formatted = ngx.cookie_time(os.time())       -- Cookie-compatible format
local http_date = ngx.http_time(os.time())         -- HTTP header format
local parsed = ngx.parse_http_time("Thu, 01 Jan 1970")  -- Parse HTTP date

SQL Escaping

local safe = ngx.quote_sql_str("O'Brien")         -- Escapes: 'O\'Brien'

Worker Process Information

local debug_mode = ngx.config.debug                -- Boolean
local install_prefix = ngx.config.prefix()         -- Installation path
local nginx_version = ngx.config.nginx_version     -- Version number
local lua_version = ngx.config.ngx_lua_version     -- Lua module version
local worker_pid = ngx.worker.pid()                -- Current worker PID
local is_exiting = ngx.worker.exiting()            -- Shutdown status

HTTP Status Code Constants

ngx.HTTP_OK                   -- 200
ngx.HTTP_CREATED              -- 201
ngx.HTTP_MOVED_PERMANENTLY    -- 301
ngx.HTTP_MOVED_TEMPORARILY    -- 302
ngx.HTTP_BAD_REQUEST          -- 400
ngx.HTTP_UNAUTHORIZED         -- 401
ngx.HTTP_FORBIDDEN            -- 403
ngx.HTTP_NOT_FOUND            -- 404
ngx.HTTP_INTERNAL_SERVER_ERROR -- 500
ngx.HTTP_SERVICE_UNAVAILABLE  -- 503

Conclusion

OpenResty's Lua integration provides a powerful framework for extending Nginx with dynamic logic. The key components covered include inline and file-based Lua execution, variable access patterns, request processing APIs, encoding utilities, and cross-phase data sharing through ngx.ctx. These fundamentals enable the development of sophisticated gateway applications, API proxies, and request processing pipelines within the Nginx ecosystem.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.