Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add server-side parameters to request object #174

Merged
merged 2 commits into from
Apr 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,47 @@ $http = new Server($socket, function (ServerRequestInterface $request) {
});
```

The `getServerParams(): mixed[]` method can be used to
get server-side parameters similar to the `$_SERVER` variable.
The following parameters are currently available:

* `REMOTE_ADDR`
The IP address of the request sender
* `REMOTE_PORT`
Port of the request sender
* `SERVER_ADDR`
The IP address of the server
* `SERVER_PORT`
The port of the server
* `REQUEST_TIME`
Unix timestamp when the complete request header has been received,
as integer similar to `time()`
* `REQUEST_TIME_FLOAT`
Unix timestamp when the complete request header has been received,
as float similar to `microtime(true)`
* `HTTPS`
Set to 'on' if the request used HTTPS, otherwise it won't be set

```php
$http = new Server($socket, function (ServerRequestInterface $request) {
$body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR'];

return new Response(
200,
array('Content-Type' => 'text/plain'),
$body
);
});
```

See also [example #2](examples).

For more details about the request object, check out the documentation of
[PSR-7 ServerRequestInterface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#321-psrhttpmessageserverrequestinterface)
and
[PSR-7 RequestInterface](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md#32-psrhttpmessagerequestinterface).

> Currently the the server params, cookies and uploaded files are not added by the
> Currently the cookies and uploaded files are not added by the
`Server`, but you can add these parameters by yourself using the given methods.
The next versions of this project will cover these features.

Expand Down
25 changes: 25 additions & 0 deletions examples/02-client-ip.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

use React\EventLoop\Factory;
use React\Socket\Server;
use React\Http\Response;
use Psr\Http\Message\ServerRequestInterface;

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

$loop = Factory::create();
$socket = new Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:0', $loop);

$server = new \React\Http\Server($socket, function (ServerRequestInterface $request) {
$body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR'];

return new Response(
200,
array('Content-Type' => 'text/plain'),
$body
);
});

echo 'Listening on http://' . $socket->getAddress() . PHP_EOL;

$loop->run();
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
35 changes: 29 additions & 6 deletions src/RequestHeaderParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ class RequestHeaderParser extends EventEmitter
private $buffer = '';
private $maxSize = 4096;

private $uri;
private $localSocketUri;
private $remoteSocketUri;

public function __construct($localSocketUri = '')
public function __construct($localSocketUri = null, $remoteSocketUri = null)
{
$this->uri = $localSocketUri;
$this->localSocketUri = $localSocketUri;
$this->remoteSocketUri = $remoteSocketUri;
}

public function feed($data)
Expand Down Expand Up @@ -85,13 +87,34 @@ private function parseRequest($data)

// create new obj implementing ServerRequestInterface by preserving all
// previous properties and restoring original request-target
$serverParams = array(
'REQUEST_TIME' => time(),
'REQUEST_TIME_FLOAT' => microtime(true)
);

if ($this->remoteSocketUri !== null) {
$remoteAddress = parse_url($this->remoteSocketUri);
$serverParams['REMOTE_ADDR'] = $remoteAddress['host'];
$serverParams['REMOTE_PORT'] = $remoteAddress['port'];
}

if ($this->localSocketUri !== null) {
$localAddress = parse_url($this->localSocketUri);
$serverParams['SERVER_ADDR'] = $localAddress['host'];
$serverParams['SERVER_PORT'] = $localAddress['port'];
if (isset($localAddress['scheme']) && $localAddress['scheme'] === 'https') {
$serverParams['HTTPS'] = 'on';
}
}

$target = $request->getRequestTarget();
$request = new ServerRequest(
$request->getMethod(),
$request->getUri(),
$request->getHeaders(),
$request->getBody(),
$request->getProtocolVersion()
$request->getProtocolVersion(),
$serverParams
);
$request = $request->withRequestTarget($target);

Expand Down Expand Up @@ -144,7 +167,7 @@ private function parseRequest($data)

// set URI components from socket address if not already filled via Host header
if ($request->getUri()->getHost() === '') {
$parts = parse_url($this->uri);
$parts = parse_url($this->localSocketUri);

$request = $request->withUri(
$request->getUri()->withScheme('http')->withHost($parts['host'])->withPort($parts['port']),
Expand All @@ -162,7 +185,7 @@ private function parseRequest($data)
}

// Update request URI to "https" scheme if the connection is encrypted
$parts = parse_url($this->uri);
$parts = parse_url($this->localSocketUri);
if (isset($parts['scheme']) && $parts['scheme'] === 'https') {
// The request URI may omit default ports here, so try to parse port
// from Host header field (if possible)
Expand Down
9 changes: 2 additions & 7 deletions src/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ public function handleConnection(ConnectionInterface $conn)
{
$that = $this;
$parser = new RequestHeaderParser(
($this->isConnectionEncrypted($conn) ? 'https://' : 'http://') . $conn->getLocalAddress()
($this->isConnectionEncrypted($conn) ? 'https://' : 'http://') . $conn->getLocalAddress(),
'tcp://' . $conn->getRemoteAddress()
);

$listener = array($parser, 'feed');
Expand Down Expand Up @@ -227,12 +228,6 @@ public function handleRequest(ConnectionInterface $conn, ServerRequestInterface
$conn->write("HTTP/1.1 100 Continue\r\n\r\n");
}

// attach remote ip to the request as metadata
$request->remoteAddress = trim(
parse_url('tcp://' . $conn->getRemoteAddress(), PHP_URL_HOST),
'[]'
);

$callback = $this->callback;
$promise = new Promise(function ($resolve, $reject) use ($callback, $request) {
$resolve($callback($request));
Expand Down
22 changes: 22 additions & 0 deletions src/ServerRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,28 @@ class ServerRequest extends Request implements ServerRequestInterface
private $queryParams = array();
private $parsedBody = null;

/**
* @param null|string $method HTTP method for the request.
* @param null|string|UriInterface $uri URI for the request.
* @param array $headers Headers for the message.
* @param string|resource|StreamInterface $body Message body.
* @param string $protocolVersion HTTP protocol version.
* @param array server-side parameters
*
* @throws InvalidArgumentException for an invalid URI
*/
public function __construct(
$method,
$uri,
array $headers = array(),
$body = null,
$protocolVersion = '1.1',
$serverParams = array()
) {
$this->serverParams = $serverParams;
parent::__construct($method, $uri, $headers, $body, $protocolVersion);
}

public function getServerParams()
{
return $this->serverParams;
Expand Down
77 changes: 77 additions & 0 deletions tests/RequestHeaderParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,83 @@ public function testInvalidHttpVersion()
$this->assertSame('Received request with invalid protocol version', $error->getMessage());
}

public function testServerParamsWillBeSetOnHttpsRequest()
{
$request = null;

$parser = new RequestHeaderParser(
'https://127.1.1.1:8000',
'https://192.168.1.1:8001'
);

$parser->on('headers', function ($parsedRequest) use (&$request) {
$request = $parsedRequest;
});

$parser->feed("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n");
$serverParams = $request->getServerParams();

$this->assertEquals('on', $serverParams['HTTPS']);
$this->assertNotEmpty($serverParams['REQUEST_TIME']);
$this->assertNotEmpty($serverParams['REQUEST_TIME_FLOAT']);

$this->assertEquals('127.1.1.1', $serverParams['SERVER_ADDR']);
$this->assertEquals('8000', $serverParams['SERVER_PORT']);

$this->assertEquals('192.168.1.1', $serverParams['REMOTE_ADDR']);
$this->assertEquals('8001', $serverParams['REMOTE_PORT']);
}

public function testServerParamsWillBeSetOnHttpRequest()
{
$request = null;

$parser = new RequestHeaderParser(
'http://127.1.1.1:8000',
'http://192.168.1.1:8001'
);

$parser->on('headers', function ($parsedRequest) use (&$request) {
$request = $parsedRequest;
});

$parser->feed("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n");
$serverParams = $request->getServerParams();

$this->assertArrayNotHasKey('HTTPS', $serverParams);
$this->assertNotEmpty($serverParams['REQUEST_TIME']);
$this->assertNotEmpty($serverParams['REQUEST_TIME_FLOAT']);

$this->assertEquals('127.1.1.1', $serverParams['SERVER_ADDR']);
$this->assertEquals('8000', $serverParams['SERVER_PORT']);

$this->assertEquals('192.168.1.1', $serverParams['REMOTE_ADDR']);
$this->assertEquals('8001', $serverParams['REMOTE_PORT']);
}

public function testServerParamsWontBeSetOnMissingUrls()
{
$request = null;

$parser = new RequestHeaderParser();

$parser->on('headers', function ($parsedRequest) use (&$request) {
$request = $parsedRequest;
});

$parser->feed("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n");
$serverParams = $request->getServerParams();

$this->assertNotEmpty($serverParams['REQUEST_TIME']);
$this->assertNotEmpty($serverParams['REQUEST_TIME_FLOAT']);

$this->assertArrayNotHasKey('SERVER_ADDR', $serverParams);
$this->assertArrayNotHasKey('SERVER_PORT', $serverParams);

$this->assertArrayNotHasKey('REMOTE_ADDR', $serverParams);
$this->assertArrayNotHasKey('REMOTE_PORT', $serverParams);
}

private function createGetRequest()
{
$data = "GET / HTTP/1.1\r\n";
Expand Down
21 changes: 21 additions & 0 deletions tests/ServerRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,25 @@ public function testWithParsedBody()
$this->assertNotSame($request, $this->request);
$this->assertEquals(array('test' => 'world'), $request->getParsedBody());
}

public function testServerRequestParameter()
{
$body = 'hello=world';
$request = new ServerRequest(
'POST',
'http://127.0.0.1',
array('Content-Length' => strlen($body)),
$body,
'1.0',
array('SERVER_ADDR' => '127.0.0.1')
);

$serverParams = $request->getServerParams();
$this->assertEquals('POST', $request->getMethod());
$this->assertEquals('http://127.0.0.1', $request->getUri());
$this->assertEquals('11', $request->getHeaderLine('Content-Length'));
$this->assertEquals('hello=world', $request->getBody());
$this->assertEquals('1.0', $request->getProtocolVersion());
$this->assertEquals('127.0.0.1', $serverParams['SERVER_ADDR']);
}
}
45 changes: 41 additions & 4 deletions tests/ServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,27 +69,30 @@ public function testRequestEvent()
$server = new Server($this->socket, function (ServerRequestInterface $request) use (&$i, &$requestAssertion) {
$i++;
$requestAssertion = $request;

return \React\Promise\resolve(new Response());
});

$this->connection
->expects($this->once())
->expects($this->any())
->method('getRemoteAddress')
->willReturn('127.0.0.1');
->willReturn('127.0.0.1:8080');

$this->socket->emit('connection', array($this->connection));

$data = $this->createGetRequest();
$this->connection->emit('data', array($data));

$serverParams = $requestAssertion->getServerParams();

$this->assertSame(1, $i);
$this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion);
$this->assertSame('GET', $requestAssertion->getMethod());
$this->assertSame('/', $requestAssertion->getRequestTarget());
$this->assertSame('/', $requestAssertion->getUri()->getPath());
$this->assertSame('http://example.com/', (string)$requestAssertion->getUri());
$this->assertSame('example.com', $requestAssertion->getHeaderLine('Host'));
$this->assertSame('127.0.0.1', $requestAssertion->remoteAddress);
$this->assertSame('127.0.0.1', $serverParams['REMOTE_ADDR']);
}

public function testRequestGetWithHostAndCustomPort()
Expand Down Expand Up @@ -288,7 +291,7 @@ public function testRequestWithoutHostEventUsesSocketAddress()
});

$this->connection
->expects($this->once())
->expects($this->any())
->method('getLocalAddress')
->willReturn('127.0.0.1:80');

Expand Down Expand Up @@ -2332,6 +2335,40 @@ function ($data) use (&$buffer) {
$this->assertInstanceOf('RuntimeException', $exception);
}

public function testServerRequestParams()
{
$requestValidation = null;
$server = new Server($this->socket, function (ServerRequestInterface $request) use (&$requestValidation) {
$requestValidation = $request;
return new Response();
});

$this->connection
->expects($this->any())
->method('getRemoteAddress')
->willReturn('192.168.1.2:80');

$this->connection
->expects($this->any())
->method('getLocalAddress')
->willReturn('127.0.0.1:8080');

$this->socket->emit('connection', array($this->connection));

$data = $this->createGetRequest();

$this->connection->emit('data', array($data));

$serverParams = $requestValidation->getServerParams();

$this->assertEquals('127.0.0.1', $serverParams['SERVER_ADDR']);
$this->assertEquals('8080', $serverParams['SERVER_PORT']);
$this->assertEquals('192.168.1.2', $serverParams['REMOTE_ADDR']);
$this->assertEquals('80', $serverParams['REMOTE_PORT']);
$this->assertNotNull($serverParams['REQUEST_TIME']);
$this->assertNotNull($serverParams['REQUEST_TIME_FLOAT']);
}

private function createGetRequest()
{
$data = "GET / HTTP/1.1\r\n";
Expand Down