chore: import upstream snapshot with attribution
CI / compile_and_lint (push) Failing after 0s
CI / docker_build (linux/amd64, -linux-amd64-duckdb, duckdb) (push) Failing after 0s
CI / docker_build (linux/arm64, -linux-arm64, minimal) (push) Failing after 2s
CI / docker_build (linux/arm64, -linux-arm64-duckdb, duckdb) (push) Failing after 1s
CI / docker_build (linux/amd64, minimal) (push) Failing after 1s
CI / test (, sqlite, sqlite::memory:) (push) Has been skipped
CI / test (mssql, mssql, mssql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (mysql, mysql, mysql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (oracle, oracle, Driver=Oracle 21 ODBC driver;Dbq=//127.0.0.1:1521/FREEPDB1;Uid=root;Pwd=Password123!) (push) Has been skipped
CI / test (postgres, odbc, Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!, true) (push) Has been skipped
CI / test (postgres, postgres, postgres://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / playwright (push) Has been skipped
CI / docker_build (linux/arm/v7, -linux-arm-v7, minimal) (push) Failing after 0s
CI / hurl_examples (push) Failing after 8s
deploy website / deploy_official_site (push) Failing after 1s
CI / hurl (${{ matrix.example }}) (push) Has been skipped
CI / docker_push (duckdb) (push) Has been cancelled
CI / docker_push (minimal) (push) Has been cancelled
CI / windows_test (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:31:57 +08:00
commit d718c5a372
986 changed files with 74597 additions and 0 deletions
+169
View File
@@ -0,0 +1,169 @@
# SQLPage with NGINX Example
This example demonstrates how to set up SQLPage behind an NGINX reverse proxy using Docker Compose. It showcases various features such as rate limiting, URL rewriting, caching, and more.
## Overview
The setup consists of three main components:
1. SQLPage: The main application server
2. NGINX: The reverse proxy
3. MySQL: The database
## Getting Started
1. Clone the repository and navigate to the `examples/nginx` directory.
2. Start the services using Docker Compose:
```bash
docker compose up
```
3. Access the application at `http://localhost`.
## Docker Compose Configuration
The `docker-compose.yml` file defines the services.
### SQLPage Service
The SQLPage service uses the latest SQLPage development image, sets up necessary volume mounts for configuration (on `/etc/sqlpage`) and website (on `/var/www`) files, and establishes a connection to the MySQL database.
It reads http requests from a Unix socket (instead of a TCP socket) for communication with NGINX. This removes the overhead of TCP/IP when nginx and sqlpage are running on the same machine.
### NGINX Service
The NGINX service uses the official Alpine-based image. It exposes port 80 and mounts the SQLPage socket and the [custom NGINX configuration file](nginx/nginx.conf).
### MySQL Service
This service sets up a MySQL database with predefined credentials and a persistent volume for data storage.
## NGINX Configuration
The `nginx.conf` file contains the NGINX configuration:
### Streaming and compression
SQLPage streams HTML as it is generated, so browsers can start rendering before the database finishes returning rows. NGINX enables `proxy_buffering` by default, which can delay those first bytes but stores responses for slow clients. Start with a modest buffer configuration and let the proxy handle compression:
```
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 4 16k;
gzip on;
gzip_buffers 2 4k;
gzip_types text/html text/plain text/css application/javascript application/json;
chunked_transfer_encoding on;
```
Keep buffering when you expect slow clients or longer SQLPage queries, increasing the buffer sizes only if responses overflow. When most users are on fast connections reading lightweight pages, consider reducing the buffer counts or flipping to `proxy_buffering off;` to minimise latency, accepting the extra load on SQLPage. See the [proxy buffering](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering), [gzip](https://nginx.org/en/docs/http/ngx_http_gzip_module.html), and [chunked transfer](https://nginx.org/en/docs/http/ngx_http_core_module.html#chunked_transfer_encoding) directives for more guidance.
When SQLPage runs behind a reverse proxy, set `compress_responses` to `false` in its configuration (documented [here](https://github.com/sqlpage/SQLPage/blob/main/configuration.md)) so that NGINX can perform compression once at the edge.
### Rate Limiting
```nginx
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
```
This line defines a rate limiting zone that allows 1 request per second per IP address.
### Server Block
```nginx
server {
listen 80;
server_name localhost;
location / {
limit_req zone=one burst=5;
proxy_pass http://unix:/tmp/sqlpage/sqlpage.sock;
}
}
```
The server block defines how NGINX handles incoming requests.
#### URL rewriting:
```nginx
rewrite ^/post/([0-9]+)$ /post.sql?id=$1 last;
```
This line rewrites URLs like `/post/123` to `/post.sql?id=123`.
#### Proxy configuration:
```nginx
proxy_pass http://unix:/tmp/sqlpage/sqlpage.sock;
```
These lines configure NGINX to proxy requests to the SQLPage Unix socket.
#### Caching:
```nginx
# Enable caching
proxy_cache_valid 200 60m;
proxy_cache_valid 404 10m;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
```
These lines enable caching of responses from SQLPage.
#### Buffering:
```nginx
# Enable buffering
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
```
These lines configure response buffering for improved performance.
#### SQLPage Configuration
The SQLPage configuration is stored in `sqlpage_config/sqlpage.json`:
```json
{
"max_database_pool_connections": 10,
"database_connection_idle_timeout_seconds": 1800,
"max_uploaded_file_size": 10485760,
"compress_responses": false,
"environment": "production"
}
```
This configuration sets various SQLPage options, including the maximum number of database connections and the environment.
## Application Structure
The application consists of several SQL files in the `website` directory:
1. `index.sql`: Displays a list of blog posts
2. `post.sql`: Shows details of a specific post and its comments
3. `add_comment.sql`: Handles adding new comments
The database schema and initial data are defined in [`sqlpage_config/migrations/000_init.sql`](sqlpage_config/migrations/000_init.sql).
+51
View File
@@ -0,0 +1,51 @@
services:
init-sqlpage-socket:
image: alpine
volumes:
- sqlpage_socket:/tmp/sqlpage
command: ["chown", "1000:1000", "/tmp/sqlpage"]
sqlpage:
image: lovasoa/sqlpage:main
volumes:
- sqlpage_socket:/tmp/sqlpage
- ./sqlpage_config:/etc/sqlpage
- ./website:/var/www/
environment:
- DATABASE_URL=mysql://sqlpage:sqlpage_password@mysql:3306/sqlpage_db
- SQLPAGE_UNIX_SOCKET=/tmp/sqlpage/sqlpage.sock
depends_on:
init-sqlpage-socket:
condition: service_completed_successfully
mysql:
condition: service_started
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- sqlpage_socket:/tmp/sqlpage:ro
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./website:/var/www:ro
depends_on:
- sqlpage
command: >
sh -c "
adduser -D -u 1000 sqlpage || true &&
nginx -g 'daemon off;'
"
mysql:
image: mysql:8
environment:
- MYSQL_ROOT_PASSWORD=root_password
- MYSQL_DATABASE=sqlpage_db
- MYSQL_USER=sqlpage
- MYSQL_PASSWORD=sqlpage_password
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
sqlpage_socket:
+127
View File
@@ -0,0 +1,127 @@
# Specify the user under which nginx will run.
# This enhances security by not running as root.
# In our case, we are using the sqlpage user (created in the docker-compose.yml file)
# so that the NGINX worker processes can access the SQLPage socket.
user sqlpage;
# Set the number of worker processes. 'auto' detects the number of CPU cores.
# Alternative: Specific number like '4' for 4 worker processes.
worker_processes auto;
# Define the file where error logs will be written. 'notice' sets the logging level.
# Alternative levels: debug, info, warn, error, crit, alert, emerg
error_log /var/log/nginx/error.log notice;
# Specify the file where the main nginx process ID will be written
pid /var/run/nginx.pid;
# Configuration for connection processing
events {
# Maximum number of simultaneous connections that can be opened by a worker process
# Can be increased for high traffic sites, but limited by system resources
worker_connections 1024;
}
# Main HTTP server configuration block
# In a typical configuration, you would have one http block for all your applications
# and each application would be defined in a different file, in the /etc/nginx/sites-available/ directory
# and then enabled by creating a symlink to it in the /etc/nginx/sites-enabled/ directory.
http {
# This individual configuration files would start here, with only the contents
# from inside the http block.
# Include MIME types definitions file
include /etc/nginx/mime.types;
# Set the default MIME type if nginx can't determine it
default_type application/octet-stream;
# Define the format of the access log entries
# This log format includes various details about each request
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Specify the file where access logs will be written, using the 'main' format defined above
access_log /var/log/nginx/access.log main;
# Enable the use of sendfile() for serving static files, which can improve performance
sendfile on;
# Set the timeout for keep-alive connections with the client
# Can be adjusted based on your application's needs
keepalive_timeout 65;
# Define a rate limiting zone to protect against DDoS attacks
# $binary_remote_addr uses less memory than $remote_addr
# 10m defines the memory size for storing IP addresses
# 1r/s sets the maximum rate of requests per second from a single IP
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
# Server block defining a virtual host
server {
# Listen on port 80 for HTTP connections
# If you want to listen on port 443 for HTTPS, you can use the certbot command to get a certificate
# and automatically configure NGINX to use it:
# sudo certbot --nginx -d yourdomain.com
listen 80;
# Define the server name. 'localhost' is used here, but should be your domain in production
# server_name yourdomain.com;
server_name localhost;
# Configuration for serving static files
# Note the trailing slash in the location block
# It is necessary because we want to serve files from /var/www/static/
# and we want to allow users to request http://localhost/static/foo.js
# as well as http://localhost/static/dir/bar.js
location /static/ {
# Set the directory from which static files will be served
# This allows you to place static files in the `website/static/` directory
# and serve them at http://localhost:80/static/...
# This removes load from the SQLPage application that will only handle dynamic requests
alias /var/www/static/;
}
# Configuration for proxying requests to SQLPage
location / {
# Apply rate limiting to this location
# burst=5 allows temporary bursts of requests
# This is useful to avoid DoS attacks
limit_req zone=one burst=5;
# URL rewriting example for pretty URLs
# Rewrites /post/123 to /post.sql?id=123
rewrite ^/post/([0-9]+)$ /post.sql?id=$1 last;
# Proxy requests to a Unix socket where SQLPage is listening
proxy_pass http://unix:/tmp/sqlpage/sqlpage.sock;
# Set headers for the proxied request
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Enable caching of proxied content
# Cache successful responses for 60 minutes and 404 responses for 10 minutes
proxy_cache_valid 200 60m;
proxy_cache_valid 404 10m;
# Use stale cached content when upstream errors occur
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
# Enable buffering of responses from the proxied server
proxy_buffering on;
# Set the size of the buffer used for reading the first part of the response
proxy_buffer_size 128k;
# Set the number and size of buffers used for reading a response
proxy_buffers 4 256k;
# Limit the amount of data that can be stored in buffers while a response is being processed
proxy_busy_buffers_size 256k;
}
}
}
@@ -0,0 +1,37 @@
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE posts (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
title VARCHAR(255) NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE comments (
id INT AUTO_INCREMENT PRIMARY KEY,
post_id INT,
user_id INT,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
INSERT INTO users (username, email) VALUES
('john_doe', 'john@example.com'),
('jane_smith', 'jane@example.com');
INSERT INTO posts (user_id, title, content) VALUES
(1, 'First Post', 'This is the content of the first post.'),
(2, 'Hello World', 'Hello everyone! This is my first post.');
INSERT INTO comments (post_id, user_id, content) VALUES
(1, 2, 'Great post!'),
(2, 1, 'Welcome to the community!');
@@ -0,0 +1,7 @@
{
"max_database_pool_connections": 10,
"database_connection_idle_timeout_seconds": 1800,
"max_uploaded_file_size": 10485760,
"compress_responses": false,
"environment": "production"
}
+23
View File
@@ -0,0 +1,23 @@
# nginx proxies SQLPage dynamic pages and rewrites pretty post URLs.
GET http://localhost:8080/
HTTP 200
[Asserts]
header "Content-Type" contains "text/html"
body contains "Blog Posts"
body contains "First Post"
body contains "Hello World"
GET http://localhost:8080/post/1
HTTP 200
[Asserts]
header "Content-Type" contains "text/html"
body contains "Post Details"
body contains "This is the content of the first post."
body contains "Great post!"
body contains "Add a comment"
# /static/ is served by nginx directly, not proxied to SQLPage.
GET http://localhost:8080/static/index.sql
HTTP 404
[Asserts]
body contains "nginx"
+2
View File
@@ -0,0 +1,2 @@
INSERT INTO comments (post_id, user_id, content) VALUES ($id, 1, :content);
SELECT 'redirect' as component, '/post/' || $id AS link;
+10
View File
@@ -0,0 +1,10 @@
SELECT 'list' AS component, 'Blog Posts' AS title;
SELECT
p.title,
u.username AS description,
'user' AS icon,
'/post/' || p.id AS link
FROM posts p
JOIN users u ON p.user_id = u.id
ORDER BY p.created_at DESC;
+46
View File
@@ -0,0 +1,46 @@
-- Display the post content using the card component
SELECT 'card' as component,
'Post Details' as title,
1 as columns;
SELECT p.title as title,
u.username as subtitle,
p.content as description,
p.created_at as footer
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.id = $id;
-- Add a divider
SELECT 'divider' as component;
-- Display comments using the list component
SELECT 'list' as component,
'Comments' as title;
SELECT u.username as title,
c.content as description,
c.created_at as subtitle,
'user' as icon,
CASE
WHEN c.user_id = p.user_id THEN 'blue'
ELSE 'gray'
END as color
FROM comments c
JOIN users u ON c.user_id = u.id
JOIN posts p ON c.post_id = p.id
WHERE c.post_id = $id
ORDER BY c.created_at DESC;
-- Add a divider
SELECT 'divider' as component;
-- Add a comment form
SELECT 'form' as component,
'Add a comment' as title,
'Post comment' as validate,
'/add_comment.sql?id=' || $id as action;
SELECT 'textarea' as type,
'content' as name,
'Your comment' as label,
'Write your comment here' as placeholder,
true as required;