Skip to content

Commit 440cf7e

Browse files
authored
Merge pull request #177 from clue-labs/remote_addr
Read `$remote_addr` attribute for `AccessLogHandler` (trusted proxies)
2 parents c444fa2 + 98449cc commit 440cf7e

File tree

3 files changed

+87
-1
lines changed

3 files changed

+87
-1
lines changed

docs/api/app.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,3 +345,76 @@ $app = new FrameworkX\App($container);
345345

346346
$app->run();
347347
```
348+
349+
X supports running behind reverse proxies just fine. However, by default it will
350+
see the IP address of the last proxy server as the client IP address (this will
351+
often be `127.0.0.1`). You can get the original client IP address if you configure
352+
your proxy server to forward the original client IP address in the `X-Forwarded-For`
353+
(XFF) or `Forwarded` HTTP request header. If you want to use these trusted headers,
354+
you may use a custom middleware to read the IP from this header before passing
355+
it to the [`AccessLogHandler`](middleware.md#accessloghandler) like this:
356+
357+
=== "Using middleware instances"
358+
359+
```php title="public/index.php"
360+
<?php
361+
362+
use Acme\Todo\TrustedProxyMiddleware;
363+
364+
require __DIR__ . '/../vendor/autoload.php';
365+
366+
$app = new FrameworkX\App(
367+
new TrustedProxyMiddleware(),
368+
new FrameworkX\AccessLogHandler(),
369+
new FrameworkX\ErrorHandler()
370+
);
371+
372+
// Register routes here, see routing…
373+
374+
$app->run();
375+
```
376+
377+
=== "Using middleware names"
378+
379+
```php title="public/index.php"
380+
<?php
381+
382+
use Acme\Todo\TrustedProxyMiddleware;
383+
384+
require __DIR__ . '/../vendor/autoload.php';
385+
386+
$app = new FrameworkX\App(
387+
TrustedProxyMiddleware::class,
388+
FrameworkX\AccessLogHandler::class,
389+
FrameworkX\ErrorHandler::class
390+
);
391+
392+
// Register routes here, see routing…
393+
394+
$app->run();
395+
```
396+
397+
```php title="src/TrustedProxyMiddleware.php"
398+
<?php
399+
400+
namespace Acme\Todo;
401+
402+
use Psr\Http\Message\ServerRequestInterface;
403+
404+
class TrustedProxyMiddleware
405+
{
406+
public function __invoke(ServerRequestInterface $request, callable $next)
407+
{
408+
// use 127.0.0.1 as trusted proxy to read from X-Forwarded-For (XFF)
409+
$remote_addr = $request->getAttribute('remote_addr') ?? $request->getServerParams()['REMOTE_ADDR'] ?? null;
410+
if ($remote_addr === '127.0.0.1' && $request->hasHeader('X-Forwarded-For')) {
411+
$remote_addr = preg_replace('/,.*/', '', $request->getHeaderLine('X-Forwarded-For'));
412+
$request = $request->withAttribute('remote_addr', $remote_addr);
413+
}
414+
415+
return $next($request);
416+
}
417+
}
418+
```
419+
420+
See also [middleware handling](middleware.md) for more details.

src/AccessLogHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private function log(ServerRequestInterface $request, ResponseInterface $respons
8686
}
8787

8888
$this->sapi->log(
89-
($request->getServerParams()['REMOTE_ADDR'] ?? '-') . ' ' .
89+
($request->getAttribute('remote_addr') ?? $request->getServerParams()['REMOTE_ADDR'] ?? '-') . ' ' .
9090
'"' . $this->escape($method) . ' ' . $this->escape($request->getRequestTarget()) . ' HTTP/' . $request->getProtocolVersion() . '" ' .
9191
$status . ' ' . $responseSize . ' ' . sprintf('%.3F', $time < 0 ? 0 : $time)
9292
);

tests/AccessLogHandlerTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,19 @@ public function testInvokeWithStreamingResponsePrintsNothingIfStreamIsPending()
215215
$stream->write('hello');
216216
}
217217

218+
public function testInvokeWithRemoteAddrAttributePrintsRequestLogWithIpFromAttribute()
219+
{
220+
$handler = new AccessLogHandler();
221+
222+
$request = new ServerRequest('GET', 'http://localhost:8080/users', [], '', '1.1', ['REMOTE_ADDR' => '127.0.0.1']);
223+
$request = $request->withAttribute('remote_addr', '10.0.0.1');
224+
$response = new Response(200, [], "Hello\n");
225+
226+
// 2021-01-29 12:22:01.717 10.0.0.1 "GET /users HTTP/1.1" 200 6 0.000\n
227+
$this->expectOutputRegex("/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} 10\.0\.0\.1 \"GET \/users HTTP\/1\.1\" 200 6 0\.0\d\d" . PHP_EOL . "$/");
228+
$handler($request, function () use ($response) { return $response; });
229+
}
230+
218231
public function testInvokeWithoutRemoteAddressPrintsRequestLogWithDashAsPlaceholder()
219232
{
220233
$handler = new AccessLogHandler();

0 commit comments

Comments
 (0)