1156 lines
27 KiB
Markdown
1156 lines
27 KiB
Markdown
# Plugin Manager Documentation
|
|
|
|
## Table of Contents
|
|
|
|
1. [Overview](#overview)
|
|
2. [Architecture](#architecture)
|
|
3. [Getting Started](#getting-started)
|
|
4. [Core Concepts](#core-concepts)
|
|
- [Plugins](#plugins)
|
|
- [Routes](#routes)
|
|
- [Hooks](#hooks)
|
|
5. [API Reference](#api-reference)
|
|
- [Route Registration](#route-registration)
|
|
- [Hook Registration](#hook-registration)
|
|
- [File Operations](#file-operations)
|
|
- [HTML Manipulation](#html-manipulation)
|
|
- [Markdown Manipulation](#markdown-manipulation)
|
|
- [Logging Functions](#logging-functions)
|
|
- [Utility Functions](#utility-functions)
|
|
6. [Guardrails & Security](#guardrails--security)
|
|
7. [Examples](#examples)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The Plugin Manager is a Python-based system that enables dynamic Lua plugin loading for extending application functionality. It provides a secure, sandboxed environment for plugins to register HTTP routes, hook into application events, and manipulate HTML/Markdown content.
|
|
|
|
**Key Features:**
|
|
- Hot-reload plugins without restarting the application
|
|
- Secure file operations with path traversal protection
|
|
- Built-in HTML and Markdown manipulation tools
|
|
- Event-based hook system
|
|
- HTTP route registration
|
|
- Comprehensive logging
|
|
- Rate limiting and validation helpers
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
project/
|
|
├── plugin_manager.py # Main plugin manager
|
|
├── plugins/ # Lua plugin files (*.lua)
|
|
├── html/ # HTML files for manipulation
|
|
└── markdown/ # Markdown files for manipulation
|
|
```
|
|
|
|
### Component Overview
|
|
|
|
**PluginManager**: Core class that manages plugin lifecycle, Lua runtime, and API exposure
|
|
|
|
**PluginFSHandler**: Watchdog-based file system handler for hot-reloading
|
|
|
|
**LuaRuntime**: Embedded Lua interpreter with Python bindings via `lupa`
|
|
|
|
---
|
|
|
|
## Getting Started
|
|
|
|
### Installation Requirements
|
|
|
|
```bash
|
|
pip install lupa watchdog
|
|
```
|
|
|
|
### Creating Your First Plugin
|
|
|
|
1. Create a file `plugins/hello.lua`:
|
|
|
|
```lua
|
|
-- Register a simple route
|
|
add_route("/hello", function(req)
|
|
return "<h1>Hello from Lua Plugin!</h1>"
|
|
end)
|
|
|
|
log("Hello plugin loaded")
|
|
```
|
|
|
|
2. Start the plugin manager:
|
|
|
|
```python
|
|
from plugin_manager import PluginManager
|
|
|
|
manager = PluginManager()
|
|
manager.load_all()
|
|
|
|
# Handle a request
|
|
result = manager.handle_request("/hello")
|
|
print(result) # (200, {"Content-Type": "text/html"}, "<h1>Hello from Lua Plugin!</h1>")
|
|
```
|
|
|
|
3. The plugin will automatically reload when you modify `hello.lua`
|
|
|
|
---
|
|
|
|
## Core Concepts
|
|
|
|
### Plugins
|
|
|
|
Plugins are Lua scripts (`.lua` files) placed in the `plugins/` directory. Each plugin can:
|
|
- Register HTTP routes via [`add_route()`](#route-registration)
|
|
- Hook into events via [`register_hook()`](#hook-registration)
|
|
- Access [file operations](#file-operations)
|
|
- Use [HTML](#html-manipulation) and [Markdown manipulation](#markdown-manipulation) tools
|
|
|
|
**Plugin Lifecycle:**
|
|
1. **Load**: Plugin file is read and executed in Lua runtime
|
|
2. **Active**: Plugin routes/hooks are registered and callable
|
|
3. **Reload**: File change detected → unload → load
|
|
4. **Unload**: Plugin deleted → routes/hooks removed
|
|
|
|
### Routes
|
|
|
|
Routes map URL paths to Lua handler functions. When a request matches a registered route, the corresponding Lua function is called.
|
|
|
|
**See:** [Route Registration](#route-registration), [Examples - Route Handler](#example-1-simple-route-handler)
|
|
|
|
### Hooks
|
|
|
|
Hooks are named events where plugins can register callback functions. Multiple plugins can hook into the same event, and they'll be called in registration order.
|
|
|
|
**Common Use Cases:**
|
|
- Pre/post-processing request data
|
|
- Modifying responses before they're sent
|
|
- Content transformation pipelines
|
|
- Event notifications
|
|
|
|
**See:** [Hook Registration](#hook-registration), [Examples - Hook System](#example-2-hook-system)
|
|
|
|
---
|
|
|
|
## API Reference
|
|
|
|
### Route Registration
|
|
|
|
#### `add_route(path, handler_function)`
|
|
|
|
Register a URL path handler.
|
|
|
|
**Parameters:**
|
|
- `path` (string): URL path to handle (e.g., `/api/users`)
|
|
- `handler_function` (function): Lua function to handle requests
|
|
|
|
**Handler Function Signature:**
|
|
```lua
|
|
function(request) -> response
|
|
```
|
|
|
|
**Request Object:**
|
|
```lua
|
|
{
|
|
method = "GET", -- HTTP method
|
|
path = "/api/users", -- Request path
|
|
query = {...}, -- Query parameters
|
|
body = "...", -- Request body
|
|
headers = {...} -- Request headers
|
|
}
|
|
```
|
|
|
|
**Response Format:**
|
|
|
|
Option 1 - Simple string (returns 200 with text/html):
|
|
```lua
|
|
return "<html>...</html>"
|
|
```
|
|
|
|
Option 2 - Full response tuple:
|
|
```lua
|
|
return 200, {["Content-Type"] = "application/json"}, '{"status":"ok"}'
|
|
```
|
|
|
|
**Example:**
|
|
```lua
|
|
add_route("/api/status", function(req)
|
|
return 200, {["Content-Type"] = "application/json"}, '{"status": "online"}'
|
|
end)
|
|
```
|
|
|
|
**See also:** [Hooks](#hook-registration), [Examples](#examples)
|
|
|
|
---
|
|
|
|
### Hook Registration
|
|
|
|
#### `register_hook(hook_name, handler_function)`
|
|
|
|
Register a callback for a named hook event.
|
|
|
|
**Parameters:**
|
|
- `hook_name` (string): Name of the hook to register for
|
|
- `handler_function` (function): Callback function
|
|
|
|
**Handler Function Signature:**
|
|
```lua
|
|
function(...args) -> result
|
|
```
|
|
|
|
**Behavior:**
|
|
- All registered hooks for an event are called in order
|
|
- Return values can modify data (last non-nil value is used)
|
|
- Hooks receive arguments passed by `run_hook()` from Python
|
|
|
|
**Example:**
|
|
```lua
|
|
-- Content transformation hook
|
|
register_hook("transform_html", function(html)
|
|
-- Add analytics script
|
|
return html_insert_before(html, "</body>", "<script>analytics.js</script>")
|
|
end)
|
|
|
|
-- Logging hook
|
|
register_hook("request_complete", function(path, status)
|
|
log("Request to " .. path .. " returned " .. status)
|
|
end)
|
|
```
|
|
|
|
**Python Side - Triggering Hooks:**
|
|
```python
|
|
# Transform HTML through all registered hooks
|
|
modified_html = manager.run_hook("transform_html", original_html)
|
|
|
|
# Notify all hooks of event
|
|
manager.run_hook("request_complete", "/api/users", 200)
|
|
```
|
|
|
|
**See also:** [Routes](#route-registration), [Examples - Hooks](#example-2-hook-system)
|
|
|
|
---
|
|
|
|
### File Operations
|
|
|
|
#### `read_file(path)`
|
|
|
|
Read any file with path validation.
|
|
|
|
**Parameters:**
|
|
- `path` (string): File path to read
|
|
|
|
**Returns:** File content as string, or `nil` on error
|
|
|
|
**Security:** Path traversal (`..`) is blocked
|
|
|
|
**Example:**
|
|
```lua
|
|
local config = read_file("config.json")
|
|
if config then
|
|
log("Config loaded")
|
|
end
|
|
```
|
|
|
|
#### `write_file(path, content)`
|
|
|
|
Write content to file with path validation.
|
|
|
|
**Parameters:**
|
|
- `path` (string): File path to write
|
|
- `content` (string): Content to write
|
|
|
|
**Returns:** `true` on success, `false` on error
|
|
|
|
**Example:**
|
|
```lua
|
|
write_file("output.txt", "Generated content")
|
|
```
|
|
|
|
**See also:** [HTML Operations](#html-manipulation), [Markdown Operations](#markdown-manipulation)
|
|
|
|
---
|
|
|
|
### HTML Manipulation
|
|
|
|
#### `read_html(filename)`
|
|
|
|
Read HTML file from the `html/` directory.
|
|
|
|
**Parameters:**
|
|
- `filename` (string): Name of HTML file (e.g., `"index.html"`)
|
|
|
|
**Returns:** HTML content as string, or `nil` if not found
|
|
|
|
**Example:**
|
|
```lua
|
|
local html = read_html("index.html")
|
|
if html then
|
|
-- Process HTML
|
|
end
|
|
```
|
|
|
|
#### `write_html(filename, content)`
|
|
|
|
Write HTML content to the `html/` directory.
|
|
|
|
**Parameters:**
|
|
- `filename` (string): Name of HTML file
|
|
- `content` (string): HTML content to write
|
|
|
|
**Returns:** `true` on success, `false` on error
|
|
|
|
**Example:**
|
|
```lua
|
|
local modified = html_insert_after(html, "<head>", "<meta name='theme' content='dark'>")
|
|
write_html("index.html", modified)
|
|
```
|
|
|
|
#### `list_html_files()`
|
|
|
|
Get list of all HTML files in the `html/` directory.
|
|
|
|
**Returns:** Array of filenames
|
|
|
|
**Example:**
|
|
```lua
|
|
local files = list_html_files()
|
|
for _, file in ipairs(files) do
|
|
log("Found HTML file: " .. file)
|
|
end
|
|
```
|
|
|
|
#### `html_find_tag(html, tag)`
|
|
|
|
Find the first occurrence of an HTML tag.
|
|
|
|
**Parameters:**
|
|
- `html` (string): HTML content to search
|
|
- `tag` (string): Tag name (e.g., `"div"`, `"title"`)
|
|
|
|
**Returns:** Matched tag with content, or `nil` if not found
|
|
|
|
**Example:**
|
|
```lua
|
|
local title_tag = html_find_tag(html, "title")
|
|
-- Result: "<title>My Page</title>"
|
|
```
|
|
|
|
#### `html_replace_tag(html, tag, new_content)`
|
|
|
|
Replace the content inside an HTML tag.
|
|
|
|
**Parameters:**
|
|
- `html` (string): HTML content
|
|
- `tag` (string): Tag name to replace content within
|
|
- `new_content` (string): New content (tag itself is preserved)
|
|
|
|
**Returns:** Modified HTML
|
|
|
|
**Example:**
|
|
```lua
|
|
-- Replace title content
|
|
local html = "<title>Old Title</title>"
|
|
local modified = html_replace_tag(html, "title", "New Title")
|
|
-- Result: "<title>New Title</title>"
|
|
```
|
|
|
|
**See also:** [`html_wrap_content()`](#html_wrap_contenthtml-tag-wrapper_tag-attrs)
|
|
|
|
#### `html_insert_before(html, marker, content)`
|
|
|
|
Insert content before a marker string.
|
|
|
|
**Parameters:**
|
|
- `html` (string): HTML content
|
|
- `marker` (string): String to insert before
|
|
- `content` (string): Content to insert
|
|
|
|
**Returns:** Modified HTML
|
|
|
|
**Example:**
|
|
```lua
|
|
-- Insert meta tag before closing head
|
|
local html = "<head></head><body></body>"
|
|
local modified = html_insert_before(html, "</head>", "<meta charset='utf-8'>")
|
|
-- Result: "<head><meta charset='utf-8'></head><body></body>"
|
|
```
|
|
|
|
**See also:** [`html_insert_after()`](#html_insert_afterhtml-marker-content)
|
|
|
|
#### `html_insert_after(html, marker, content)`
|
|
|
|
Insert content after a marker string.
|
|
|
|
**Parameters:**
|
|
- `html` (string): HTML content
|
|
- `marker` (string): String to insert after
|
|
- `content` (string): Content to insert
|
|
|
|
**Returns:** Modified HTML
|
|
|
|
**Example:**
|
|
```lua
|
|
-- Insert script before closing body
|
|
local modified = html_insert_after(html, "<body>", "<script>init();</script>")
|
|
```
|
|
|
|
**See also:** [`html_insert_before()`](#html_insert_beforehtml-marker-content)
|
|
|
|
#### `html_wrap_content(html, tag, wrapper_tag, attrs)`
|
|
|
|
Wrap the content of a tag with another tag.
|
|
|
|
**Parameters:**
|
|
- `html` (string): HTML content
|
|
- `tag` (string): Tag whose content to wrap
|
|
- `wrapper_tag` (string): Tag to wrap with
|
|
- `attrs` (string): Attributes for wrapper tag (optional)
|
|
|
|
**Returns:** Modified HTML
|
|
|
|
**Example:**
|
|
```lua
|
|
local html = "<div>Hello World</div>"
|
|
local modified = html_wrap_content(html, "div", "span", 'class="highlight"')
|
|
-- Result: "<div><span class="highlight">Hello World</span></div>"
|
|
```
|
|
|
|
**See also:** [`html_replace_tag()`](#html_replace_taghtml-tag-new_content)
|
|
|
|
---
|
|
|
|
### Markdown Manipulation
|
|
|
|
#### `read_markdown(filename)`
|
|
|
|
Read Markdown file from the `markdown/` directory.
|
|
|
|
**Parameters:**
|
|
- `filename` (string): Name of Markdown file (e.g., `"README.md"`)
|
|
|
|
**Returns:** Markdown content as string, or `nil` if not found
|
|
|
|
**Example:**
|
|
```lua
|
|
local md = read_markdown("README.md")
|
|
```
|
|
|
|
#### `write_markdown(filename, content)`
|
|
|
|
Write Markdown content to the `markdown/` directory.
|
|
|
|
**Parameters:**
|
|
- `filename` (string): Name of Markdown file
|
|
- `content` (string): Markdown content to write
|
|
|
|
**Returns:** `true` on success, `false` on error
|
|
|
|
**Example:**
|
|
```lua
|
|
write_markdown("output.md", "# Generated Document\n\nContent here...")
|
|
```
|
|
|
|
#### `list_markdown_files()`
|
|
|
|
Get list of all Markdown files in the `markdown/` directory.
|
|
|
|
**Returns:** Array of filenames
|
|
|
|
**Example:**
|
|
```lua
|
|
local files = list_markdown_files()
|
|
for _, file in ipairs(files) do
|
|
local content = read_markdown(file)
|
|
-- Process each file
|
|
end
|
|
```
|
|
|
|
#### `md_add_header(markdown, level, text)`
|
|
|
|
Add a header at the beginning of Markdown content.
|
|
|
|
**Parameters:**
|
|
- `markdown` (string): Existing Markdown content
|
|
- `level` (number): Header level (1-6, where 1 is `#` and 6 is `######`)
|
|
- `text` (string): Header text
|
|
|
|
**Returns:** Modified Markdown with header prepended
|
|
|
|
**Example:**
|
|
```lua
|
|
local md = "Some content here"
|
|
local modified = md_add_header(md, 1, "Introduction")
|
|
-- Result:
|
|
-- # Introduction
|
|
--
|
|
-- Some content here
|
|
```
|
|
|
|
**See also:** [`md_replace_section()`](#md_replace_sectionmarkdown-header-new_content)
|
|
|
|
#### `md_replace_section(markdown, header, new_content)`
|
|
|
|
Replace an entire Markdown section (from header to next header or end).
|
|
|
|
**Parameters:**
|
|
- `markdown` (string): Markdown content
|
|
- `header` (string): Header text to find (without `#` symbols)
|
|
- `new_content` (string): New content for the section
|
|
|
|
**Returns:** Modified Markdown
|
|
|
|
**Example:**
|
|
```lua
|
|
local md = [[
|
|
# Introduction
|
|
Old intro text
|
|
|
|
# Features
|
|
Feature list here
|
|
]]
|
|
|
|
local modified = md_replace_section(md, "Introduction", "New introduction paragraph")
|
|
-- Result:
|
|
-- ## Introduction
|
|
--
|
|
-- New introduction paragraph
|
|
--
|
|
-- # Features
|
|
-- Feature list here
|
|
```
|
|
|
|
**See also:** [`md_add_header()`](#md_add_headermarkdown-level-text), [`md_append_content()`](#md_append_contentmarkdown-content)
|
|
|
|
#### `md_append_content(markdown, content)`
|
|
|
|
Append content to the end of Markdown document.
|
|
|
|
**Parameters:**
|
|
- `markdown` (string): Existing Markdown content
|
|
- `content` (string): Content to append
|
|
|
|
**Returns:** Modified Markdown
|
|
|
|
**Example:**
|
|
```lua
|
|
local md = "# Document\n\nExisting content"
|
|
local modified = md_append_content(md, "## Appendix\n\nAdditional information")
|
|
```
|
|
|
|
**See also:** [`md_add_header()`](#md_add_headermarkdown-level-text)
|
|
|
|
---
|
|
|
|
### Logging Functions
|
|
|
|
#### `log(message)`
|
|
|
|
Log informational message.
|
|
|
|
**Parameters:**
|
|
- `message` (string): Message to log
|
|
|
|
**Example:**
|
|
```lua
|
|
log("Plugin initialized successfully")
|
|
```
|
|
|
|
**See also:** [`log_warn()`](#log_warnmessage), [`log_error()`](#log_errormessage)
|
|
|
|
#### `log_warn(message)`
|
|
|
|
Log warning message.
|
|
|
|
**Parameters:**
|
|
- `message` (string): Warning message
|
|
|
|
**Example:**
|
|
```lua
|
|
if not config then
|
|
log_warn("Configuration file not found, using defaults")
|
|
end
|
|
```
|
|
|
|
**See also:** [`log()`](#logmessage), [`log_error()`](#log_errormessage)
|
|
|
|
#### `log_error(message)`
|
|
|
|
Log error message.
|
|
|
|
**Parameters:**
|
|
- `message` (string): Error message
|
|
|
|
**Example:**
|
|
```lua
|
|
local html = read_html("template.html")
|
|
if not html then
|
|
log_error("Failed to load template.html")
|
|
return
|
|
end
|
|
```
|
|
|
|
**See also:** [`log()`](#logmessage), [`log_warn()`](#log_warnmessage)
|
|
|
|
---
|
|
|
|
### Utility Functions
|
|
|
|
#### `table_to_json(lua_table)`
|
|
|
|
Convert Lua table to JSON string.
|
|
|
|
**Parameters:**
|
|
- `lua_table` (table): Lua table to convert
|
|
|
|
**Returns:** JSON string
|
|
|
|
**Example:**
|
|
```lua
|
|
local data = {name = "John", age = 30}
|
|
local json = table_to_json(data)
|
|
-- Result: '{"name":"John","age":30}'
|
|
```
|
|
|
|
**See also:** [`json_to_table()`](#json_to_tablejson_str)
|
|
|
|
#### `json_to_table(json_str)`
|
|
|
|
Parse JSON string into Lua table.
|
|
|
|
**Parameters:**
|
|
- `json_str` (string): JSON string
|
|
|
|
**Returns:** Lua table, or empty table on error
|
|
|
|
**Example:**
|
|
```lua
|
|
local json = '{"status":"ok","count":5}'
|
|
local data = json_to_table(json)
|
|
log("Status: " .. data.status) -- Status: ok
|
|
```
|
|
|
|
**See also:** [`table_to_json()`](#table_to_jsonlua_table)
|
|
|
|
---
|
|
|
|
## Guardrails & Security
|
|
|
|
The plugin system includes built-in security features and helper functions to prevent common vulnerabilities.
|
|
|
|
### Path Traversal Protection
|
|
|
|
All file operations automatically block path traversal attempts:
|
|
|
|
```lua
|
|
-- BLOCKED - Will fail safely
|
|
read_file("../../../etc/passwd")
|
|
read_html("../../config.php")
|
|
|
|
-- ALLOWED - Sandboxed to designated directories
|
|
read_html("index.html")
|
|
read_markdown("docs.md")
|
|
```
|
|
|
|
**Implementation:** Any path containing `..` is rejected before file access.
|
|
|
|
### Safe String Operations
|
|
|
|
#### `safe_concat(...)`
|
|
|
|
Safely concatenate multiple values, converting to strings and handling `nil`.
|
|
|
|
**Example:**
|
|
```lua
|
|
local msg = safe_concat("User ", nil, " logged in at ", os.time())
|
|
-- Result: "User logged in at 1609459200"
|
|
```
|
|
|
|
**See also:** [`escape_html()`](#escape_htmlstr)
|
|
|
|
#### `escape_html(str)`
|
|
|
|
Escape HTML special characters to prevent XSS attacks.
|
|
|
|
**Escapes:**
|
|
- `&` → `&`
|
|
- `<` → `<`
|
|
- `>` → `>`
|
|
- `"` → `"`
|
|
- `'` → `'`
|
|
|
|
**Example:**
|
|
```lua
|
|
local user_input = '<script>alert("XSS")</script>'
|
|
local safe = escape_html(user_input)
|
|
-- Result: "<script>alert("XSS")</script>"
|
|
|
|
return "<div>" .. safe .. "</div>"
|
|
```
|
|
|
|
**Use Case:** Always escape user input before including in HTML responses.
|
|
|
|
**See also:** [Security Best Practices](#security)
|
|
|
|
### Filename Validation
|
|
|
|
#### `is_valid_filename(name)`
|
|
|
|
Validate filename for safety.
|
|
|
|
**Checks:**
|
|
- Not nil or empty
|
|
- No `..` (path traversal)
|
|
- No `/` or `\` (directory separators)
|
|
|
|
**Returns:** `true` if valid, `false` otherwise
|
|
|
|
**Example:**
|
|
```lua
|
|
local filename = req.query.file
|
|
|
|
if not is_valid_filename(filename) then
|
|
return 400, {}, "Invalid filename"
|
|
end
|
|
|
|
local content = read_html(filename)
|
|
```
|
|
|
|
**See also:** [Path Traversal Protection](#path-traversal-protection)
|
|
|
|
### Error Handling
|
|
|
|
#### `try_catch(fn, catch_fn)`
|
|
|
|
Safe error handling wrapper using `pcall`.
|
|
|
|
**Parameters:**
|
|
- `fn` (function): Function to execute
|
|
- `catch_fn` (function): Error handler (receives error message)
|
|
|
|
**Returns:** `true` if successful, `false` if error occurred
|
|
|
|
**Example:**
|
|
```lua
|
|
local success = try_catch(
|
|
function()
|
|
local html = read_html("template.html")
|
|
local modified = html_replace_tag(html, "title", "New Title")
|
|
write_html("output.html", modified)
|
|
end,
|
|
function(err)
|
|
log_error("HTML processing failed: " .. tostring(err))
|
|
end
|
|
)
|
|
|
|
if not success then
|
|
return 500, {}, "Internal error"
|
|
end
|
|
```
|
|
|
|
**See also:** [`validate_request()`](#validate_requestreq-required_fields)
|
|
|
|
### Request Validation
|
|
|
|
#### `validate_request(req, required_fields)`
|
|
|
|
Validate request object has required fields.
|
|
|
|
**Parameters:**
|
|
- `req` (table): Request object to validate
|
|
- `required_fields` (array): List of required field names
|
|
|
|
**Returns:** `(true, nil)` if valid, or `(false, error_message)` if invalid
|
|
|
|
**Example:**
|
|
```lua
|
|
add_route("/api/create", function(req)
|
|
local valid, err = validate_request(req, {"name", "email", "body"})
|
|
if not valid then
|
|
return 400, {}, "Bad request: " .. err
|
|
end
|
|
|
|
-- Process valid request
|
|
return 200, {}, "Created"
|
|
end)
|
|
```
|
|
|
|
**See also:** [`try_catch()`](#try_catchfn-catch_fn)
|
|
|
|
### Rate Limiting
|
|
|
|
#### `check_rate_limit(key, max_calls, window_seconds)`
|
|
|
|
Simple in-memory rate limiting.
|
|
|
|
**Parameters:**
|
|
- `key` (string): Unique identifier for rate limit (e.g., IP address, user ID)
|
|
- `max_calls` (number): Maximum calls allowed in window
|
|
- `window_seconds` (number): Time window in seconds
|
|
|
|
**Returns:** `true` if within limit, `false` if exceeded
|
|
|
|
**Example:**
|
|
```lua
|
|
add_route("/api/search", function(req)
|
|
local client_ip = req.headers["X-Forwarded-For"] or "unknown"
|
|
|
|
if not check_rate_limit(client_ip, 10, 60) then
|
|
return 429, {}, "Rate limit exceeded. Try again later."
|
|
end
|
|
|
|
-- Process search
|
|
return 200, {}, "Search results"
|
|
end)
|
|
```
|
|
|
|
**Note:** Rate limits are stored in memory and reset when plugin reloads.
|
|
|
|
**See also:** [Security Best Practices](#security)
|
|
|
|
### Table Utilities
|
|
|
|
#### `table_contains(tbl, value)`
|
|
|
|
Check if table contains a value.
|
|
|
|
**Example:**
|
|
```lua
|
|
local allowed = {"admin", "moderator", "user"}
|
|
if table_contains(allowed, req.role) then
|
|
-- Process request
|
|
end
|
|
```
|
|
|
|
#### `table_keys(tbl)`
|
|
|
|
Get all keys from a table.
|
|
|
|
**Example:**
|
|
```lua
|
|
local data = {name = "John", age = 30}
|
|
local keys = table_keys(data)
|
|
-- Result: {"name", "age"}
|
|
```
|
|
|
|
#### `table_values(tbl)`
|
|
|
|
Get all values from a table.
|
|
|
|
**Example:**
|
|
```lua
|
|
local data = {a = 1, b = 2, c = 3}
|
|
local values = table_values(data)
|
|
-- Result: {1, 2, 3}
|
|
```
|
|
|
|
---
|
|
|
|
## Examples
|
|
|
|
### Example 1: Simple Route Handler
|
|
|
|
Create `plugins/api.lua`:
|
|
|
|
```lua
|
|
-- Simple API route
|
|
add_route("/api/hello", function(req)
|
|
local name = req.query.name or "World"
|
|
local safe_name = escape_html(name)
|
|
|
|
return 200,
|
|
{["Content-Type"] = "application/json"},
|
|
'{"message": "Hello ' .. safe_name .. '"}'
|
|
end)
|
|
|
|
log("API plugin loaded")
|
|
```
|
|
|
|
**Usage:**
|
|
```python
|
|
result = manager.handle_request("/api/hello", {
|
|
"query": {"name": "Alice"}
|
|
})
|
|
# Returns: (200, {...}, '{"message": "Hello Alice"}')
|
|
```
|
|
|
|
**See also:** [Route Registration](#route-registration)
|
|
|
|
---
|
|
|
|
### Example 2: Hook System
|
|
|
|
Create `plugins/analytics.lua`:
|
|
|
|
```lua
|
|
-- Hook into HTML transformation
|
|
register_hook("transform_html", function(html, page_name)
|
|
log("Adding analytics to " .. page_name)
|
|
|
|
local analytics_script = [[
|
|
<script>
|
|
window.analytics = {
|
|
page: "]] .. page_name .. [[",
|
|
timestamp: ]] .. os.time() .. [[
|
|
};
|
|
</script>
|
|
]]
|
|
|
|
return html_insert_before(html, "</head>", analytics_script)
|
|
end)
|
|
```
|
|
|
|
**Python Side:**
|
|
```python
|
|
# Trigger hook
|
|
original_html = "<html><head></head><body>...</body></html>"
|
|
modified_html = manager.run_hook("transform_html", original_html, "home")
|
|
```
|
|
|
|
**See also:** [Hook Registration](#hook-registration)
|
|
|
|
---
|
|
|
|
### Example 3: HTML Template Processor
|
|
|
|
Create `plugins/templates.lua`:
|
|
|
|
```lua
|
|
-- Process HTML templates with variable substitution
|
|
add_route("/render/:template", function(req)
|
|
local template_name = req.params.template .. ".html"
|
|
|
|
-- Validate filename
|
|
if not is_valid_filename(template_name) then
|
|
return 400, {}, "Invalid template name"
|
|
end
|
|
|
|
-- Read template
|
|
local html = read_html(template_name)
|
|
if not html then
|
|
return 404, {}, "Template not found"
|
|
end
|
|
|
|
-- Replace variables
|
|
local title = req.query.title or "Untitled"
|
|
local content = req.query.content or "No content"
|
|
|
|
html = html_replace_tag(html, "title", escape_html(title))
|
|
html = html_replace_tag(html, "main", escape_html(content))
|
|
|
|
return html
|
|
end)
|
|
```
|
|
|
|
**See also:** [HTML Manipulation](#html-manipulation), [Route Registration](#route-registration)
|
|
|
|
---
|
|
|
|
### Example 4: Markdown Documentation Generator
|
|
|
|
Create `plugins/docs_generator.lua`:
|
|
|
|
```lua
|
|
-- Generate documentation from code files
|
|
add_route("/docs/generate", function(req)
|
|
local doc_content = [[
|
|
# API Documentation
|
|
|
|
Generated on: ]] .. os.date() .. [[
|
|
|
|
## Overview
|
|
|
|
This documentation is auto-generated from source code.
|
|
]]
|
|
|
|
-- Add sections for each endpoint
|
|
local endpoints = {
|
|
{path = "/api/users", method = "GET", desc = "Get all users"},
|
|
{path = "/api/users/:id", method = "GET", desc = "Get user by ID"},
|
|
{path = "/api/users", method = "POST", desc = "Create new user"}
|
|
}
|
|
|
|
for _, endpoint in ipairs(endpoints) do
|
|
local section = string.format([[
|
|
|
|
## %s %s
|
|
|
|
%s
|
|
|
|
**Example:**
|
|
```
|
|
curl -X %s http://localhost%s
|
|
```
|
|
]], endpoint.method, endpoint.path, endpoint.desc, endpoint.method, endpoint.path)
|
|
|
|
doc_content = md_append_content(doc_content, section)
|
|
end
|
|
|
|
-- Save to file
|
|
write_markdown("api_docs.md", doc_content)
|
|
|
|
return 200, {}, "Documentation generated successfully"
|
|
end)
|
|
```
|
|
|
|
**See also:** [Markdown Manipulation](#markdown-manipulation)
|
|
|
|
---
|
|
|
|
### Example 5: Content Security System
|
|
|
|
Create `plugins/security.lua`:
|
|
|
|
```lua
|
|
-- Rate-limited API with validation
|
|
add_route("/api/submit", function(req)
|
|
-- Rate limiting
|
|
local client_ip = req.headers["X-Real-IP"] or "unknown"
|
|
if not check_rate_limit(client_ip, 5, 60) then
|
|
log_warn("Rate limit exceeded for " .. client_ip)
|
|
return 429, {}, "Too many requests"
|
|
end
|
|
|
|
-- Request validation
|
|
local valid, err = validate_request(req, {"title", "content", "author"})
|
|
if not valid then
|
|
return 400, {}, err
|
|
end
|
|
|
|
-- Content sanitization
|
|
local title = escape_html(req.body.title)
|
|
local content = escape_html(req.body.content)
|
|
local author = escape_html(req.body.author)
|
|
|
|
-- Save as HTML
|
|
local html = string.format([[
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>%s</title>
|
|
</head>
|
|
<body>
|
|
<h1>%s</h1>
|
|
<p>By: %s</p>
|
|
<div>%s</div>
|
|
</body>
|
|
</html>
|
|
]], title, title, author, content)
|
|
|
|
local filename = "post_" .. os.time() .. ".html"
|
|
write_html(filename, html)
|
|
|
|
log("Created post: " .. filename)
|
|
return 200, {}, "Post created successfully"
|
|
end)
|
|
```
|
|
|
|
**See also:** [Guardrails & Security](#guardrails--security)
|
|
|
|
---
|
|
|
|
### Example 6: Multi-File HTML Processor
|
|
|
|
Create `plugins/html_batch.lua`:
|
|
|
|
```lua
|
|
-- Process all HTML files in a directory
|
|
add_route("/admin/add_footer", function(req)
|
|
local files = list_html_files()
|
|
local processed = 0
|
|
|
|
local footer = [[
|
|
<footer style="text-align:center; padding:20px; background:#f0f0f0;">
|
|
<p>© 2025 My Website. All rights reserved.</p>
|
|
</footer>
|
|
]]
|
|
|
|
for _, filename in ipairs(files) do
|
|
try_catch(
|
|
function()
|
|
local html = read_html(filename)
|
|
if html then
|
|
-- Add footer before closing body tag
|
|
local modified = html_insert_before(html, "</body>", footer)
|
|
write_html(filename, modified)
|
|
processed = processed + 1
|
|
end
|
|
end,
|
|
function(err)
|
|
log_error("Failed to process " .. filename .. ": " .. tostring(err))
|
|
end
|
|
)
|
|
end
|
|
|
|
return 200, {}, "Processed " .. processed .. " files"
|
|
end)
|
|
```
|
|
|
|
**See also:** [HTML Manipulation](#html-manipulation), [Error Handling](#error-handling)
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### Plugin Design
|
|
|
|
1. **Single Responsibility**: Each plugin should have one clear purpose
|
|
2. **Error Handling**: Always use [`try_catch()`](#try_catchfn-catch_fn) for operations that might fail
|
|
3. **Logging**: Use appropriate log levels ([`log()`](#logmessage), [`log_warn()`](#log_warnmessage), [`log_error()`](#log_errormessage))
|
|
4. **Documentation**: Add comments explaining what your plugin does
|
|
|
|
```lua
|
|
-- Good: Clear purpose, error handling, logging
|
|
add_route("/api/config", function(req)
|
|
try_catch(
|
|
function()
|
|
local config = read_file("config.json")
|
|
return 200, {}, config
|
|
end,
|
|
function(err)
|
|
log_error("Config read failed: " .. tostring(err))
|
|
return 500, {}, "Internal error"
|
|
end
|
|
)
|
|
end)
|
|
```
|
|
|
|
**See also:** [Examples](#examples)
|
|
|
|
---
|
|
|
|
### Security
|
|
|
|
1. **Always Escape User Input**: Use [`escape_html()`](#escape_htmlstr) for any user-provided content
|
|
2. **Validate Filenames**: Use [`is_valid_filename()`](#is_valid_filenamename) before file operations
|
|
3. **Validate Requests**: Use [`validate_request()`](#validate_requestreq-required_fields) for required fields
|
|
4. **Implement Rate Limiting**: Use [`check_rate_limit()`](#check_rate_limitkey-max_calls-window_seconds) for public APIs
|
|
5. **Never Trust Input**: Sanitize and validate all external data
|
|
|
|
```lua
|
|
-- Good: Comprehensive security
|
|
add_route("/api/read", function(req)
|
|
local filename = req.query.file
|
|
|
|
-- Validate filename
|
|
if not is_valid_filename(filename) then
|
|
return 400, {}, "Invalid filename"
|
|
end
|
|
|
|
-- Rate limit
|
|
if not check_rate_limit(req.ip, 10, 60) then
|
|
return 429, {}, "Rate limited"
|
|
end
|
|
|
|
-- Safe read
|
|
local content = read_html(filename)
|
|
if content then
|
|
return escape_html(content)
|
|
end
|
|
return 404, {}, "Not found"
|
|
end)
|
|
```
|
|
|
|
**See also:** [Guardrails & Security](#guardrails--security), [Path Traversal Protection](#path-traversal-protection)
|
|
|