diff --git a/.gitignore b/.gitignore index e629a84fb..bc59dbd0b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /tests/Bridge/Symfony/cache /tests/Bridge/Symfony/logs /.bref/ +/.php_cs.cache diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..c926d335c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +language: php + +notifications: + email: + on_success: never + +php: + - 7.1 + - 7.2 + - nightly + +matrix: + fast_finish: true + allow_failures: + - php: nightly + include: + - php: 7.1 + env: dependencies=lowest + +cache: + directories: + - $HOME/.composer/cache + +before_script: + - composer install -n + - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable -n; fi; + +script: + - vendor/bin/phpunit diff --git a/src/Application.php b/src/Application.php index 3b7d414cd..4e842d0c3 100644 --- a/src/Application.php +++ b/src/Application.php @@ -99,7 +99,7 @@ public function run() : void // Run the appropriate handler if (isset($event['httpMethod'])) { // HTTP request - $request = (new RequestFactory)->fromLambdaEvent($event); + $request = RequestFactory::fromLambdaEvent($event); $response = $this->httpHandler->handle($request); $output = LambdaResponse::fromPsr7Response($response)->toJson(); } elseif (isset($event['cli'])) { diff --git a/src/Bridge/Psr7/RequestFactory.php b/src/Bridge/Psr7/RequestFactory.php index 8efae900d..8f7f5642b 100644 --- a/src/Bridge/Psr7/RequestFactory.php +++ b/src/Bridge/Psr7/RequestFactory.php @@ -4,20 +4,27 @@ namespace Bref\Bridge\Psr7; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; use Zend\Diactoros\ServerRequest; +use Zend\Diactoros\Stream; /** - * Create a PSR-7 request from a lambda event. + * Creates PSR-7 requests. * * @author Matthieu Napoli */ class RequestFactory { - public function fromLambdaEvent(array $event) : ServerRequestInterface + /** + * Create a PSR-7 server request from an AWS Lambda HTTP event. + */ + public static function fromLambdaEvent(array $event) : ServerRequestInterface { $method = $event['httpMethod'] ?? 'GET'; $query = $event['queryStringParameters'] ?? []; - parse_str($event['body'] ?? '', $request); + $bodyString = $event['body'] ?? ''; + $body = self::createBodyStream($bodyString); + $parsedBody = null; $files = []; $uri = $event['requestContext']['path'] ?? '/'; $headers = $event['headers'] ?? []; @@ -25,6 +32,11 @@ public function fromLambdaEvent(array $event) : ServerRequestInterface // TODO $cookies = []; + $contentType = $headers['Content-Type'] ?? null; + if ($method === 'POST' && $contentType === 'application/x-www-form-urlencoded') { + parse_str($bodyString, $parsedBody); + } + $server = [ 'SERVER_PROTOCOL' => $protocolVersion, 'REQUEST_METHOD' => $method, @@ -39,12 +51,21 @@ public function fromLambdaEvent(array $event) : ServerRequestInterface $files, $uri, $method, - 'file:///dev/null', + $body, $headers, $cookies, $query, - $request, + $parsedBody, $protocolVersion ); } + + private static function createBodyStream(string $body) : StreamInterface + { + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $body); + rewind($stream); + + return new Stream($stream); + } } diff --git a/tests/Bridge/Psr7/RequestFactoryTest.php b/tests/Bridge/Psr7/RequestFactoryTest.php new file mode 100644 index 000000000..fcb2747c6 --- /dev/null +++ b/tests/Bridge/Psr7/RequestFactoryTest.php @@ -0,0 +1,109 @@ + 'GET', + 'queryStringParameters' => [ + 'foo' => 'bar', + 'bim' => 'baz', + ], + 'requestContext' => [ + 'protocol' => '1.1', + 'path' => '/test', + 'requestTimeEpoch' => $currentTimestamp, + ], + 'headers' => [ + ], + ]); + + self::assertEquals('GET', $request->getMethod()); + self::assertEquals(['foo' => 'bar', 'bim' => 'baz'], $request->getQueryParams()); + self::assertEquals('1.1', $request->getProtocolVersion()); + self::assertEquals('/test', $request->getUri()->__toString()); + self::assertEquals('', $request->getBody()->getContents()); + self::assertEquals([], $request->getAttributes()); + $serverParams = $request->getServerParams(); + unset($serverParams['DOCUMENT_ROOT']); + self::assertEquals([ + 'SERVER_PROTOCOL' => '1.1', + 'REQUEST_METHOD' => 'GET', + 'REQUEST_TIME' => $currentTimestamp, + 'QUERY_STRING' => 'foo=bar&bim=baz', + 'REQUEST_URI' => '/test', + ], $serverParams); + self::assertEquals('/test', $request->getRequestTarget()); + self::assertEquals([], $request->getHeaders()); + } + + public function test non empty body() + { + $request = RequestFactory::fromLambdaEvent([ + 'httpMethod' => 'GET', + 'body' => 'test test test', + ]); + + self::assertEquals('test test test', $request->getBody()->getContents()); + } + + public function test POST body is parsed() + { + $request = RequestFactory::fromLambdaEvent([ + 'httpMethod' => 'POST', + 'headers' => [ + 'Content-Type' => 'application/x-www-form-urlencoded', + ], + 'body' => 'foo=bar&bim=baz', + ]); + self::assertEquals('POST', $request->getMethod()); + self::assertEquals(['foo' => 'bar', 'bim' => 'baz'], $request->getParsedBody()); + } + + public function test POST JSON body is not parsed() + { + $request = RequestFactory::fromLambdaEvent([ + 'httpMethod' => 'POST', + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'body' => json_encode(['foo' => 'bar']), + ]); + self::assertEquals('POST', $request->getMethod()); + self::assertEquals(null, $request->getParsedBody()); + self::assertEquals(['foo' => 'bar'], json_decode($request->getBody()->getContents(), true)); + } + + public function test multipart form data is not supported() + { + $request = RequestFactory::fromLambdaEvent([ + 'httpMethod' => 'POST', + 'headers' => [ + 'Content-Type' => 'multipart/form-data', + ], + 'body' => 'abcd', + ]); + self::assertEquals('POST', $request->getMethod()); + self::assertNull(null, $request->getParsedBody()); + } + + public function test cookies are not supported() + { + $request = RequestFactory::fromLambdaEvent([ + 'httpMethod' => 'GET', + 'headers' => [ + 'Cookie' => 'theme=light', + ], + ]); + self::assertEquals([], $request->getCookieParams()); + } +}