diff --git a/docs/api/app.md b/docs/api/app.md index 26ae747..9155052 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -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" + run(); + ``` + +=== "Using middleware names" + + ```php title="public/index.php" + run(); + ``` + +```php title="src/TrustedProxyMiddleware.php" +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. diff --git a/src/AccessLogHandler.php b/src/AccessLogHandler.php index 6cecaba..1d13321 100644 --- a/src/AccessLogHandler.php +++ b/src/AccessLogHandler.php @@ -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) ); diff --git a/tests/AccessLogHandlerTest.php b/tests/AccessLogHandlerTest.php index c4d36fd..d4037ea 100644 --- a/tests/AccessLogHandlerTest.php +++ b/tests/AccessLogHandlerTest.php @@ -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();