Skip to content

Commit b22f78e

Browse files
committed
Merge branch 'release/0.7.0'
2 parents 1dace5c + a3b90da commit b22f78e

21 files changed

+2879
-5
lines changed

.version.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"strategy": "semver",
33
"major": 0,
4-
"minor": 6,
5-
"patch": 9,
4+
"minor": 7,
5+
"patch": 0,
66
"build": 0
77
}

README.md

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[![Build Status](https://app.travis-ci.com/Neuron-PHP/routing.svg?token=F8zCwpT7x7Res7J2N4vF&branch=master)](https://app.travis-ci.com/Neuron-PHP/routing)
1+
[![CI](https://github.com/neuron-php/routing/actions/workflows/ci.yml/badge.svg)](https://github.com/neuron-php/routing/actions)
22
# Neuron-PHP Routing
33

44
## Overview
@@ -80,6 +80,168 @@ several routes including one with a variable.
8080
If present, the extra element is merged into the parameters array
8181
before it is passed to the routes closure.
8282

83+
## Rate Limiting
84+
85+
The routing component includes a powerful rate limiting system with multiple storage backends and flexible configuration options.
86+
87+
### Basic Usage
88+
89+
```php
90+
use Neuron\Routing\Router;
91+
use Neuron\Routing\Filters\RateLimitFilter;
92+
use Neuron\Routing\RateLimit\RateLimitConfig;
93+
94+
$router = Router::instance();
95+
96+
// Create rate limit configuration
97+
$config = new RateLimitConfig([
98+
'enabled' => true,
99+
'storage' => 'redis', // Options: redis, file, memory (testing only)
100+
'requests' => 100, // Max requests per window
101+
'window' => 3600 // Time window in seconds (1 hour)
102+
]);
103+
104+
// Create and register the filter
105+
$rateLimitFilter = new RateLimitFilter($config);
106+
$router->registerFilter('rate_limit', $rateLimitFilter);
107+
108+
// Apply globally to all routes
109+
$router->addFilter('rate_limit');
110+
111+
// Or apply to specific routes
112+
$router->get('/api/data', $handler, 'rate_limit');
113+
```
114+
115+
### Configuration Options
116+
117+
Rate limiting can be configured via array or environment variables:
118+
119+
```php
120+
// Array configuration
121+
$config = new RateLimitConfig([
122+
'enabled' => true,
123+
'storage' => 'redis',
124+
'requests' => 100,
125+
'window' => 3600,
126+
'redis_host' => '127.0.0.1',
127+
'redis_port' => 6379,
128+
'file_path' => 'cache/rate_limits'
129+
]);
130+
131+
// From settings/environment variables (flat structure)
132+
// RATE_LIMIT_ENABLED=true
133+
// RATE_LIMIT_STORAGE=redis
134+
// RATE_LIMIT_REQUESTS=100
135+
// RATE_LIMIT_WINDOW=3600
136+
$config = RateLimitConfig::fromSettings($settingsSource);
137+
```
138+
139+
### Storage Backends
140+
141+
#### Redis (Recommended for Production)
142+
Best for distributed systems and high-traffic applications:
143+
144+
```php
145+
$config = new RateLimitConfig([
146+
'storage' => 'redis',
147+
'redis_host' => '127.0.0.1',
148+
'redis_port' => 6379,
149+
'redis_database' => 0,
150+
'redis_prefix' => 'rate_limit_',
151+
'redis_auth' => 'password', // Optional
152+
'redis_persistent' => true // Use persistent connections
153+
]);
154+
```
155+
156+
#### File Storage
157+
Simple solution for single-server deployments:
158+
159+
```php
160+
$config = new RateLimitConfig([
161+
'storage' => 'file',
162+
'file_path' => 'cache/rate_limits' // Directory for rate limit files
163+
]);
164+
```
165+
166+
#### Memory Storage (Testing Only)
167+
For unit tests and development:
168+
169+
```php
170+
$config = new RateLimitConfig([
171+
'storage' => 'memory' // Data lost when PHP process ends
172+
]);
173+
```
174+
175+
### Advanced Features
176+
177+
#### Key Strategies
178+
Control how rate limits are applied:
179+
180+
```php
181+
// Limit by IP address (default)
182+
$filter = new RateLimitFilter($config, 'ip');
183+
184+
// Limit by authenticated user
185+
$filter = new RateLimitFilter($config, 'user');
186+
187+
// Limit by IP + route combination
188+
$filter = new RateLimitFilter($config, 'route');
189+
190+
// Custom key generation
191+
class CustomRateLimitFilter extends RateLimitFilter {
192+
protected function getCustomKey(RouteMap $route): string {
193+
// Your custom logic here
194+
return $_SESSION['tenant_id'] ?? $this->getClientIp();
195+
}
196+
}
197+
```
198+
199+
#### Whitelisting and Blacklisting
200+
201+
```php
202+
$filter = new RateLimitFilter(
203+
$config,
204+
'ip',
205+
['192.168.1.100', '10.0.0.1'], // Whitelist - no limits
206+
['45.67.89.10'] // Blacklist - stricter limits (1/10th)
207+
);
208+
```
209+
210+
#### Custom Responses
211+
Rate limit exceeded responses include appropriate headers:
212+
- `X-RateLimit-Limit`: Maximum requests allowed
213+
- `X-RateLimit-Remaining`: Requests remaining
214+
- `X-RateLimit-Reset`: Unix timestamp when limit resets
215+
- `Retry-After`: Seconds until retry allowed
216+
217+
The response format (JSON/HTML) is automatically determined from the Accept header.
218+
219+
### Example: API Rate Limiting
220+
221+
```php
222+
// Different limits for different endpoints
223+
$publicConfig = new RateLimitConfig([
224+
'enabled' => true,
225+
'storage' => 'redis',
226+
'requests' => 10,
227+
'window' => 60 // 10 requests per minute
228+
]);
229+
230+
$apiConfig = new RateLimitConfig([
231+
'enabled' => true,
232+
'storage' => 'redis',
233+
'requests' => 1000,
234+
'window' => 3600 // 1000 requests per hour
235+
]);
236+
237+
$router->registerFilter('public_limit', new RateLimitFilter($publicConfig));
238+
$router->registerFilter('api_limit', new RateLimitFilter($apiConfig));
239+
240+
// Apply different limits
241+
$router->get('/public/search', $searchHandler, 'public_limit');
242+
$router->get('/api/users', $usersHandler, 'api_limit');
243+
```
244+
83245
# More Information
84246

85247
You can read more about the Neuron components at [neuronphp.com](http://neuronphp.com)

VERSIONLOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
* Added comprehensive rate limiting system with multiple storage backends.
2+
* Added RateLimitFilter for request throttling.
3+
* Added Redis, File, and Memory storage implementations for rate limiting.
4+
* Added configurable rate limiting with environment variable support (flat structure).
5+
* Added whitelisting and blacklisting support for rate limiting.
6+
* Added automatic rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset).
7+
* Added HTTP 429 response formatting with JSON/HTML content negotiation.
8+
* Added comprehensive tests for rate limiting functionality.
9+
* Added IPResolver.
10+
11+
## 0.6.10 2025-11-04
112
## 0.6.9 2025-05-21
213

314
## 0.6.8 2025-02-18

src/Routing/DefaultIpResolver.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace Neuron\Routing;
4+
5+
/**
6+
* Default IP resolver implementation that checks common proxy headers.
7+
*
8+
* This resolver checks standard proxy headers in order of preference,
9+
* validating and extracting the client IP address. It handles:
10+
* - Cloudflare (CF-Connecting-IP)
11+
* - Standard proxies (X-Forwarded-For)
12+
* - Nginx (X-Real-IP)
13+
* - Other proxies (Client-IP)
14+
*
15+
* Falls back to REMOTE_ADDR if no valid IP is found in headers.
16+
*
17+
* @package Neuron\Routing
18+
*/
19+
class DefaultIpResolver implements IIpResolver
20+
{
21+
/**
22+
* Resolve the client IP address by checking common proxy headers.
23+
*
24+
* @param array $server The $_SERVER array
25+
* @return string The resolved IP address
26+
*/
27+
public function resolve( array $server ): string
28+
{
29+
// Check common proxy headers in order of preference
30+
$headers = [
31+
'HTTP_CF_CONNECTING_IP', // Cloudflare
32+
'HTTP_X_FORWARDED_FOR', // Standard proxy
33+
'HTTP_X_REAL_IP', // Nginx
34+
'HTTP_CLIENT_IP', // Some proxies
35+
];
36+
37+
foreach( $headers as $header )
38+
{
39+
if( !empty( $server[ $header ] ) )
40+
{
41+
// Handle comma-separated list (proxy chain) - take first IP
42+
$ip = $this->extractFirstIp( $server[ $header ] );
43+
44+
// Validate IP format
45+
if( filter_var( $ip, FILTER_VALIDATE_IP ) )
46+
{
47+
return $ip;
48+
}
49+
}
50+
}
51+
52+
// Fallback to direct connection
53+
return $server[ 'REMOTE_ADDR' ] ?? '0.0.0.0';
54+
}
55+
56+
/**
57+
* Extract the first IP from a potentially comma-separated list.
58+
*
59+
* @param string $ipString The IP string (may contain multiple IPs)
60+
* @return string The first IP address
61+
*/
62+
protected function extractFirstIp( string $ipString ): string
63+
{
64+
if( strpos( $ipString, ',' ) !== false )
65+
{
66+
$ips = explode( ',', $ipString );
67+
return trim( $ips[0] );
68+
}
69+
70+
return trim( $ipString );
71+
}
72+
}

0 commit comments

Comments
 (0)