HTTP Applications with Luna Framework

Build powerful HTTP servers and web applications with Luna Framework. Full-featured HTTP server implementation for Lua with routing, middleware, and SSL support.

Important Note

HTTP applications are not available in the Python version of Luna Framework. If you're using Luna with Python, please use standard HTTP libraries for Python instead of the new_http_app and remove_http_app methods.

Luna HTTP Applications Features

Luna Framework provides a complete HTTP server implementation for Lua with SSL support, middleware system, routing, and a simple API for creating web applications of any complexity.

Advantages

Full HTTP Server

Complete HTTP/1.1 protocol implementation for creating web applications in Lua.

Flexible Routing

Support for all HTTP methods and route grouping with prefixes.

Built-in SSL Support

Secure connections with SSL/TLS support for data protection.

Middleware System

Extensible middleware architecture for request processing.

Built-in Templates

Pre-built middleware for common tasks like CORS, rate limiting, and static files.

High Performance

Optimized code for handling hundreds of simultaneous connections.

Basic Example

Simple example of an HTTP server with routing and middleware.

-- Create a new HTTP application
local app = luna.new_http_app({
    name = "My HTTP Server",
    debug = true,
    no_errors = true,
    error_handler = function(err)
        print("HTTP Server Error:", err)
    end,
    new_client = function(ip, client_data)
        print("New client connected from:", ip)
    end,
    close_client = function(ip, client_data, reason)
        print("Client disconnected:", ip, reason)
    end
})

-- Add middleware
app:use(body_parser())
app:use(logger())

-- Define routes
app:get("/", function(req, res, client_data)
    res:status(200)
    res:send("<h1>Hello, World!</h1>")
end)

app:get("/api/users", function(req, res, client_data)
    res:json({users = {"Alice", "Bob", "Charlie"}})
end)

app:post("/api/users", function(req, res, client_data)
    local user = req.body
    -- Process user creation
    res:status(201)
    res:json({message = "User created", user = user})
end)

-- Start the server
app:listen(8080, "0.0.0.0")

Core Methods

luna.new_http_app(config)
app_data

Creates a new HTTP application with the specified configuration.

Configuration Parameters:

Parameter Type Required Description
name string Yes Application name (for debugging)
error_handler function No Function for error handling
no_errors boolean No If true, errors won't stop the server
debug boolean No Enable debug mode (default: true)
new_client function No Callback when new client connects: function(ip, client_data)
close_client function No Callback when client disconnects: function(ip, client_data, reason)
timeout_client function No Callback when client timeout: function(ip, client_data, inactive_time)
error_client function No Callback when client error: function(ip, client_data, error_msg)

Example:

local app = luna.new_http_app({
    name = "My HTTP Server",
    error_handler = function(err)
        print("Server error:", err)
    end,
    no_errors = true,
    debug = true,
    new_client = function(ip, client_data)
        print("New client:", ip)
    end,
    close_client = function(ip, client_data, reason)
        print("Client disconnected:", ip, reason)
    end,
    timeout_client = function(ip, client_data, inactive_time)
        print("Client timeout:", ip, inactive_time)
    end,
    error_client = function(ip, client_data, error_msg)
        print("Client error:", ip, error_msg)
    end
})
luna.remove_http_app(app_data_or_name)

Removes an HTTP application and releases resources.

luna.remove_http_app("My HTTP Server")
-- or
luna.remove_http_app(app)

Application Methods

app_data.listen(port, host, [protocol], [ssl_config])

Starts the HTTP server and begins listening for connections.

Parameters:

port number Yes Port to listen on
host string Yes Host to bind to (e.g., "0.0.0.0" or "127.0.0.1")
protocol string No Protocol ("http" or "https")
ssl_config table No SSL configuration for HTTPS

Example:

-- Start HTTP server
app:listen(8080, "0.0.0.0")

-- Start HTTPS server
app:listen(8443, "0.0.0.0", "https", {
    cert = "path/to/cert.pem",
    key = "path/to/key.pem"
})
app_data.stop()

