Add FastCGI Plugin for PHP-FPM support with examples, tests, and documentation
- Introduced `FastcgiPlugin` for automatic HAProxy `fcgi-app` configuration generation. - Added example Docker Compose setup for PHP-FPM with FastCGI. - Updated documentation with detailed examples and usage instructions for FastCGI. - Included test cases to validate plugin behavior and generated configurations. - Enhanced `easymapping` to support `fcgi-app` definitions in global configs.
This commit is contained in:
parent
a993025718
commit
883521e7e1
12 changed files with 1029 additions and 2 deletions
|
|
@ -312,6 +312,99 @@ http-request deny content-type 'text/html' string 'JWT has expired' if { var(txn
|
||||||
|
|
||||||
**Important:** Requires HAProxy 2.5+ with JWT support. Mount public key file as read-only volume.
|
**Important:** Requires HAProxy 2.5+ with JWT support. Mount public key file as read-only volume.
|
||||||
|
|
||||||
|
### FastCGI Plugin (Domain)
|
||||||
|
|
||||||
|
Configures FastCGI parameters for PHP-FPM and other FastCGI applications.
|
||||||
|
|
||||||
|
**Why use it:** Automatically generates HAProxy `fcgi-app` configuration that defines required CGI parameters for PHP-FPM communication without manual HAProxy configuration.
|
||||||
|
|
||||||
|
**Configuration options:**
|
||||||
|
- `enabled` - Enable/disable plugin (default: `true`)
|
||||||
|
- `document_root` - Document root path (default: `/var/www/html`)
|
||||||
|
- `script_filename` - Custom pattern for SCRIPT_FILENAME (default: `%[path]`, uses HAProxy's default)
|
||||||
|
- `index_file` - Default index file (default: `index.php`)
|
||||||
|
- `path_info` - Enable PATH_INFO support (default: `true`)
|
||||||
|
- `custom_params` - Dictionary of custom FastCGI parameters (optional)
|
||||||
|
|
||||||
|
**Enable via container label (TCP connection):**
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
php-fpm:
|
||||||
|
image: php:8.2-fpm
|
||||||
|
labels:
|
||||||
|
easyhaproxy.http.host: phpapp.local
|
||||||
|
easyhaproxy.http.port: 80
|
||||||
|
easyhaproxy.http.localport: 9000
|
||||||
|
easyhaproxy.http.proto: fcgi
|
||||||
|
easyhaproxy.http.plugins: fastcgi
|
||||||
|
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/html
|
||||||
|
easyhaproxy.http.plugin.fastcgi.index_file: index.php
|
||||||
|
volumes:
|
||||||
|
- ./app:/var/www/html
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or with Unix socket:**
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
php-fpm:
|
||||||
|
image: php:8.2-fpm
|
||||||
|
labels:
|
||||||
|
easyhaproxy.http.host: phpapp.local
|
||||||
|
easyhaproxy.http.socket: /run/php/php-fpm.sock
|
||||||
|
easyhaproxy.http.proto: fcgi
|
||||||
|
easyhaproxy.http.plugins: fastcgi
|
||||||
|
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/html
|
||||||
|
easyhaproxy.http.plugin.fastcgi.index_file: index.php
|
||||||
|
volumes:
|
||||||
|
- ./app:/var/www/html
|
||||||
|
- /run/php:/run/php
|
||||||
|
```
|
||||||
|
|
||||||
|
**Custom document root and index file:**
|
||||||
|
```yaml
|
||||||
|
labels:
|
||||||
|
easyhaproxy.http.plugins: fastcgi
|
||||||
|
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/myapp/public
|
||||||
|
easyhaproxy.http.plugin.fastcgi.index_file: app.php
|
||||||
|
easyhaproxy.http.plugin.fastcgi.path_info: true
|
||||||
|
```
|
||||||
|
|
||||||
|
**HAProxy config generated:**
|
||||||
|
|
||||||
|
The plugin generates a top-level `fcgi-app` section and a `use-fcgi-app` directive in the backend:
|
||||||
|
|
||||||
|
```haproxy
|
||||||
|
# Top-level fcgi-app definition (added after defaults, before frontends/backends)
|
||||||
|
fcgi-app fcgi_phpapp_local
|
||||||
|
docroot /var/www/html
|
||||||
|
index index.php
|
||||||
|
path-info ^(/.+\.php)(/.*)?$
|
||||||
|
|
||||||
|
# Backend configuration (added to the backend section)
|
||||||
|
backend srv_phpapp_local_80
|
||||||
|
use-fcgi-app fcgi_phpapp_local
|
||||||
|
# TCP connection:
|
||||||
|
server srv-0 172.19.0.3:9000 proto fcgi
|
||||||
|
# OR Unix socket:
|
||||||
|
# server srv-0 /run/php/php-fpm.sock proto fcgi
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** HAProxy automatically sets standard CGI parameters (SCRIPT_FILENAME, DOCUMENT_ROOT, REQUEST_URI, QUERY_STRING, REQUEST_METHOD, CONTENT_TYPE, CONTENT_LENGTH, SERVER_NAME, SERVER_PORT, etc.) based on the `fcgi-app` configuration when communicating with PHP-FPM via the FastCGI protocol.
|
||||||
|
|
||||||
|
**What it configures:**
|
||||||
|
- ✅ SCRIPT_FILENAME - Path to PHP script
|
||||||
|
- ✅ DOCUMENT_ROOT - Document root directory
|
||||||
|
- ✅ SCRIPT_NAME - Script name from URL
|
||||||
|
- ✅ REQUEST_URI - Full request URI with query string
|
||||||
|
- ✅ QUERY_STRING - URL query parameters
|
||||||
|
- ✅ REQUEST_METHOD - HTTP method (GET, POST, etc.)
|
||||||
|
- ✅ CONTENT_TYPE & CONTENT_LENGTH - Request body info
|
||||||
|
- ✅ SERVER_NAME & SERVER_PORT - Server details
|
||||||
|
- ✅ HTTPS - SSL/TLS status
|
||||||
|
- ✅ PATH_INFO - Path information (optional)
|
||||||
|
|
||||||
|
**Important:** Use this plugin together with `proto: fcgi` parameter for complete PHP-FPM support.
|
||||||
|
|
||||||
## Configuration Methods
|
## Configuration Methods
|
||||||
|
|
||||||
Plugins can be configured using different methods depending on your deployment environment:
|
Plugins can be configured using different methods depending on your deployment environment:
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,117 @@ labels:
|
||||||
|
|
||||||
## Plugin Examples
|
## Plugin Examples
|
||||||
|
|
||||||
|
### FastCGI Plugin with PHP-FPM
|
||||||
|
|
||||||
|
Run PHP applications with FastCGI protocol support:
|
||||||
|
|
||||||
|
**File:** `docker-compose-php-fpm.yml`
|
||||||
|
|
||||||
|
**What it demonstrates:**
|
||||||
|
- PHP-FPM 8.5 with TCP connection on port 9000
|
||||||
|
- FastCGI protocol support (`proto: fcgi`)
|
||||||
|
- FastCGI plugin for PHP environment configuration
|
||||||
|
- Custom document root and index file
|
||||||
|
- PATH_INFO support for RESTful routing
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- HAProxy forwards requests to PHP-FPM via TCP (port 9000)
|
||||||
|
- FastCGI plugin generates `fcgi-app` configuration that defines CGI parameters:
|
||||||
|
- `SCRIPT_FILENAME`, `DOCUMENT_ROOT`, `REQUEST_URI`
|
||||||
|
- `QUERY_STRING`, `REQUEST_METHOD`, `CONTENT_TYPE`
|
||||||
|
- `SERVER_NAME`, `SERVER_PORT`, `HTTPS`
|
||||||
|
- `PATH_INFO` (for routing support)
|
||||||
|
- Sample PHP application included in `php-app/` directory
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
```yaml
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
haproxy:
|
||||||
|
image: byjg/easy-haproxy:4.6.0
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
environment:
|
||||||
|
EASYHAPROXY_DISCOVER: docker
|
||||||
|
ports:
|
||||||
|
- "80:80/tcp"
|
||||||
|
|
||||||
|
php-fpm:
|
||||||
|
image: byjg/php:8.5-fpm
|
||||||
|
volumes:
|
||||||
|
- ./php-app:/var/www/html:ro
|
||||||
|
labels:
|
||||||
|
easyhaproxy.http.host: phpapp.local
|
||||||
|
easyhaproxy.http.port: 80
|
||||||
|
# PHP-FPM listens on port 9000
|
||||||
|
easyhaproxy.http.localport: 9000
|
||||||
|
easyhaproxy.http.proto: fcgi
|
||||||
|
# Enable FastCGI plugin
|
||||||
|
easyhaproxy.http.plugins: fastcgi
|
||||||
|
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/html
|
||||||
|
easyhaproxy.http.plugin.fastcgi.index_file: index.php
|
||||||
|
easyhaproxy.http.plugin.fastcgi.path_info: "true"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
# Add to /etc/hosts
|
||||||
|
echo "127.0.0.1 phpapp.local" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
|
# Start the stack
|
||||||
|
docker compose -f docker-compose-php-fpm.yml up -d
|
||||||
|
|
||||||
|
# Test PHP application
|
||||||
|
curl http://phpapp.local/
|
||||||
|
curl http://phpapp.local/info.php
|
||||||
|
curl http://phpapp.local/test-path-info.php/users/123
|
||||||
|
```
|
||||||
|
|
||||||
|
**Alternative: Unix Socket Connection**
|
||||||
|
|
||||||
|
For PHP-FPM images that support Unix sockets, you can use socket connection:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
haproxy:
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- php-fpm-socket:/run/php
|
||||||
|
|
||||||
|
php-fpm:
|
||||||
|
image: php:8.2-fpm # Official PHP image supports sockets
|
||||||
|
volumes:
|
||||||
|
- php-fpm-socket:/run/php
|
||||||
|
- ./php-app:/var/www/html:ro
|
||||||
|
labels:
|
||||||
|
easyhaproxy.http.host: phpapp.local
|
||||||
|
easyhaproxy.http.port: 80
|
||||||
|
easyhaproxy.http.socket: /run/php/php-fpm.sock
|
||||||
|
easyhaproxy.http.proto: fcgi
|
||||||
|
easyhaproxy.http.plugins: fastcgi
|
||||||
|
# ... plugin configuration
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
php-fpm-socket:
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sample Application:**
|
||||||
|
|
||||||
|
The `php-app/` directory contains:
|
||||||
|
- `index.php` - Main page showing FastCGI environment
|
||||||
|
- `info.php` - PHP configuration info (phpinfo)
|
||||||
|
- `test-path-info.php` - PATH_INFO routing demonstration
|
||||||
|
|
||||||
|
**What the FastCGI plugin does:**
|
||||||
|
1. Sets `SCRIPT_FILENAME` with proper document root path
|
||||||
|
2. Handles directory requests (appends `index.php`)
|
||||||
|
3. Sets all standard CGI environment variables
|
||||||
|
4. Enables `PATH_INFO` for RESTful URL routing
|
||||||
|
5. Supports custom FastCGI parameters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### JWT Validator Plugin
|
### JWT Validator Plugin
|
||||||
|
|
||||||
Protect your API with JWT token validation:
|
Protect your API with JWT token validation:
|
||||||
|
|
|
||||||
60
examples/docker/docker-compose-php-fpm.yml
Normal file
60
examples/docker/docker-compose-php-fpm.yml
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# FastCGI Plugin Example with PHP-FPM
|
||||||
|
#
|
||||||
|
# This example demonstrates PHP-FPM configuration with FastCGI protocol support
|
||||||
|
# using HAProxy as a reverse proxy and the FastCGI plugin for PHP environment setup.
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# 1. Add to /etc/hosts:
|
||||||
|
# 127.0.0.1 phpapp.local
|
||||||
|
#
|
||||||
|
# 2. Start the stack:
|
||||||
|
# docker compose -f docker-compose-php-fpm.yml up -d
|
||||||
|
#
|
||||||
|
# 3. Test PHP application:
|
||||||
|
# curl http://phpapp.local/
|
||||||
|
# curl http://phpapp.local/info.php
|
||||||
|
#
|
||||||
|
# Features:
|
||||||
|
# - PHP-FPM 8.5 with TCP connection on port 9000
|
||||||
|
# - FastCGI protocol support
|
||||||
|
# - Custom document root
|
||||||
|
# - PATH_INFO support for routing
|
||||||
|
# - Custom FastCGI parameters
|
||||||
|
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
haproxy:
|
||||||
|
image: byjg/easy-haproxy:4.6.0
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
environment:
|
||||||
|
EASYHAPROXY_DISCOVER: docker
|
||||||
|
HAPROXY_CUSTOMERRORS: "true"
|
||||||
|
HAPROXY_USERNAME: admin
|
||||||
|
HAPROXY_PASSWORD: password
|
||||||
|
HAPROXY_STATS_PORT: 1936
|
||||||
|
ports:
|
||||||
|
- "80:80/tcp"
|
||||||
|
- "1936:1936/tcp"
|
||||||
|
|
||||||
|
# PHP-FPM service using byjg/php image
|
||||||
|
php-fpm:
|
||||||
|
image: byjg/php:8.5-fpm
|
||||||
|
volumes:
|
||||||
|
# Mount PHP application files
|
||||||
|
- ./php-app:/var/www/html:ro
|
||||||
|
labels:
|
||||||
|
easyhaproxy.http.host: phpapp.local
|
||||||
|
easyhaproxy.http.port: 80
|
||||||
|
# PHP-FPM listens on port 9000
|
||||||
|
easyhaproxy.http.localport: 9000
|
||||||
|
easyhaproxy.http.proto: fcgi
|
||||||
|
|
||||||
|
# Enable FastCGI plugin for PHP environment configuration
|
||||||
|
easyhaproxy.http.plugins: fastcgi
|
||||||
|
|
||||||
|
# FastCGI plugin configuration
|
||||||
|
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/html
|
||||||
|
easyhaproxy.http.plugin.fastcgi.index_file: index.php
|
||||||
|
easyhaproxy.http.plugin.fastcgi.path_info: "true"
|
||||||
151
examples/docker/php-app/README.md
Normal file
151
examples/docker/php-app/README.md
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
# Sample PHP Application for FastCGI Plugin
|
||||||
|
|
||||||
|
This directory contains a sample PHP application that demonstrates the FastCGI plugin functionality with EasyHAProxy.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
### index.php
|
||||||
|
The main page that displays:
|
||||||
|
- PHP version and configuration
|
||||||
|
- FastCGI environment variables set by EasyHAProxy
|
||||||
|
- How the FastCGI plugin works
|
||||||
|
- Links to test pages
|
||||||
|
|
||||||
|
Access: `http://phpapp.local/`
|
||||||
|
|
||||||
|
### info.php
|
||||||
|
Standard `phpinfo()` page showing complete PHP configuration.
|
||||||
|
|
||||||
|
Access: `http://phpapp.local/info.php`
|
||||||
|
|
||||||
|
### test-path-info.php
|
||||||
|
Demonstrates PATH_INFO support for RESTful URL routing.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- `http://phpapp.local/test-path-info.php/users`
|
||||||
|
- `http://phpapp.local/test-path-info.php/users/123`
|
||||||
|
- `http://phpapp.local/test-path-info.php/api/v1/products`
|
||||||
|
|
||||||
|
## FastCGI Environment Variables
|
||||||
|
|
||||||
|
The FastCGI plugin generates an `fcgi-app` configuration that defines these CGI parameters for HAProxy to use:
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `SCRIPT_FILENAME` | Full path to PHP script | `/var/www/html/index.php` |
|
||||||
|
| `DOCUMENT_ROOT` | Document root directory | `/var/www/html` |
|
||||||
|
| `SCRIPT_NAME` | Script path | `/index.php` |
|
||||||
|
| `REQUEST_URI` | Full request URI with query | `/index.php?page=1` |
|
||||||
|
| `QUERY_STRING` | Query string parameters | `page=1&limit=10` |
|
||||||
|
| `REQUEST_METHOD` | HTTP method | `GET`, `POST`, etc. |
|
||||||
|
| `CONTENT_TYPE` | Request content type | `application/json` |
|
||||||
|
| `CONTENT_LENGTH` | Request body length | `1024` |
|
||||||
|
| `SERVER_NAME` | Virtual host name | `phpapp.local` |
|
||||||
|
| `SERVER_PORT` | Server port | `80` or `443` |
|
||||||
|
| `HTTPS` | SSL status | `on` or `off` |
|
||||||
|
| `PATH_INFO` | Extra path info (optional) | `/users/123` |
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **FastCGI plugin generates configuration** (at startup)
|
||||||
|
- Creates an `fcgi-app` section with CGI parameter definitions
|
||||||
|
- Includes `docroot`, `index`, and `path-info` settings
|
||||||
|
- Adds `use-fcgi-app` directive to the backend
|
||||||
|
|
||||||
|
2. **Request arrives at HAProxy** (port 80)
|
||||||
|
- URL: `http://phpapp.local/index.php`
|
||||||
|
|
||||||
|
3. **HAProxy uses the fcgi-app configuration**
|
||||||
|
- Sets `SCRIPT_FILENAME` to `/var/www/html/index.php`
|
||||||
|
- Sets `DOCUMENT_ROOT` to `/var/www/html`
|
||||||
|
- Sets all other CGI variables based on the request
|
||||||
|
- Handles directory requests (appends `index.php`)
|
||||||
|
|
||||||
|
4. **HAProxy forwards to PHP-FPM** via FastCGI protocol
|
||||||
|
- Host: `php-fpm` (container name)
|
||||||
|
- Port: `9000` (TCP) or Unix socket
|
||||||
|
- Protocol: `fcgi`
|
||||||
|
- Sends CGI parameters in FastCGI format
|
||||||
|
|
||||||
|
5. **PHP-FPM executes the script**
|
||||||
|
- Reads the PHP file from `SCRIPT_FILENAME`
|
||||||
|
- Processes the PHP code with CGI environment
|
||||||
|
- Returns HTML/JSON response
|
||||||
|
|
||||||
|
6. **HAProxy sends response to client**
|
||||||
|
|
||||||
|
## Customizing
|
||||||
|
|
||||||
|
You can customize the FastCGI plugin configuration in `docker-compose-php-fpm.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
labels:
|
||||||
|
# Change document root
|
||||||
|
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/public
|
||||||
|
|
||||||
|
# Change default index file
|
||||||
|
easyhaproxy.http.plugin.fastcgi.index_file: app.php
|
||||||
|
|
||||||
|
# Disable PATH_INFO
|
||||||
|
easyhaproxy.http.plugin.fastcgi.path_info: "false"
|
||||||
|
|
||||||
|
# Add custom FastCGI parameters
|
||||||
|
easyhaproxy.http.plugin.fastcgi.custom_params: '{"PHP_VALUE":"memory_limit=256M","APP_ENV":"production"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding Your Own PHP Application
|
||||||
|
|
||||||
|
Replace the contents of this directory with your own PHP application:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove sample files
|
||||||
|
rm -rf php-app/*
|
||||||
|
|
||||||
|
# Copy your PHP application
|
||||||
|
cp -r /path/to/your/app/* php-app/
|
||||||
|
|
||||||
|
# Restart the stack
|
||||||
|
docker compose -f docker-compose-php-fpm.yml restart
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure your application's entry point matches the `index_file` configuration (default: `index.php`).
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "File not found" error
|
||||||
|
|
||||||
|
Check that:
|
||||||
|
1. The file exists in the `php-app/` directory
|
||||||
|
2. The `document_root` matches the container path (`/var/www/html`)
|
||||||
|
3. The volume mount is correct in docker-compose.yml
|
||||||
|
|
||||||
|
### PATH_INFO not working
|
||||||
|
|
||||||
|
Ensure `path_info` is enabled in the plugin configuration:
|
||||||
|
```yaml
|
||||||
|
easyhaproxy.http.plugin.fastcgi.path_info: "true"
|
||||||
|
```
|
||||||
|
|
||||||
|
### PHP-FPM connection error
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
1. The `localport: 9000` is set correctly
|
||||||
|
2. The `proto: fcgi` parameter is set
|
||||||
|
3. Both containers are running and can communicate
|
||||||
|
|
||||||
|
View logs:
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose-php-fpm.yml logs php-fpm
|
||||||
|
docker compose -f docker-compose-php-fpm.yml logs haproxy
|
||||||
|
```
|
||||||
|
|
||||||
|
Check connectivity:
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose-php-fpm.yml exec haproxy ping php-fpm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
- [FastCGI Plugin Documentation](../../../docs/plugins.md#fastcgi-plugin)
|
||||||
|
- [Container Labels Reference](../../../docs/container-labels.md)
|
||||||
|
- [HAProxy FastCGI Documentation](https://docs.haproxy.org/2.8/configuration.html#5.2-proto)
|
||||||
152
examples/docker/php-app/index.php
Normal file
152
examples/docker/php-app/index.php
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PHP-FPM with EasyHAProxy</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 3px solid #4CAF50;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
background: #d4edda;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
color: #155724;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.info-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.info-table th,
|
||||||
|
.info-table td {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
.info-table th {
|
||||||
|
background: #f8f9fa;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.info-table tr:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #4CAF50;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>PHP-FPM with EasyHAProxy FastCGI Plugin</h1>
|
||||||
|
|
||||||
|
<div class="success">
|
||||||
|
<strong>Success!</strong> PHP is running via FastCGI protocol through HAProxy.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>FastCGI Environment</h2>
|
||||||
|
<table class="info-table">
|
||||||
|
<tr>
|
||||||
|
<th>PHP Version</th>
|
||||||
|
<td><?php echo PHP_VERSION; ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Server Software</th>
|
||||||
|
<td><?php echo $_SERVER['SERVER_SOFTWARE'] ?? 'N/A'; ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Document Root</th>
|
||||||
|
<td><code><?php echo $_SERVER['DOCUMENT_ROOT'] ?? 'N/A'; ?></code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Script Filename</th>
|
||||||
|
<td><code><?php echo $_SERVER['SCRIPT_FILENAME'] ?? 'N/A'; ?></code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Request URI</th>
|
||||||
|
<td><code><?php echo $_SERVER['REQUEST_URI'] ?? 'N/A'; ?></code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Request Method</th>
|
||||||
|
<td><?php echo $_SERVER['REQUEST_METHOD'] ?? 'N/A'; ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Server Name</th>
|
||||||
|
<td><?php echo $_SERVER['SERVER_NAME'] ?? 'N/A'; ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Server Port</th>
|
||||||
|
<td><?php echo $_SERVER['SERVER_PORT'] ?? 'N/A'; ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>HTTPS</th>
|
||||||
|
<td><?php echo ($_SERVER['HTTPS'] ?? 'off') === 'on' ? 'Yes' : 'No'; ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>PATH_INFO</th>
|
||||||
|
<td><code><?php echo $_SERVER['PATH_INFO'] ?? 'Not set'; ?></code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Gateway Interface</th>
|
||||||
|
<td><?php echo $_SERVER['GATEWAY_INTERFACE'] ?? 'N/A'; ?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Test Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/info.php">View PHP Info</a></li>
|
||||||
|
<li><a href="/test-path-info.php/extra/path">Test PATH_INFO support</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>How This Works</h2>
|
||||||
|
<p>
|
||||||
|
This setup uses HAProxy with EasyHAProxy to proxy requests to PHP-FPM via the FastCGI protocol:
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<li>HAProxy receives HTTP request on port 80</li>
|
||||||
|
<li>The FastCGI plugin generates an <code>fcgi-app</code> configuration that defines CGI parameters (SCRIPT_FILENAME, DOCUMENT_ROOT, etc.)</li>
|
||||||
|
<li>HAProxy uses this configuration to communicate with PHP-FPM via the FastCGI protocol</li>
|
||||||
|
<li>HAProxy connects to PHP-FPM (via TCP port 9000 or Unix socket, depending on configuration)</li>
|
||||||
|
<li>PHP-FPM processes the PHP script and returns the response</li>
|
||||||
|
<li>HAProxy sends the response back to the client</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Configuration</h2>
|
||||||
|
<p>The FastCGI plugin is configured in <code>docker-compose-php-fpm.yml</code>:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>document_root:</strong> <code>/var/www/html</code></li>
|
||||||
|
<li><strong>index_file:</strong> <code>index.php</code></li>
|
||||||
|
<li><strong>path_info:</strong> <code>true</code> (enables PATH_INFO support)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9
examples/docker/php-app/info.php
Normal file
9
examples/docker/php-app/info.php
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* PHP Info Page
|
||||||
|
*
|
||||||
|
* This page displays comprehensive PHP configuration information
|
||||||
|
* including FastCGI environment variables set by EasyHAProxy.
|
||||||
|
*/
|
||||||
|
|
||||||
|
phpinfo();
|
||||||
106
examples/docker/php-app/test-path-info.php
Normal file
106
examples/docker/php-app/test-path-info.php
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PATH_INFO Test</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 3px solid #2196F3;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
background: #d4edda;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
color: #155724;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
background: #d1ecf1;
|
||||||
|
border: 1px solid #bee5eb;
|
||||||
|
color: #0c5460;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #2196F3;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>PATH_INFO Test</h1>
|
||||||
|
|
||||||
|
<?php if (isset($_SERVER['PATH_INFO']) && !empty($_SERVER['PATH_INFO'])): ?>
|
||||||
|
<div class="success">
|
||||||
|
<strong>Success!</strong> PATH_INFO is working correctly.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>PATH_INFO Value</h2>
|
||||||
|
<pre><?php echo htmlspecialchars($_SERVER['PATH_INFO']); ?></pre>
|
||||||
|
|
||||||
|
<h2>Parsed Path Segments</h2>
|
||||||
|
<pre><?php
|
||||||
|
$segments = explode('/', trim($_SERVER['PATH_INFO'], '/'));
|
||||||
|
print_r(array_filter($segments));
|
||||||
|
?></pre>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="info">
|
||||||
|
<strong>Note:</strong> PATH_INFO is not set. Try accessing this page with additional path segments.
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<h2>Request Information</h2>
|
||||||
|
<pre><?php
|
||||||
|
echo "SCRIPT_NAME: " . ($_SERVER['SCRIPT_NAME'] ?? 'N/A') . "\n";
|
||||||
|
echo "REQUEST_URI: " . ($_SERVER['REQUEST_URI'] ?? 'N/A') . "\n";
|
||||||
|
echo "PATH_INFO: " . ($_SERVER['PATH_INFO'] ?? 'N/A') . "\n";
|
||||||
|
echo "QUERY_STRING: " . ($_SERVER['QUERY_STRING'] ?? 'N/A') . "\n";
|
||||||
|
?></pre>
|
||||||
|
|
||||||
|
<h2>Example Usage</h2>
|
||||||
|
<p>PATH_INFO enables RESTful URL routing. Try these URLs:</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/test-path-info.php/users">/test-path-info.php/users</a></li>
|
||||||
|
<li><a href="/test-path-info.php/users/123">/test-path-info.php/users/123</a></li>
|
||||||
|
<li><a href="/test-path-info.php/api/v1/products">/test-path-info.php/api/v1/products</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><a href="/">← Back to Home</a></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -98,7 +98,9 @@ class HaproxyConfigGenerator:
|
||||||
enabled_list = []
|
enabled_list = []
|
||||||
|
|
||||||
global_results = self.plugin_manager.execute_global_plugins(global_context, enabled_list)
|
global_results = self.plugin_manager.execute_global_plugins(global_context, enabled_list)
|
||||||
self.global_plugin_configs = [r.haproxy_config for r in global_results if r.haproxy_config]
|
# Extend instead of replace to preserve fcgi-app definitions from domain plugins
|
||||||
|
global_configs = [r.haproxy_config for r in global_results if r.haproxy_config]
|
||||||
|
self.global_plugin_configs.extend(global_configs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
import logging
|
||||||
logging.warning(f"Failed to execute global plugins: {e}")
|
logging.warning(f"Failed to execute global plugins: {e}")
|
||||||
|
|
@ -265,6 +267,12 @@ class HaproxyConfigGenerator:
|
||||||
easymapping[port]["hosts"][hostname]["plugin_configs"] = [
|
easymapping[port]["hosts"][hostname]["plugin_configs"] = [
|
||||||
r.haproxy_config for r in domain_results if r.haproxy_config
|
r.haproxy_config for r in domain_results if r.haproxy_config
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Extract fcgi-app definitions from metadata and add to global configs
|
||||||
|
for result in domain_results:
|
||||||
|
if result.metadata and "fcgi_app_definition" in result.metadata:
|
||||||
|
if result.metadata["fcgi_app_definition"] not in self.global_plugin_configs:
|
||||||
|
self.global_plugin_configs.append(result.metadata["fcgi_app_definition"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
import logging
|
||||||
logging.warning(f"Failed to execute domain plugins for {hostname}: {e}")
|
logging.warning(f"Failed to execute domain plugins for {hostname}: {e}")
|
||||||
|
|
|
||||||
155
src/plugins/builtin/fastcgi.py
Normal file
155
src/plugins/builtin/fastcgi.py
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
"""
|
||||||
|
FastCGI Plugin for EasyHAProxy
|
||||||
|
|
||||||
|
This plugin generates HAProxy fcgi-app configuration for PHP-FPM and other FastCGI applications.
|
||||||
|
It runs as a DOMAIN plugin (once per domain).
|
||||||
|
|
||||||
|
The plugin creates:
|
||||||
|
1. A top-level fcgi-app section with CGI parameter definitions
|
||||||
|
2. A use-fcgi-app directive in the backend
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
- enabled: Enable/disable the plugin (default: true)
|
||||||
|
- document_root: Document root path (default: /var/www/html)
|
||||||
|
- script_filename: Pattern for SCRIPT_FILENAME (default: %[path])
|
||||||
|
- index_file: Default index file (default: index.php)
|
||||||
|
- path_info: Enable PATH_INFO support (default: true)
|
||||||
|
- custom_params: Dictionary of custom FastCGI parameters (optional)
|
||||||
|
|
||||||
|
Example YAML config:
|
||||||
|
plugins:
|
||||||
|
fastcgi:
|
||||||
|
enabled: true
|
||||||
|
document_root: /var/www/html
|
||||||
|
index_file: index.php
|
||||||
|
path_info: true
|
||||||
|
|
||||||
|
Example Container Label:
|
||||||
|
easyhaproxy.http.plugins: "fastcgi"
|
||||||
|
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/myapp
|
||||||
|
easyhaproxy.http.plugin.fastcgi.index_file: index.php
|
||||||
|
easyhaproxy.http.plugin.fastcgi.path_info: true
|
||||||
|
|
||||||
|
Example Kubernetes Annotation:
|
||||||
|
easyhaproxy.plugins: "fastcgi"
|
||||||
|
easyhaproxy.plugin.fastcgi.document_root: /var/www/myapp
|
||||||
|
easyhaproxy.plugin.fastcgi.index_file: index.php
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Add parent directory to path for imports
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from plugins import PluginInterface, PluginType, PluginContext, PluginResult
|
||||||
|
from functions import loggerEasyHaproxy
|
||||||
|
|
||||||
|
|
||||||
|
class FastcgiPlugin(PluginInterface):
|
||||||
|
"""Plugin to configure FastCGI parameters for PHP-FPM"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.enabled = True
|
||||||
|
self.document_root = "/var/www/html"
|
||||||
|
self.script_filename = "%[path]"
|
||||||
|
self.index_file = "index.php"
|
||||||
|
self.path_info = True
|
||||||
|
self.custom_params = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return "fastcgi"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plugin_type(self) -> PluginType:
|
||||||
|
return PluginType.DOMAIN
|
||||||
|
|
||||||
|
def configure(self, config: dict) -> None:
|
||||||
|
"""
|
||||||
|
Configure the plugin
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: Dictionary with configuration options
|
||||||
|
- enabled: Whether plugin is enabled
|
||||||
|
- document_root: Document root path
|
||||||
|
- script_filename: Pattern for SCRIPT_FILENAME
|
||||||
|
- index_file: Default index file
|
||||||
|
- path_info: Enable PATH_INFO support
|
||||||
|
- custom_params: Dictionary of custom FastCGI parameters
|
||||||
|
"""
|
||||||
|
if "enabled" in config:
|
||||||
|
self.enabled = str(config["enabled"]).lower() in ["true", "1", "yes"]
|
||||||
|
|
||||||
|
if "document_root" in config:
|
||||||
|
self.document_root = config["document_root"]
|
||||||
|
|
||||||
|
if "script_filename" in config:
|
||||||
|
self.script_filename = config["script_filename"]
|
||||||
|
|
||||||
|
if "index_file" in config:
|
||||||
|
self.index_file = config["index_file"]
|
||||||
|
|
||||||
|
if "path_info" in config:
|
||||||
|
self.path_info = str(config["path_info"]).lower() in ["true", "1", "yes"]
|
||||||
|
|
||||||
|
if "custom_params" in config:
|
||||||
|
self.custom_params = config["custom_params"]
|
||||||
|
|
||||||
|
def process(self, context: PluginContext) -> PluginResult:
|
||||||
|
"""
|
||||||
|
Process the plugin and generate FastCGI configuration
|
||||||
|
|
||||||
|
Args:
|
||||||
|
context: Plugin execution context
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PluginResult with HAProxy FastCGI configuration
|
||||||
|
"""
|
||||||
|
if not self.enabled:
|
||||||
|
return PluginResult()
|
||||||
|
|
||||||
|
# Generate a unique fcgi-app name based on the domain
|
||||||
|
# Replace dots and colons with underscores for valid HAProxy identifier
|
||||||
|
domain_safe = context.domain.replace(".", "_").replace(":", "_")
|
||||||
|
fcgi_app_name = f"fcgi_{domain_safe}"
|
||||||
|
|
||||||
|
# Generate the use-fcgi-app directive for the backend
|
||||||
|
backend_config = f"use-fcgi-app {fcgi_app_name}"
|
||||||
|
|
||||||
|
# Generate the fcgi-app section (to be inserted at top level)
|
||||||
|
fcgi_app_lines = [f"fcgi-app {fcgi_app_name}"]
|
||||||
|
fcgi_app_lines.append(f" docroot {self.document_root}")
|
||||||
|
fcgi_app_lines.append(f" index {self.index_file}")
|
||||||
|
|
||||||
|
# PATH_INFO support
|
||||||
|
if self.path_info:
|
||||||
|
fcgi_app_lines.append(f" path-info ^(/.+\\.php)(/.*)?$")
|
||||||
|
|
||||||
|
# Set SCRIPT_FILENAME if customized
|
||||||
|
if self.script_filename and self.script_filename != "%[path]":
|
||||||
|
fcgi_app_lines.append(f" set-param SCRIPT_FILENAME {self.script_filename}")
|
||||||
|
|
||||||
|
# Custom parameters
|
||||||
|
if self.custom_params:
|
||||||
|
for param_name, param_value in self.custom_params.items():
|
||||||
|
fcgi_app_lines.append(f" set-param {param_name.upper()} {param_value}")
|
||||||
|
|
||||||
|
fcgi_app_definition = "\n".join(fcgi_app_lines)
|
||||||
|
|
||||||
|
# Build metadata - store fcgi_app_definition to be extracted and added to global configs
|
||||||
|
metadata = {
|
||||||
|
"domain": context.domain,
|
||||||
|
"fcgi_app_name": fcgi_app_name,
|
||||||
|
"fcgi_app_definition": fcgi_app_definition, # For top-level injection
|
||||||
|
"document_root": self.document_root,
|
||||||
|
"index_file": self.index_file,
|
||||||
|
"path_info": self.path_info,
|
||||||
|
"custom_params_count": len(self.custom_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PluginResult(
|
||||||
|
haproxy_config=backend_config, # use-fcgi-app directive for the backend
|
||||||
|
modified_easymapping=None,
|
||||||
|
metadata=metadata
|
||||||
|
)
|
||||||
56
src/tests/expected/services-fcgi.txt
Normal file
56
src/tests/expected/services-fcgi.txt
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
global
|
||||||
|
log stdout format raw local0 info
|
||||||
|
maxconn 2000
|
||||||
|
tune.ssl.default-dh-param 2048
|
||||||
|
|
||||||
|
# intermediate configuration
|
||||||
|
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
|
||||||
|
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
|
||||||
|
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
|
||||||
|
|
||||||
|
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
|
||||||
|
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
|
||||||
|
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
|
||||||
|
|
||||||
|
ssl-dh-param-file /etc/haproxy/dhparam
|
||||||
|
|
||||||
|
defaults
|
||||||
|
log global
|
||||||
|
option httplog
|
||||||
|
|
||||||
|
timeout connect 3s
|
||||||
|
timeout client 10s
|
||||||
|
timeout server 10m
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
frontend http_in_80
|
||||||
|
bind *:80
|
||||||
|
mode http
|
||||||
|
|
||||||
|
acl is_rule_phpapp_local_80_1 hdr(host) -i phpapp.local
|
||||||
|
acl is_rule_phpapp_local_80_2 hdr(host) -i phpapp.local:80
|
||||||
|
use_backend srv_phpapp_local_80 if is_rule_phpapp_local_80_1 OR is_rule_phpapp_local_80_2
|
||||||
|
|
||||||
|
acl is_rule_phpapp-tcp_local_80_1 hdr(host) -i phpapp-tcp.local
|
||||||
|
acl is_rule_phpapp-tcp_local_80_2 hdr(host) -i phpapp-tcp.local:80
|
||||||
|
use_backend srv_phpapp-tcp_local_80 if is_rule_phpapp-tcp_local_80_1 OR is_rule_phpapp-tcp_local_80_2
|
||||||
|
|
||||||
|
backend srv_phpapp_local_80
|
||||||
|
balance roundrobin
|
||||||
|
mode http
|
||||||
|
option forwardfor
|
||||||
|
http-request set-header X-Forwarded-Port %[dst_port]
|
||||||
|
http-request add-header X-Forwarded-Proto https if { ssl_fc }
|
||||||
|
server srv-0 /run/php/php-fpm.sock check weight 1 proto fcgi
|
||||||
|
backend srv_phpapp-tcp_local_80
|
||||||
|
balance roundrobin
|
||||||
|
mode http
|
||||||
|
option forwardfor
|
||||||
|
http-request set-header X-Forwarded-Port %[dst_port]
|
||||||
|
http-request add-header X-Forwarded-Proto https if { ssl_fc }
|
||||||
|
server srv-0 172.17.0.3:9000 check weight 1 proto fcgi
|
||||||
|
|
||||||
|
backend certbot_backend
|
||||||
|
mode http
|
||||||
|
server certbot 127.0.0.1:2080
|
||||||
16
src/tests/fixtures/services-fcgi
vendored
Normal file
16
src/tests/fixtures/services-fcgi
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"172.17.0.2": {
|
||||||
|
"easyhaproxy.definitions": "fcgi",
|
||||||
|
"easyhaproxy.fcgi.host": "phpapp.local",
|
||||||
|
"easyhaproxy.fcgi.port": "80",
|
||||||
|
"easyhaproxy.fcgi.socket": "/run/php/php-fpm.sock",
|
||||||
|
"easyhaproxy.fcgi.proto": "fcgi"
|
||||||
|
},
|
||||||
|
"172.17.0.3": {
|
||||||
|
"easyhaproxy.definitions": "fcgi-tcp",
|
||||||
|
"easyhaproxy.fcgi-tcp.host": "phpapp-tcp.local",
|
||||||
|
"easyhaproxy.fcgi-tcp.port": "80",
|
||||||
|
"easyhaproxy.fcgi-tcp.localport": "9000",
|
||||||
|
"easyhaproxy.fcgi-tcp.proto": "fcgi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ from plugins.builtin.cleanup import CleanupPlugin
|
||||||
from plugins.builtin.deny_pages import DenyPagesPlugin
|
from plugins.builtin.deny_pages import DenyPagesPlugin
|
||||||
from plugins.builtin.ip_whitelist import IpWhitelistPlugin
|
from plugins.builtin.ip_whitelist import IpWhitelistPlugin
|
||||||
from plugins.builtin.jwt_validator import JwtValidatorPlugin
|
from plugins.builtin.jwt_validator import JwtValidatorPlugin
|
||||||
|
from plugins.builtin.fastcgi import FastcgiPlugin
|
||||||
import easymapping
|
import easymapping
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -736,6 +737,114 @@ class TestJwtValidatorPlugin:
|
||||||
assert result.metadata["path_validation"] is False
|
assert result.metadata["path_validation"] is False
|
||||||
|
|
||||||
|
|
||||||
|
class TestFastcgiPlugin:
|
||||||
|
"""Test cases for FastcgiPlugin"""
|
||||||
|
|
||||||
|
def test_fastcgi_plugin_initialization(self):
|
||||||
|
"""Test plugin initializes with correct defaults"""
|
||||||
|
plugin = FastcgiPlugin()
|
||||||
|
|
||||||
|
assert plugin.name == "fastcgi"
|
||||||
|
assert plugin.enabled is True
|
||||||
|
assert plugin.document_root == "/var/www/html"
|
||||||
|
assert plugin.index_file == "index.php"
|
||||||
|
assert plugin.path_info is True
|
||||||
|
assert plugin.custom_params == {}
|
||||||
|
|
||||||
|
def test_fastcgi_plugin_configuration(self):
|
||||||
|
"""Test plugin configuration"""
|
||||||
|
plugin = FastcgiPlugin()
|
||||||
|
plugin.configure({
|
||||||
|
"document_root": "/var/www/myapp",
|
||||||
|
"index_file": "app.php",
|
||||||
|
"path_info": "false"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert plugin.document_root == "/var/www/myapp"
|
||||||
|
assert plugin.index_file == "app.php"
|
||||||
|
assert plugin.path_info is False
|
||||||
|
|
||||||
|
def test_fastcgi_plugin_generates_config(self):
|
||||||
|
"""Test plugin generates correct HAProxy config"""
|
||||||
|
plugin = FastcgiPlugin()
|
||||||
|
plugin.configure({
|
||||||
|
"document_root": "/var/www/html",
|
||||||
|
"index_file": "index.php"
|
||||||
|
})
|
||||||
|
|
||||||
|
context = PluginContext(
|
||||||
|
parsed_object={},
|
||||||
|
easymapping=[],
|
||||||
|
container_env={},
|
||||||
|
domain="phpapp.local",
|
||||||
|
port="80",
|
||||||
|
host_config={}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = plugin.process(context)
|
||||||
|
|
||||||
|
assert result.haproxy_config is not None
|
||||||
|
assert "use-fcgi-app fcgi_phpapp_local" in result.haproxy_config
|
||||||
|
|
||||||
|
# Check fcgi-app definition in metadata
|
||||||
|
assert "fcgi_app_definition" in result.metadata
|
||||||
|
fcgi_app_def = result.metadata["fcgi_app_definition"]
|
||||||
|
assert "fcgi-app fcgi_phpapp_local" in fcgi_app_def
|
||||||
|
assert "docroot /var/www/html" in fcgi_app_def
|
||||||
|
assert "index index.php" in fcgi_app_def
|
||||||
|
assert result.metadata["document_root"] == "/var/www/html"
|
||||||
|
assert result.metadata["index_file"] == "index.php"
|
||||||
|
|
||||||
|
def test_fastcgi_plugin_custom_params(self):
|
||||||
|
"""Test plugin with custom FastCGI parameters"""
|
||||||
|
plugin = FastcgiPlugin()
|
||||||
|
plugin.configure({
|
||||||
|
"custom_params": {
|
||||||
|
"CUSTOM_VAR": "custom_value",
|
||||||
|
"APP_ENV": "production"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
context = PluginContext(
|
||||||
|
parsed_object={},
|
||||||
|
easymapping=[],
|
||||||
|
container_env={},
|
||||||
|
domain="phpapp.local",
|
||||||
|
port="80",
|
||||||
|
host_config={}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = plugin.process(context)
|
||||||
|
|
||||||
|
assert result.haproxy_config is not None
|
||||||
|
assert "use-fcgi-app fcgi_phpapp_local" in result.haproxy_config
|
||||||
|
|
||||||
|
# Check custom params in fcgi-app definition in metadata
|
||||||
|
assert "fcgi_app_definition" in result.metadata
|
||||||
|
fcgi_app_def = result.metadata["fcgi_app_definition"]
|
||||||
|
assert "set-param CUSTOM_VAR custom_value" in fcgi_app_def
|
||||||
|
assert "set-param APP_ENV production" in fcgi_app_def
|
||||||
|
assert result.metadata["custom_params_count"] == 2
|
||||||
|
|
||||||
|
def test_fastcgi_plugin_disabled(self):
|
||||||
|
"""Test plugin returns empty config when disabled"""
|
||||||
|
plugin = FastcgiPlugin()
|
||||||
|
plugin.configure({"enabled": "false"})
|
||||||
|
|
||||||
|
context = PluginContext(
|
||||||
|
parsed_object={},
|
||||||
|
easymapping=[],
|
||||||
|
container_env={},
|
||||||
|
domain="phpapp.local",
|
||||||
|
port="80",
|
||||||
|
host_config={}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = plugin.process(context)
|
||||||
|
|
||||||
|
assert result.haproxy_config is None or result.haproxy_config == ""
|
||||||
|
|
||||||
|
|
||||||
class TestPluginManager:
|
class TestPluginManager:
|
||||||
"""Test cases for PluginManager"""
|
"""Test cases for PluginManager"""
|
||||||
|
|
||||||
|
|
@ -750,10 +859,11 @@ class TestPluginManager:
|
||||||
assert "deny_pages" in manager.plugins
|
assert "deny_pages" in manager.plugins
|
||||||
assert "ip_whitelist" in manager.plugins
|
assert "ip_whitelist" in manager.plugins
|
||||||
assert "jwt_validator" in manager.plugins
|
assert "jwt_validator" in manager.plugins
|
||||||
|
assert "fastcgi" in manager.plugins
|
||||||
|
|
||||||
# Verify plugin types
|
# Verify plugin types
|
||||||
assert len(manager.global_plugins) == 1 # cleanup
|
assert len(manager.global_plugins) == 1 # cleanup
|
||||||
assert len(manager.domain_plugins) == 4 # cloudflare, deny_pages, ip_whitelist, jwt_validator
|
assert len(manager.domain_plugins) == 5 # cloudflare, deny_pages, ip_whitelist, jwt_validator, fastcgi
|
||||||
|
|
||||||
# Verify plugin instances
|
# Verify plugin instances
|
||||||
assert manager.plugins["cloudflare"].name == "cloudflare"
|
assert manager.plugins["cloudflare"].name == "cloudflare"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue