Skip to content

Commit

Permalink
Merge pull request #177 from clue-labs/remote_addr
Browse files Browse the repository at this point in the history
Read `$remote_addr` attribute for `AccessLogHandler` (trusted proxies)
  • Loading branch information
SimonFrings authored Jul 26, 2022
2 parents c444fa2 + 98449cc commit 440cf7e
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 1 deletion.
73 changes: 73 additions & 0 deletions docs/api/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,76 @@ $app = new FrameworkX\App($container);

$app->run();
```

X supports running behind reverse proxies just fine. However, by default it will
see the IP address of the last proxy server as the client IP address (this will
often be `127.0.0.1`). You can get the original client IP address if you configure
your proxy server to forward the original client IP address in the `X-Forwarded-For`
(XFF) or `Forwarded` HTTP request header. If you want to use these trusted headers,
you may use a custom middleware to read the IP from this header before passing
it to the [`AccessLogHandler`](middleware.md#accessloghandler) like this:

=== "Using middleware instances"

```php title="public/index.php"
<?php

use Acme\Todo\TrustedProxyMiddleware;

require __DIR__ . '/../vendor/autoload.php';

$app = new FrameworkX\App(
new TrustedProxyMiddleware(),
new FrameworkX\AccessLogHandler(),
new FrameworkX\ErrorHandler()
);

// Register routes here, see routing…

$app->run();
```

=== "Using middleware names"

```php title="public/index.php"
<?php

use Acme\Todo\TrustedProxyMiddleware;

require __DIR__ . '/../vendor/autoload.php';

$app = new FrameworkX\App(
TrustedProxyMiddleware::class,
FrameworkX\AccessLogHandler::class,
FrameworkX\ErrorHandler::class
);

// Register routes here, see routing…

$app->run();
```

```php title="src/TrustedProxyMiddleware.php"
<?php

namespace Acme\Todo;

use Psr\Http\Message\ServerRequestInterface;

class TrustedProxyMiddleware
{
public function __invoke(ServerRequestInterface $request, callable $next)
{
// use 127.0.0.1 as trusted proxy to read from X-Forwarded-For (XFF)
$remote_addr = $request->getAttribute('remote_addr') ?? $request->getServerParams()['REMOTE_ADDR'] ?? null;
if ($remote_addr === '127.0.0.1' && $request->hasHeader('X-Forwarded-For')) {
$remote_addr = preg_replace('/,.*/', '', $request->getHeaderLine('X-Forwarded-For'));
$request = $request->withAttribute('remote_addr', $remote_addr);
}

return $next($request);
}
}
```

See also [middleware handling](middleware.md) for more details.
2 changes: 1 addition & 1 deletion src/AccessLogHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private function log(ServerRequestInterface $request, ResponseInterface $respons
}

$this->sapi->log(
($request->getServerParams()['REMOTE_ADDR'] ?? '-') . ' ' .
($request->getAttribute('remote_addr') ?? $request->getServerParams()['REMOTE_ADDR'] ?? '-') . ' ' .
'"' . $this->escape($method) . ' ' . $this->escape($request->getRequestTarget()) . ' HTTP/' . $request->getProtocolVersion() . '" ' .
$status . ' ' . $responseSize . ' ' . sprintf('%.3F', $time < 0 ? 0 : $time)
);
Expand Down
13 changes: 13 additions & 0 deletions tests/AccessLogHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,19 @@ public function testInvokeWithStreamingResponsePrintsNothingIfStreamIsPending()
$stream->write('hello');
}

public function testInvokeWithRemoteAddrAttributePrintsRequestLogWithIpFromAttribute()
{
$handler = new AccessLogHandler();

$request = new ServerRequest('GET', 'http://localhost:8080/users', [], '', '1.1', ['REMOTE_ADDR' => '127.0.0.1']);
$request = $request->withAttribute('remote_addr', '10.0.0.1');
$response = new Response(200, [], "Hello\n");

// 2021-01-29 12:22:01.717 10.0.0.1 "GET /users HTTP/1.1" 200 6 0.000\n
$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 . "$/");
$handler($request, function () use ($response) { return $response; });
}

public function testInvokeWithoutRemoteAddressPrintsRequestLogWithDashAsPlaceholder()
{
$handler = new AccessLogHandler();
Expand Down

0 comments on commit 440cf7e

Please sign in to comment.