Stops the HTTP server.

app:stop()
print("HTTP server stopped")
app_data.is_running()
boolean

Returns whether the server is currently running.

if app:is_running() then
    print("Server is running")
else
    print("Server is stopped")
end
app_data.get_client_count()
number

Returns the number of currently connected clients.

local count = app:get_client_count()
print("Connected clients:", count)

Default Settings

app_data.set_default_timeout(timeout)

Sets the default timeout for new clients (in seconds).

app:set_default_timeout(30) -- 30 seconds
app_data.set_default_max_header_size(size)

Sets the default maximum header size for new clients (in bytes).

app:set_default_max_header_size(8192) -- 8KB
app_data.set_default_max_body_size(size)

Sets the default maximum body size for new clients (in bytes).

app:set_default_max_body_size(1048576) -- 1MB

Middleware

app_data.use(middleware_function)

Adds a middleware function to the application. Middleware functions are executed in the order they are added.

Parameters:

middleware_function function Yes Middleware function: function(req, res, next)

Example:

app:use(function(req, res, next)
    -- Log request
    print("Request:", req.method, req.path)
    -- Continue to next middleware/route
    next()
end)

app:use(function(req, res, next)
    -- Add custom header
    res:setHeader("X-Powered-By", "Luna Framework")
    next()
end)

Routing

app_data.get(path, handler)

Adds a GET route handler.

app:get("/users", function(req, res, client_data)
    res:json({users = {"Alice", "Bob"}})
end)
app_data.post(path, handler)

Adds a POST route handler.

app:post("/users", function(req, res, client_data)
    local user = req.body
    -- Create user
    res:status(201):json({message = "User created"})
end)
app_data.put(path, handler)

Adds a PUT route handler.

app:put("/users", function(req, res, client_data)
    local userId = req.params.id
    -- Update user
    res:json({message = "User updated"})
end)
app_data.delete(path, handler)

Adds a DELETE route handler.

app:delete("/users", function(req, res, client_data)
    local userId = req.params.id
    -- Delete user
    res:status(204):send()
end)
app_data.patch(path, handler)

Adds a PATCH route handler.

app:patch("/users", function(req, res, client_data)
    local userId = req.params.id
    -- Partially update user
    res:json({message = "User updated"})
end)
app_data.head(path, handler)

Adds a HEAD route handler.

app:head("/health", function(req, res, client_data)
    res:status(200):send()
end)
app_data.options(path, handler)

Adds an OPTIONS route handler.

app:options("/users", function(req, res, client_datas)
    res:setHeader("Allow", "GET, POST, OPTIONS")
    res:status(200):send()
end)

Route Groups

app_data.group(prefix)
group

Creates a route group with a common prefix.

Parameters:

prefix string Yes Prefix for all routes in the group

Example:

local api = app:group("/api")

api:get("/users", function(req, res, client_data)
    -- Handles GET /api/users
end)

api:post("/users", function(req, res, client_data)
    -- Handles POST /api/users
end)

-- Nested groups
local v1 = api:group("/v1")
v1:get("/status", function(req, res, client_data)
    -- Handles GET /api/v1/status
end)

Template: static()

Serves static files from a directory.

static(directory)
middleware_function

Parameters:

directory string Yes Directory path to serve files from

Example:

app:use(static(arg[1] .. "/public"))
-- Serves files from ./public directory
-- e.g., GET /style.css serves ./public/style.css

Template: cors()

Adds CORS (Cross-Origin Resource Sharing) headers to responses.

cors(options)
middleware_function

Options:

origins table No Allowed origins (default: {"*"})
methods table No Allowed HTTP methods
headers table No Allowed headers
max_age number No Max age in seconds (default: 86400)
credentials boolean No Allow credentials (default: false)

Example:

