diff --git a/src/Io/RequestHeaderParser.php b/src/Io/RequestHeaderParser.php index 6220f022..58fb47d9 100644 --- a/src/Io/RequestHeaderParser.php +++ b/src/Io/RequestHeaderParser.php @@ -4,6 +4,7 @@ use Evenement\EventEmitter; use Psr\Http\Message\ServerRequestInterface; +use React\Socket\ConnectionInterface; use RingCentral\Psr7 as g7; use Exception; @@ -20,50 +21,70 @@ */ class RequestHeaderParser extends EventEmitter { - private $buffer = ''; private $maxSize = 8192; - private $localSocketUri; - private $remoteSocketUri; - - public function __construct($localSocketUri = null, $remoteSocketUri = null) + public function handle(ConnectionInterface $conn) { - $this->localSocketUri = $localSocketUri; - $this->remoteSocketUri = $remoteSocketUri; - } + $buffer = ''; + $conn->on('data', $fn = function ($data) use (&$buffer, &$fn, $conn) { + // append chunk of data to buffer and look for end of request headers + $buffer .= $data; + $endOfHeader = \strpos($buffer, "\r\n\r\n"); + + // reject request if buffer size is exceeded + if ($endOfHeader > $this->maxSize || ($endOfHeader === false && isset($buffer[$this->maxSize]))) { + $conn->removeListener('data', $fn); + $fn = null; + + $this->emit('error', array( + new \OverflowException("Maximum header size of {$this->maxSize} exceeded.", 431), + $conn + )); + return; + } - public function feed($data) - { - $this->buffer .= $data; - $endOfHeader = \strpos($this->buffer, "\r\n\r\n"); + // ignore incomplete requests + if ($endOfHeader === false) { + return; + } - if ($endOfHeader > $this->maxSize || ($endOfHeader === false && isset($this->buffer[$this->maxSize]))) { - $this->emit('error', array(new \OverflowException("Maximum header size of {$this->maxSize} exceeded.", 431), $this)); - $this->removeAllListeners(); - return; - } + // request headers received => try to parse request + $conn->removeListener('data', $fn); + $fn = null; - if (false !== $endOfHeader) { try { - $request = $this->parseRequest((string)\substr($this->buffer, 0, $endOfHeader)); + $request = $this->parseRequest( + (string)\substr($buffer, 0, $endOfHeader), + $conn->getRemoteAddress(), + $conn->getLocalAddress() + ); } catch (Exception $exception) { - $this->emit('error', array($exception)); - $this->removeAllListeners(); + $buffer = ''; + $this->emit('error', array( + $exception, + $conn + )); return; } - $bodyBuffer = isset($this->buffer[$endOfHeader + 4]) ? \substr($this->buffer, $endOfHeader + 4) : ''; - $this->emit('headers', array($request, $bodyBuffer)); - $this->removeAllListeners(); - } + $bodyBuffer = isset($buffer[$endOfHeader + 4]) ? \substr($buffer, $endOfHeader + 4) : ''; + $buffer = ''; + $this->emit('headers', array($request, $bodyBuffer, $conn)); + }); + + $conn->on('close', function () use (&$buffer, &$fn) { + $fn = $buffer = null; + }); } /** * @param string $headers buffer string containing request headers only - * @throws \InvalidArgumentException + * @param ?string $remoteSocketUri + * @param ?string $localSocketUri * @return ServerRequestInterface + * @throws \InvalidArgumentException */ - private function parseRequest($headers) + private function parseRequest($headers, $remoteSocketUri, $localSocketUri) { // additional, stricter safe-guard for request line // because request parser doesn't properly cope with invalid ones @@ -103,8 +124,8 @@ private function parseRequest($headers) // apply REMOTE_ADDR and REMOTE_PORT if source address is known // address should always be known, unless this is over Unix domain sockets (UDS) - if ($this->remoteSocketUri !== null) { - $remoteAddress = \parse_url($this->remoteSocketUri); + if ($remoteSocketUri !== null) { + $remoteAddress = \parse_url($remoteSocketUri); $serverParams['REMOTE_ADDR'] = $remoteAddress['host']; $serverParams['REMOTE_PORT'] = $remoteAddress['port']; } @@ -112,13 +133,13 @@ private function parseRequest($headers) // apply SERVER_ADDR and SERVER_PORT if server address is known // address should always be known, even for Unix domain sockets (UDS) // but skip UDS as it doesn't have a concept of host/port.s - if ($this->localSocketUri !== null) { - $localAddress = \parse_url($this->localSocketUri); + if ($localSocketUri !== null) { + $localAddress = \parse_url($localSocketUri); if (isset($localAddress['host'], $localAddress['port'])) { $serverParams['SERVER_ADDR'] = $localAddress['host']; $serverParams['SERVER_PORT'] = $localAddress['port']; } - if (isset($localAddress['scheme']) && $localAddress['scheme'] === 'https') { + if (isset($localAddress['scheme']) && $localAddress['scheme'] === 'tls') { $serverParams['HTTPS'] = 'on'; } } @@ -177,7 +198,7 @@ private function parseRequest($headers) // set URI components from socket address if not already filled via Host header if ($request->getUri()->getHost() === '') { - $parts = \parse_url($this->localSocketUri); + $parts = \parse_url($localSocketUri); if (!isset($parts['host'], $parts['port'])) { $parts = array('host' => '127.0.0.1', 'port' => 80); } @@ -198,8 +219,8 @@ private function parseRequest($headers) } // Update request URI to "https" scheme if the connection is encrypted - $parts = \parse_url($this->localSocketUri); - if (isset($parts['scheme']) && $parts['scheme'] === 'https') { + $parts = \parse_url($localSocketUri); + if (isset($parts['scheme']) && $parts['scheme'] === 'tls') { // The request URI may omit default ports here, so try to parse port // from Host header field (if possible) $port = $request->getUri()->getPort(); diff --git a/src/StreamingServer.php b/src/StreamingServer.php index 64a82af2..fa3f9777 100644 --- a/src/StreamingServer.php +++ b/src/StreamingServer.php @@ -87,6 +87,7 @@ final class StreamingServer extends EventEmitter { private $callback; + private $parser; /** * Creates an HTTP server that invokes the given callback for each incoming HTTP request @@ -108,6 +109,27 @@ public function __construct($requestHandler) } $this->callback = $requestHandler; + $this->parser = new RequestHeaderParser(); + + $that = $this; + $this->parser->on('headers', function (ServerRequestInterface $request, $bodyBuffer, ConnectionInterface $conn) use ($that) { + $that->handleRequest($conn, $request); + + if ($bodyBuffer !== '') { + $conn->emit('data', array($bodyBuffer)); + } + }); + + $this->parser->on('error', function(\Exception $e, ConnectionInterface $conn) use ($that) { + $that->emit('error', array($e)); + + // parsing failed => assume dummy request and send appropriate error + $that->writeError( + $conn, + $e->getCode() !== 0 ? $e->getCode() : 400, + new ServerRequest('GET', '/') + ); + }); } /** @@ -154,47 +176,7 @@ public function __construct($requestHandler) */ public function listen(ServerInterface $socket) { - $socket->on('connection', array($this, 'handleConnection')); - } - - /** @internal */ - public function handleConnection(ConnectionInterface $conn) - { - $uriLocal = $conn->getLocalAddress(); - if ($uriLocal !== null) { - // local URI known, so translate transport scheme to application scheme - $uriLocal = \strtr($uriLocal, array('tcp://' => 'http://', 'tls://' => 'https://')); - } - - $uriRemote = $conn->getRemoteAddress(); - - $that = $this; - $parser = new RequestHeaderParser($uriLocal, $uriRemote); - - $listener = array($parser, 'feed'); - $parser->on('headers', function (ServerRequestInterface $request, $bodyBuffer) use ($conn, $listener, $that) { - // parsing request completed => stop feeding parser - $conn->removeListener('data', $listener); - - $that->handleRequest($conn, $request); - - if ($bodyBuffer !== '') { - $conn->emit('data', array($bodyBuffer)); - } - }); - - $conn->on('data', $listener); - $parser->on('error', function(\Exception $e) use ($conn, $listener, $that) { - $conn->removeListener('data', $listener); - $that->emit('error', array($e)); - - // parsing failed => assume dummy request and send appropriate error - $that->writeError( - $conn, - $e->getCode() !== 0 ? $e->getCode() : 400, - new ServerRequest('GET', '/') - ); - }); + $socket->on('connection', array($this->parser, 'handle')); } /** @internal */ diff --git a/tests/Io/RequestHeaderParserTest.php b/tests/Io/RequestHeaderParserTest.php index b47eac15..dbf44e69 100644 --- a/tests/Io/RequestHeaderParserTest.php +++ b/tests/Io/RequestHeaderParserTest.php @@ -12,14 +12,18 @@ public function testSplitShouldHappenOnDoubleCrlf() $parser = new RequestHeaderParser(); $parser->on('headers', $this->expectCallableNever()); - $parser->feed("GET / HTTP/1.1\r\n"); - $parser->feed("Host: example.com:80\r\n"); - $parser->feed("Connection: close\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + + $parser->handle($connection); + + $connection->emit('data', array("GET / HTTP/1.1\r\n")); + $connection->emit('data', array("Host: example.com:80\r\n")); + $connection->emit('data', array("Connection: close\r\n")); $parser->removeAllListeners(); $parser->on('headers', $this->expectCallableOnce()); - $parser->feed("\r\n"); + $connection->emit('data', array("\r\n")); } public function testFeedInOneGo() @@ -27,24 +31,53 @@ public function testFeedInOneGo() $parser = new RequestHeaderParser(); $parser->on('headers', $this->expectCallableOnce()); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + + $data = $this->createGetRequest(); + $connection->emit('data', array($data)); + } + + public function testFeedTwoRequestsOnSeparateConnections() + { + $parser = new RequestHeaderParser(); + + $called = 0; + $parser->on('headers', function () use (&$called) { + ++$called; + }); + + $connection1 = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $connection2 = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection1); + $parser->handle($connection2); + $data = $this->createGetRequest(); - $parser->feed($data); + $connection1->emit('data', array($data)); + $connection2->emit('data', array($data)); + + $this->assertEquals(2, $called); } - public function testHeadersEventShouldReturnRequestAndBodyBuffer() + public function testHeadersEventShouldReturnRequestAndBodyBufferAndConnection() { $request = null; $bodyBuffer = null; + $conn = null; $parser = new RequestHeaderParser(); - $parser->on('headers', function ($parsedRequest, $parsedBodyBuffer) use (&$request, &$bodyBuffer) { + $parser->on('headers', function ($parsedRequest, $parsedBodyBuffer, $connection) use (&$request, &$bodyBuffer, &$conn) { $request = $parsedRequest; $bodyBuffer = $parsedBodyBuffer; + $conn = $connection; }); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + $data = $this->createGetRequest(); $data .= 'RANDOM DATA'; - $parser->feed($data); + $connection->emit('data', array($data)); $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $request); $this->assertSame('GET', $request->getMethod()); @@ -53,6 +86,8 @@ public function testHeadersEventShouldReturnRequestAndBodyBuffer() $this->assertSame(array('Host' => array('example.com'), 'Connection' => array('close')), $request->getHeaders()); $this->assertSame('RANDOM DATA', $bodyBuffer); + + $this->assertSame($connection, $conn); } public function testHeadersEventShouldReturnBinaryBodyBuffer() @@ -64,9 +99,12 @@ public function testHeadersEventShouldReturnBinaryBodyBuffer() $bodyBuffer = $parsedBodyBuffer; }); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + $data = $this->createGetRequest(); $data .= "\0x01\0x02\0x03\0x04\0x05"; - $parser->feed($data); + $connection->emit('data', array($data)); $this->assertSame("\0x01\0x02\0x03\0x04\0x05", $bodyBuffer); } @@ -80,8 +118,11 @@ public function testHeadersEventShouldParsePathAndQueryString() $request = $parsedRequest; }); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + $data = $this->createAdvancedPostRequest(); - $parser->feed($data); + $connection->emit('data', array($data)); $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $request); $this->assertSame('POST', $request->getMethod()); @@ -95,31 +136,39 @@ public function testHeadersEventShouldParsePathAndQueryString() $this->assertSame($headers, $request->getHeaders()); } - public function testHeaderEventWithShouldApplyDefaultAddressFromConstructor() + public function testHeaderEventWithShouldApplyDefaultAddressFromLocalConnectionAddress() { $request = null; - $parser = new RequestHeaderParser('http://127.1.1.1:8000'); + $parser = new RequestHeaderParser(); $parser->on('headers', function ($parsedRequest) use (&$request) { $request = $parsedRequest; }); - $parser->feed("GET /foo HTTP/1.0\r\n\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('getLocalAddress'))->getMock(); + $connection->expects($this->once())->method('getLocalAddress')->willReturn('tcp://127.1.1.1:8000'); + $parser->handle($connection); + + $connection->emit('data', array("GET /foo HTTP/1.0\r\n\r\n")); $this->assertEquals('http://127.1.1.1:8000/foo', $request->getUri()); $this->assertEquals('127.1.1.1:8000', $request->getHeaderLine('Host')); } - public function testHeaderEventViaHttpsShouldApplySchemeFromConstructor() + public function testHeaderEventViaHttpsShouldApplyHttpsSchemeFromLocalTlsConnectionAddress() { $request = null; - $parser = new RequestHeaderParser('https://127.1.1.1:8000'); + $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"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('getLocalAddress'))->getMock(); + $connection->expects($this->once())->method('getLocalAddress')->willReturn('tls://127.1.1.1:8000'); + $parser->handle($connection); + + $connection->emit('data', array("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n")); $this->assertEquals('https://example.com/foo', $request->getUri()); $this->assertEquals('example.com', $request->getHeaderLine('Host')); @@ -128,26 +177,24 @@ public function testHeaderEventViaHttpsShouldApplySchemeFromConstructor() public function testHeaderOverflowShouldEmitError() { $error = null; - $passedParser = null; + $passedConnection = null; $parser = new RequestHeaderParser(); $parser->on('headers', $this->expectCallableNever()); - $parser->on('error', function ($message, $parser) use (&$error, &$passedParser) { + $parser->on('error', function ($message, $connection) use (&$error, &$passedConnection) { $error = $message; - $passedParser = $parser; + $passedConnection = $connection; }); - $this->assertSame(1, count($parser->listeners('headers'))); - $this->assertSame(1, count($parser->listeners('error'))); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); $data = str_repeat('A', 8193); - $parser->feed($data); + $connection->emit('data', array($data)); $this->assertInstanceOf('OverflowException', $error); $this->assertSame('Maximum header size of 8192 exceeded.', $error->getMessage()); - $this->assertSame($parser, $passedParser); - $this->assertSame(0, count($parser->listeners('headers'))); - $this->assertSame(0, count($parser->listeners('error'))); + $this->assertSame($connection, $passedConnection); } public function testHeaderOverflowShouldNotEmitErrorWhenDataExceedsMaxHeaderSize() @@ -161,11 +208,13 @@ public function testHeaderOverflowShouldNotEmitErrorWhenDataExceedsMaxHeaderSize $bodyBuffer = $parsedBodyBuffer; }); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + $data = $this->createAdvancedPostRequest(); $body = str_repeat('A', 8193 - strlen($data)); $data .= $body; - - $parser->feed($data); + $connection->emit('data', array($data)); $headers = array( 'Host' => array('example.com'), @@ -187,15 +236,13 @@ public function testInvalidEmptyRequestHeadersParseException() $error = $message; }); - $this->assertSame(1, count($parser->listeners('headers'))); - $this->assertSame(1, count($parser->listeners('error'))); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); - $parser->feed("\r\n\r\n"); + $connection->emit('data', array("\r\n\r\n")); $this->assertInstanceOf('InvalidArgumentException', $error); $this->assertSame('Unable to parse invalid request-line', $error->getMessage()); - $this->assertSame(0, count($parser->listeners('headers'))); - $this->assertSame(0, count($parser->listeners('error'))); } public function testInvalidMalformedRequestLineParseException() @@ -208,15 +255,13 @@ public function testInvalidMalformedRequestLineParseException() $error = $message; }); - $this->assertSame(1, count($parser->listeners('headers'))); - $this->assertSame(1, count($parser->listeners('error'))); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); - $parser->feed("GET /\r\n\r\n"); + $connection->emit('data', array("GET /\r\n\r\n")); $this->assertInstanceOf('InvalidArgumentException', $error); $this->assertSame('Unable to parse invalid request-line', $error->getMessage()); - $this->assertSame(0, count($parser->listeners('headers'))); - $this->assertSame(0, count($parser->listeners('error'))); } public function testInvalidAbsoluteFormSchemeEmitsError() @@ -229,7 +274,10 @@ public function testInvalidAbsoluteFormSchemeEmitsError() $error = $message; }); - $parser->feed("GET tcp://example.com:80/ HTTP/1.0\r\n\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + + $connection->emit('data', array("GET tcp://example.com:80/ HTTP/1.0\r\n\r\n")); $this->assertInstanceOf('InvalidArgumentException', $error); $this->assertSame('Invalid absolute-form request-target', $error->getMessage()); @@ -245,7 +293,10 @@ public function testOriginFormWithSchemeSeparatorInParam() $request = $parsedRequest; }); - $parser->feed("GET /somepath?param=http://example.com HTTP/1.1\r\nHost: localhost\r\n\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + + $connection->emit('data', array("GET /somepath?param=http://example.com HTTP/1.1\r\nHost: localhost\r\n\r\n")); $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $request); $this->assertSame('GET', $request->getMethod()); @@ -267,7 +318,10 @@ public function testUriStartingWithColonSlashSlashFails() $error = $message; }); - $parser->feed("GET ://example.com:80/ HTTP/1.0\r\n\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + + $connection->emit('data', array("GET ://example.com:80/ HTTP/1.0\r\n\r\n")); $this->assertInstanceOf('InvalidArgumentException', $error); $this->assertSame('Invalid request string', $error->getMessage()); @@ -283,7 +337,10 @@ public function testInvalidAbsoluteFormWithFragmentEmitsError() $error = $message; }); - $parser->feed("GET http://example.com:80/#home HTTP/1.0\r\n\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + + $connection->emit('data', array("GET http://example.com:80/#home HTTP/1.0\r\n\r\n")); $this->assertInstanceOf('InvalidArgumentException', $error); $this->assertSame('Invalid absolute-form request-target', $error->getMessage()); @@ -299,7 +356,10 @@ public function testInvalidHeaderContainsFullUri() $error = $message; }); - $parser->feed("GET / HTTP/1.1\r\nHost: http://user:pass@host/\r\n\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + + $connection->emit('data', array("GET / HTTP/1.1\r\nHost: http://user:pass@host/\r\n\r\n")); $this->assertInstanceOf('InvalidArgumentException', $error); $this->assertSame('Invalid Host header value', $error->getMessage()); @@ -315,7 +375,10 @@ public function testInvalidAbsoluteFormWithHostHeaderEmpty() $error = $message; }); - $parser->feed("GET http://example.com/ HTTP/1.1\r\nHost: \r\n\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + + $connection->emit('data', array("GET http://example.com/ HTTP/1.1\r\nHost: \r\n\r\n")); $this->assertInstanceOf('InvalidArgumentException', $error); $this->assertSame('Invalid Host header value', $error->getMessage()); @@ -331,7 +394,10 @@ public function testInvalidConnectRequestWithNonAuthorityForm() $error = $message; }); - $parser->feed("CONNECT http://example.com:8080/ HTTP/1.1\r\nHost: example.com:8080\r\n\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + + $connection->emit('data', array("CONNECT http://example.com:8080/ HTTP/1.1\r\nHost: example.com:8080\r\n\r\n")); $this->assertInstanceOf('InvalidArgumentException', $error); $this->assertSame('CONNECT method MUST use authority-form request target', $error->getMessage()); @@ -347,7 +413,10 @@ public function testInvalidHttpVersion() $error = $message; }); - $parser->feed("GET / HTTP/1.2\r\n\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + + $connection->emit('data', array("GET / HTTP/1.2\r\n\r\n")); $this->assertInstanceOf('InvalidArgumentException', $error); $this->assertSame(505, $error->getCode()); @@ -358,16 +427,19 @@ public function testServerParamsWillBeSetOnHttpsRequest() { $request = null; - $parser = new RequestHeaderParser( - 'https://127.1.1.1:8000', - 'https://192.168.1.1:8001' - ); + $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"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('getLocalAddress', 'getRemoteAddress'))->getMock(); + $connection->expects($this->once())->method('getLocalAddress')->willReturn('tls://127.1.1.1:8000'); + $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tls://192.168.1.1:8001'); + $parser->handle($connection); + + $connection->emit('data', array("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n")); + $serverParams = $request->getServerParams(); $this->assertEquals('on', $serverParams['HTTPS']); @@ -385,16 +457,19 @@ public function testServerParamsWillBeSetOnHttpRequest() { $request = null; - $parser = new RequestHeaderParser( - 'http://127.1.1.1:8000', - 'http://192.168.1.1:8001' - ); + $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"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('getLocalAddress', 'getRemoteAddress'))->getMock(); + $connection->expects($this->once())->method('getLocalAddress')->willReturn('tcp://127.1.1.1:8000'); + $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://192.168.1.1:8001'); + $parser->handle($connection); + + $connection->emit('data', array("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n")); + $serverParams = $request->getServerParams(); $this->assertArrayNotHasKey('HTTPS', $serverParams); @@ -412,16 +487,19 @@ public function testServerParamsWillNotSetRemoteAddressForUnixDomainSockets() { $request = null; - $parser = new RequestHeaderParser( - 'unix://./server.sock', - 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"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('getLocalAddress', 'getRemoteAddress'))->getMock(); + $connection->expects($this->once())->method('getLocalAddress')->willReturn('unix://./server.sock'); + $connection->expects($this->once())->method('getRemoteAddress')->willReturn(null); + $parser->handle($connection); + + $connection->emit('data', array("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n")); + $serverParams = $request->getServerParams(); $this->assertArrayNotHasKey('HTTPS', $serverParams); @@ -445,7 +523,11 @@ public function testServerParamsWontBeSetOnMissingUrls() $request = $parsedRequest; }); - $parser->feed("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + + $connection->emit('data', array("GET /foo HTTP/1.0\r\nHost: example.com\r\n\r\n")); + $serverParams = $request->getServerParams(); $this->assertNotEmpty($serverParams['REQUEST_TIME']); @@ -468,7 +550,11 @@ public function testQueryParmetersWillBeSet() $request = $parsedRequest; }); - $parser->feed("GET /foo.php?hello=world&test=this HTTP/1.0\r\nHost: example.com\r\n\r\n"); + $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock(); + $parser->handle($connection); + + $connection->emit('data', array("GET /foo.php?hello=world&test=this HTTP/1.0\r\nHost: example.com\r\n\r\n")); + $queryParams = $request->getQueryParams(); $this->assertEquals('world', $queryParams['hello']);