Skip to content

Commit

Permalink
Merge pull request #174 from legionth/server-params
Browse files Browse the repository at this point in the history
Add server-side parameters to request object
  • Loading branch information
clue authored Apr 26, 2017
2 parents a3b1a84 + 4acbfdb commit 371d53c
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 18 deletions.
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

0 comments on commit 371d53c

Please sign in to comment.