app:use(cors({
    origins = {"https://example.com", "https://app.example.com"},
    methods = {"GET", "POST", "PUT", "DELETE"},
    headers = {"Content-Type", "Authorization"},
    max_age = 3600,
    credentials = true
}))

Template: rate_limited()

Adds rate limiting to prevent abuse.

rate_limited(options)
middleware_function

Options:

window_ms number No Time window in milliseconds (default: 60000)
max_requests number No Max requests per window (default: 100)
skip function No Function to skip rate limiting: function(req) return boolean end
key_generator function No Function to generate rate limit key: function(req) return string end
message string No Error message (default: "Too many requests")
status_code number No HTTP status code (default: 429)

Example:

app:use(rate_limited({
    window_ms = 60000, -- 1 minute
    max_requests = 10, -- 10 requests per minute
    skip = function(req)
        -- Skip rate limiting for health checks
        return req.path == "/health"
    end,
    key_generator = function(req)
        -- Rate limit by IP
        return req.headers["x-forwarded-for"] or req.client_data.ip or "unknown"
    end
}))

Template: body_parser()

Parses request body for JSON and form data.

body_parser()
middleware_function

Automatically parses application/json and application/x-www-form-urlencoded request bodies.

Example:

app:use(body_parser())

app:post("/api/users", function(req, res, client_data)
    -- req.body is automatically parsed
    local user = req.body
    res:json({message = "User created", user = user})
end)

Template: logger()

Logs HTTP requests.

logger(options)
middleware_function

Options:

format string No Log format (default: ":method :url :status :response-time ms")
stream table No Output stream: { write = function(msg) end }
skip function No Function to skip logging: function(req) return boolean end

Example:

app:use(logger({
    format = ":method :url :status :response-time ms",
    stream = {
        write = function(msg)
            print(msg)
        end
    },
    skip = function(req)
        -- Skip logging for health checks
        return req.path == "/health"
    end
}))

Request Object

Properties and methods available in the request object passed to route handlers.

Property Type Description
method string HTTP method (e.g., "GET", "POST")
path string Request path
query table Query parameters
headers table HTTP headers
body string Request body (parsed by body_parser)
client client_data.socket Client connection socket
client_data client_data Client connection data (direct reference)
rawRequest string Raw HTTP request string

Example:

app:get("/search", function(req, res, client_data)
    local query = req.query.q
    local userAgent = req.headers["User-Agent"]
    
    -- Access client data directly
    print("Client IP:", req.client_data.ip)
    print("Using SSL:", req.client_data.is_ssl)
    
    print("Search query:", query)
    print("User agent:", userAgent)
    
    res:json({results = {}})
end)

Response Object

Methods available in the response object passed to route handlers.

response:status(code)

Sets the HTTP status code.

res:status(404)
res:send("Not found")
response:setHeader(key, value)

Sets an HTTP header.

res:setHeader("Content-Type", "application/json")
res:setHeader("X-Custom-Header", "value")
response:send(data)

Sends a response. Automatically sets Content-Type to "text/html" if not already set.

res:send("<h1>Hello World</h1>")
-- or
res:status(200):send("OK")
response:sendFile(filePath, [mimeType])

Sends a file as response. Automatically detects MIME type from file extension if not specified.

-- Send file with automatic MIME type detection
res:sendFile("/path/to/image.png")

-- Send file with custom MIME type
res:sendFile("/path/to/data.custom", "application/x-custom-format")

-- Send file with status code
res:status(200):sendFile("/path/to/document.pdf")
response:json(data_or_table)

Sends a JSON response. Automatically sets Content-Type to "application/json".

res:json({message = "Success", data = {}})
-- or
res:status(201):json({id = 123, name = "John"})
response:build()

Builds the HTTP response string. Usually called automatically.

local httpResponse = res:build()

Client Data

Properties and methods available in the client_data object.

