diff --git a/docs/plugins.md b/docs/plugins.md
index 6db84fc..081900f 100644
--- a/docs/plugins.md
+++ b/docs/plugins.md
@@ -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.
+### 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
Plugins can be configured using different methods depending on your deployment environment:
diff --git a/examples/docker/README.md b/examples/docker/README.md
index ce0640b..cf68e56 100644
--- a/examples/docker/README.md
+++ b/examples/docker/README.md
@@ -151,6 +151,117 @@ labels:
## 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
Protect your API with JWT token validation:
diff --git a/examples/docker/docker-compose-php-fpm.yml b/examples/docker/docker-compose-php-fpm.yml
new file mode 100644
index 0000000..8c030c6
--- /dev/null
+++ b/examples/docker/docker-compose-php-fpm.yml
@@ -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"
diff --git a/examples/docker/php-app/README.md b/examples/docker/php-app/README.md
new file mode 100644
index 0000000..f869c88
--- /dev/null
+++ b/examples/docker/php-app/README.md
@@ -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)
diff --git a/examples/docker/php-app/index.php b/examples/docker/php-app/index.php
new file mode 100644
index 0000000..34a20f0
--- /dev/null
+++ b/examples/docker/php-app/index.php
@@ -0,0 +1,152 @@
+
+
+
+
+
+ PHP-FPM with EasyHAProxy
+
+
+
+
+
PHP-FPM with EasyHAProxy FastCGI Plugin
+
+
+ Success! PHP is running via FastCGI protocol through HAProxy.
+
+
+
FastCGI Environment
+
+
+ | PHP Version |
+ |
+
+
+ | Server Software |
+ |
+
+
+ | Document Root |
+ |
+
+
+ | Script Filename |
+ |
+
+
+ | Request URI |
+ |
+
+
+ | Request Method |
+ |
+
+
+ | Server Name |
+ |
+
+
+ | Server Port |
+ |
+
+
+ | HTTPS |
+ |
+
+
+ | PATH_INFO |
+ |
+
+
+ | Gateway Interface |
+ |
+
+
+
+
Test Links
+
+
+
How This Works
+
+ This setup uses HAProxy with EasyHAProxy to proxy requests to PHP-FPM via the FastCGI protocol:
+
+
+ - HAProxy receives HTTP request on port 80
+ - The FastCGI plugin generates an
fcgi-app configuration that defines CGI parameters (SCRIPT_FILENAME, DOCUMENT_ROOT, etc.)
+ - HAProxy uses this configuration to communicate with PHP-FPM via the FastCGI protocol
+ - HAProxy connects to PHP-FPM (via TCP port 9000 or Unix socket, depending on configuration)
+ - PHP-FPM processes the PHP script and returns the response
+ - HAProxy sends the response back to the client
+
+
+
Configuration
+
The FastCGI plugin is configured in docker-compose-php-fpm.yml:
+
+ - document_root:
/var/www/html
+ - index_file:
index.php
+ - path_info:
true (enables PATH_INFO support)
+
+
+
+
diff --git a/examples/docker/php-app/info.php b/examples/docker/php-app/info.php
new file mode 100644
index 0000000..9a6e273
--- /dev/null
+++ b/examples/docker/php-app/info.php
@@ -0,0 +1,9 @@
+
+
+
+
+
+ PATH_INFO Test
+
+
+
+
+
PATH_INFO Test
+
+
+
+ Success! PATH_INFO is working correctly.
+
+
+
PATH_INFO Value
+
+
+
Parsed Path Segments
+
+
+
+
+ Note: PATH_INFO is not set. Try accessing this page with additional path segments.
+
+
+
+
Request Information
+
+
+
Example Usage
+
PATH_INFO enables RESTful URL routing. Try these URLs:
+
+
+
← Back to Home
+
+
+
diff --git a/src/easymapping/__init__.py b/src/easymapping/__init__.py
index eb810f9..ccf5812 100644
--- a/src/easymapping/__init__.py
+++ b/src/easymapping/__init__.py
@@ -98,7 +98,9 @@ class HaproxyConfigGenerator:
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:
import logging
logging.warning(f"Failed to execute global plugins: {e}")
@@ -265,6 +267,12 @@ class HaproxyConfigGenerator:
easymapping[port]["hosts"][hostname]["plugin_configs"] = [
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:
import logging
logging.warning(f"Failed to execute domain plugins for {hostname}: {e}")
diff --git a/src/plugins/builtin/fastcgi.py b/src/plugins/builtin/fastcgi.py
new file mode 100644
index 0000000..c50206f
--- /dev/null
+++ b/src/plugins/builtin/fastcgi.py
@@ -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
+ )
diff --git a/src/tests/expected/services-fcgi.txt b/src/tests/expected/services-fcgi.txt
new file mode 100644
index 0000000..f7c1bf5
--- /dev/null
+++ b/src/tests/expected/services-fcgi.txt
@@ -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
diff --git a/src/tests/fixtures/services-fcgi b/src/tests/fixtures/services-fcgi
new file mode 100644
index 0000000..d4fdc12
--- /dev/null
+++ b/src/tests/fixtures/services-fcgi
@@ -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"
+ }
+}
diff --git a/src/tests/test_plugins.py b/src/tests/test_plugins.py
index 52600fd..188844d 100644
--- a/src/tests/test_plugins.py
+++ b/src/tests/test_plugins.py
@@ -22,6 +22,7 @@ from plugins.builtin.cleanup import CleanupPlugin
from plugins.builtin.deny_pages import DenyPagesPlugin
from plugins.builtin.ip_whitelist import IpWhitelistPlugin
from plugins.builtin.jwt_validator import JwtValidatorPlugin
+from plugins.builtin.fastcgi import FastcgiPlugin
import easymapping
@@ -736,6 +737,114 @@ class TestJwtValidatorPlugin:
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:
"""Test cases for PluginManager"""
@@ -750,10 +859,11 @@ class TestPluginManager:
assert "deny_pages" in manager.plugins
assert "ip_whitelist" in manager.plugins
assert "jwt_validator" in manager.plugins
+ assert "fastcgi" in manager.plugins
# Verify plugin types
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
assert manager.plugins["cloudflare"].name == "cloudflare"