1
0
Fork 0

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:
Joao Gilberto Magalhaes 2025-12-01 13:04:32 -05:00
parent a993025718
commit 883521e7e1
12 changed files with 1029 additions and 2 deletions

View file

@ -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:

View 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"

View 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)

View 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>

View file

@ -0,0 +1,9 @@
<?php
/**
* PHP Info Page
*
* This page displays comprehensive PHP configuration information
* including FastCGI environment variables set by EasyHAProxy.
*/
phpinfo();

View 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>