Property/Method Type Description
socket socket LuaSocket TCP socket (может быть SSL-оберткой)
raw_socket socket Исходный сокет (до SSL-обертки)
buffer string Client buffer
is_ssl boolean True if client uses SSL (HTTPS)
connect_time number Connection timestamp
last_activity number Last activity timestamp
ip string Client IP address
set_timeout(timeout) function Sets client timeout
set_max_header_size(size) function Sets max header size
set_max_body_size(size) function Sets max body size
timeout number Current timeout value
max_header_size number Current max header size
max_body_size number Current max body size

Example:

app:use(function(req, res, next)
    -- Set custom timeout for this client
    req.client_data:set_timeout(60) -- 60 seconds
    
    -- Log client info
    print("Client IP:", req.client_data.ip)
    print("Connected at:", req.client_data.connect_time)
    print("Using SSL:", req.client_data.is_ssl)
    print("Raw socket available:", req.client_data.raw_socket ~= nil)
    
    -- Access raw socket if needed
    if req.client_data.raw_socket then
        print("Raw socket type:", type(req.client_data.raw_socket))
    end
    
    next()
end)

Constants

Luna Framework provides built-in constants for HTTP status codes and MIME types to make your code more readable and maintainable.

app.STATUS_CODES

A table mapping HTTP status codes to their descriptive messages.

Available Status Codes:

Code Message Description
100 Continue Client should continue with request
101 Switching Protocols Server is switching protocols
200 OK Request succeeded
201 Created Resource created successfully
202 Accepted Request accepted for processing
204 No Content Success but no content to return
206 Partial Content Partial content delivered
301 Moved Permanently Resource moved permanently
302 Found Resource found at different location
304 Not Modified Resource not modified since last request
307 Temporary Redirect Temporary redirect
400 Bad Request Malformed request
401 Unauthorized Authentication required
403 Forbidden Access denied
404 Not Found Resource not found
405 Method Not Allowed HTTP method not allowed
429 Too Many Requests Rate limit exceeded
500 Internal Server Error Server encountered an error
502 Bad Gateway Invalid response from upstream
503 Service Unavailable Service temporarily unavailable
504 Gateway Timeout Upstream server timeout

Example Usage:

app:get("/api/users", function(req, res, client_data)
    -- Using status codes constants
    res:status(app.STATUS_CODES[200])
    res:json({users = {"Alice", "Bob"}})
end)

app:post("/api/users", function(req, res, client_data)
    -- Create user logic here
    res:status(app.STATUS_CODES[201])
    res:json({message = "User created successfully"})
end)
app.MIME_TYPES

A table mapping file extensions to their corresponding MIME types.

Available MIME Types:

Category Extension MIME Type
Text html text/html
htm text/html
css text/css
js application/javascript
json application/json
txt text/plain
md text/markdown
xml application/xml
Images png image/png
jpg image/jpeg
jpeg image/jpeg
gif image/gif
svg image/svg+xml
ico image/x-icon
webp image/webp
Audio/Video mp3 audio/mpeg
mp4 video/mp4
csv text/csv
bin application/octet-stream
Fonts woff font/woff
woff2 font/woff2
ttf font/ttf
eot application/vnd.ms-fontobject
Application pdf application/pdf
zip application/zip
tar application/x-tar
gz application/gzip

Example Usage:

-- Using MIME types with static file serving
app:use(static("public"))

-- Setting correct Content-Type for API responses
app:get("/api/data", function(req, res, client_data)
    res:setHeader("Content-Type", app.MIME_TYPES.json)
    res:json({data = "some data"})
end)

-- Serving different content types based on request
app:get("/content", function(req, res, client_data)
    local format = req.query.format or "json"
    
    if format == "xml" then
        res:setHeader("Content-Type", app.MIME_TYPES.xml)
        res:send("<data>Some XML data</data>")
    elseif format == "txt" then
        res:setHeader("Content-Type", app.MIME_TYPES.txt)
        res:send("Some plain text data")
    else
        res:setHeader("Content-Type", app.MIME_TYPES.json)
        res:json({data = "Some JSON data"})
    end
end)

HTTP/HTTPS Client

