From 6e7dd6634b66bf23c1b1205e4e1e256541d359e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 19 Apr 2019 14:53:16 +0200 Subject: [PATCH] Reuse single request parser for all requests This changeset is in preparation for upcoming refactorings to move unrelated logic out of the parser class to prepare for persistent HTTP connections in follow-up PR. This changeset does not affect the public API and happens to improves performance slightly from around 9000 req/s to 9200 req/s on my machine (best of 5). --- src/Io/RequestHeaderParser.php | 94 +++++++----- src/StreamingServer.php | 64 +++----- tests/Io/RequestHeaderParserTest.php | 210 +++++++++++++++++++-------- 3 files changed, 230 insertions(+), 138 deletions(-) diff --git a/src/Io/RequestHeaderParser.php b/src/Io/RequestHeaderParser.php index 6220f022..88a554d7 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,73 @@ */ 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 = ''; + $maxSize = $this->maxSize; + $that = $this; + $conn->on('data', $fn = function ($data) use (&$buffer, &$fn, $conn, $maxSize, $that) { + // 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 > $maxSize || ($endOfHeader === false && isset($buffer[$maxSize]))) { + $conn->removeListener('data', $fn); + $fn = null; + + $that->emit('error', array( + new \OverflowException("Maximum header size of {$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 = $that->parseRequest( + (string)\substr($buffer, 0, $endOfHeader), + $conn->getRemoteAddress(), + $conn->getLocalAddress() + ); } catch (Exception $exception) { - $this->emit('error', array($exception)); - $this->removeAllListeners(); + $buffer = ''; + $that->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 = ''; + $that->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 + * @internal */ - private function parseRequest($headers) + public function parseRequest($headers, $remoteSocketUri, $localSocketUri) { // additional, stricter safe-guard for request line // because request parser doesn't properly cope with invalid ones @@ -103,8 +127,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 +136,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 +201,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 +222,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']);