Lunac теперь включает встроенный HTTP/HTTPS клиент для выполнения веб-запросов. Поддерживает как синхронные, так и асинхронные запросы.

lunac.http.init(config)

Инициализирует HTTP клиент. Должен быть вызван перед использованием методов fetch.

Configuration Parameters:

Parameter Type Required Description
luna table No Luna объект, если сервер находится в том же приложении (для fetch)
lunac table No Lunac объект, если нужно работать параллельно при fetch

Example:

-- Initialize HTTP client
lunac.http.init({
    luna = luna,    -- if server is in same app
    lunac = lunac   -- for parallel operation
})

Synchronous HTTP Requests

lunac.http.fetch(url, options)
result, code, headers, status

Выполняет синхронный HTTP/HTTPS запрос и возвращает результат.

Parameters:

Parameter Type Required Description
url string Yes URL для запроса (http:// или https://)
options table No Опции запроса (method, headers, sink, etc.)

Returns:

result mixed Результат запроса или nil при ошибке
code number HTTP статус код или описание ошибки
headers table Заголовки ответа
status string Статус ответа

Example:

local response_data = {}
local sync_data, sync_code, sync_headers, sync_status = lunac.http.fetch("https://www.google.com", {
    sink = ltn12.sink.table(response_data)
})

if sync_data then
    print("HTTPS -------------------")
    print("Success:", true)
    print("HTTP Code:", sync_code)
    print("Status:", sync_status)
    print("Data size:", #table.concat(response_data))
    print("-------------------")
else
    print("Error:", sync_code)
end

Asynchronous HTTP Requests

lunac.http.noawait_fetch(url, options, callback)

Выполняет асинхронный HTTP/HTTPS запрос и вызывает callback при завершении.

Parameters:

Parameter Type Required Description
url string Yes URL для запроса (http:// или https://)
options table No Опции запроса (method, headers, sink, etc.)
callback function Yes Функция обратного вызова: function(result, code, headers, status)

Example:

local response_data = {}
lunac.http.noawait_fetch("http://www.google.com", {
    sink = ltn12.sink.table(response_data)
}, function(result, code, headers, status)
    print("HTTP -----------")
    print("HTTP Code:", code)
    print("Headers:", headers)
    print("Status:", status)
    print("Data size:", #table.concat(response_data))
end)

Advanced Examples

GET Request with Headers

-- GET request with custom headers
local response_data = {}
local result, code, headers = lunac.http.fetch("https://api.example.com/data", {
    method = "GET",
    headers = {
        ["Authorization"] = "Bearer token123",
        ["Content-Type"] = "application/json"
    },
    sink = ltn12.sink.table(response_data)
})

if result then
    local data = table.concat(response_data)
    print("Response:", data)
else
    print("Request failed:", code)
end

POST Request with JSON Data

-- POST request with JSON body
local json = require("json")  -- assuming JSON library is available
local request_data = json.encode({
    name = "John Doe",
    email = "john@example.com"
})

local response_data = {}
local result, code = lunac.http.fetch("https://api.example.com/users", {
    method = "POST",
    headers = {
        ["Content-Type"] = "application/json",
        ["Content-Length"] = tostring(#request_data)
    },
    source = ltn12.source.string(request_data),
    sink = ltn12.sink.table(response_data)
})

if result then
    print("User created successfully")
else
    print("Failed to create user:", code)
end

Multiple Async Requests

-- Multiple asynchronous requests
local urls = {
    "https://api.example.com/users",
    "https://api.example.com/posts",
    "https://api.example.com/comments"
}

local completed = 0
local total = #urls

for i, url in ipairs(urls) do
    local response_data = {}
    lunac.http.noawait_fetch(url, {
        sink = ltn12.sink.table(response_data)
    }, function(result, code, headers, status)
        completed = completed + 1
        print("Request", i, "completed:", code)
        
        if completed == total then
            print("All requests completed!")
        end
    end)
end
Install Luna Framework