Skip to content

Commit

Permalink
Merge pull request #600 from brefphp/max-upload-size
Browse files Browse the repository at this point in the history
Fix #599 Set up the same "max upload size" in PHP as API Gateway/Lambda
  • Loading branch information
mnapoli authored Mar 25, 2020
2 parents 151cc66 + f35d0d5 commit b222348
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 118 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,7 @@ demo:
layers.json:
php runtime/layers/layer-list.php

.PHONY: runtimes website website-preview website-assets demo layers.json
test-stack:
serverless deploy -c tests/serverless.tests.yml

.PHONY: runtimes website website-preview website-assets demo layers.json test-stack
1 change: 1 addition & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ parameters:
- %rootDir%/../../../tests/Bridge/Symfony/logs/*
- %rootDir%/../../../tests/Sam/Php/*
- %rootDir%/../../../tests/Sam/PhpFpm/*
- %rootDir%/../../../tests/Functional/fpm/*

ignoreErrors:
-
Expand Down
2 changes: 2 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
<testsuites>
<testsuite name="small">
<directory>./tests/</directory>
<exclude>./tests/Functional</exclude>
<exclude>./tests/Sam</exclude>
</testsuite>
<testsuite name="runtimes">
<directory>./tests/Functional</directory>
<directory>./tests/Sam</directory>
</testsuite>
</testsuites>
Expand Down
5 changes: 5 additions & 0 deletions runtime/layers/fpm/php.ini
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ variables_order="EGPCS"
; The lambda environment is not compatible with fastcgi_finish_request
; See https://github.com/brefphp/bref/issues/214
disable_functions=fastcgi_finish_request

; The total upload size limit is 6Mb, we override the defaults to match this limit
; API Gateway has a 10Mb limit, but Lambda's is 6Mb
post_max_size=6M
upload_max_filesize=6M
2 changes: 2 additions & 0 deletions runtime/layers/web/default.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ server {
server_name php-docker.local;
root /var/task/##DOCUMENT_ROOT##;

client_max_body_size 6M;

location / {
# try to serve file directly, fallback to handler
try_files $uri /##HANDLER_DR##$is_args$args;
Expand Down
244 changes: 244 additions & 0 deletions tests/Functional/FpmRuntimeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
<?php declare(strict_types=1);

namespace Bref\Test\Functional;

use GuzzleHttp\Client;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;

class FpmRuntimeTest extends TestCase
{
/** @var Client */
private $http;

public function setUp(): void
{
parent::setUp();

$this->http = new Client([
'base_uri' => 'https://5octfcz6gc.execute-api.eu-west-1.amazonaws.com/dev/',
'http_errors' => false,
]);
}

public function test GET()
{
$response = $this->http->request('GET');

$this->assertResponseSuccessful($response);
self::assertEquals('Hello world!', $this->getBody($response));
}

public function test GET with query parameter()
{
$response = $this->http->request('GET', '?name=Abby');

$this->assertResponseSuccessful($response);
self::assertEquals('Hello Abby', $this->getBody($response));
}

public function test stderr do not show in the HTTP response()
{
$response = $this->http->request('GET', '?stderr=1');

$this->assertResponseSuccessful($response);
self::assertNotContains('This is a test log into stderr', $this->responseAsString($response));
}

public function test error_log function()
{
$response = $this->http->request('GET', '?error_log=1');

$this->assertResponseSuccessful($response);
self::assertNotContains('This is a test log from error_log', $this->responseAsString($response));
}

public function test uncaught exception returns a 500 without the details()
{
$response = $this->http->request('GET', '?exception=1');

self::assertSame(500, $response->getStatusCode());
self::assertNotContains('This is an uncaught exception', $this->responseAsString($response));
}

public function test error returns a 500 without the details()
{
$response = $this->http->request('GET', '?error=1');

self::assertSame(500, $response->getStatusCode());
self::assertNotContains('strlen() expects exactly 1 parameter, 0 given', $this->responseAsString($response));
}

public function test fatal error returns a 500 without the details()
{
$response = $this->http->request('GET', '?fatal_error=1');

self::assertSame(500, $response->getStatusCode());
self::assertNotContains("require(): Failed opening required 'foo'", $this->responseAsString($response));
}

public function test warnings do not fail the request and do not appear in the response()
{
$response = $this->http->request('GET', '?warning=1');

$this->assertResponseSuccessful($response);
self::assertEquals('Hello world!', $this->getBody($response));
self::assertNotContains('This is a test warning', $this->responseAsString($response));
}

public function test php extensions()
{
$response = $this->http->request('GET', '?extensions=1');
$extensions = $this->getJsonBody($response);
sort($extensions);

self::assertEquals([
'Core',
'PDO',
'Phar',
'Reflection',
'SPL',
'SimpleXML',
'Zend OPcache',
'bcmath',
'cgi-fcgi',
'ctype',
'curl',
'date',
'dom',
'exif',
'fileinfo',
'filter',
'ftp',
'gd',
'gettext',
'hash',
'iconv',
'json',
'libxml',
'mbstring',
'mysqli',
'mysqlnd',
'openssl',
'pcntl',
'pcre',
'pdo_sqlite',
'posix',
'readline',
'session',
'soap',
'sockets',
'sodium',
'sqlite3',
'standard',
'tokenizer',
'xml',
'xmlreader',
'xmlwriter',
'xsl',
'zip',
'zlib',
], $extensions);
}

/**
* Check some PHP config values
*/
public function test php config()
{
$response = $this->http->request('GET', '?php-config=1');

self::assertArraySubset([
// On PHP-FPM we don't want errors to be sent to stdout because that sends them to the HTTP response
'display_errors' => '0',
// This is sent to PHP-FPM, which sends them back to CloudWatch
'error_log' => null,
// This is the default production value
'error_reporting' => (string) (E_ALL & ~E_DEPRECATED & ~E_STRICT),
'extension_dir' => '/opt/bref/lib/php/extensions/no-debug-zts-20190902',
// Same limit as API Gateway
'max_execution_time' => '30',
'max_input_time' => '60',
// Use the max amount of memory possibly available, lambda will limit us
'memory_limit' => '3008M',
'opcache.enable' => '1',
'opcache.enable_cli' => '0',
// Since we have PHP-FPM we don't need the file cache here
'opcache.file_cache' => null,
'opcache.max_accelerated_files' => '10000',
'opcache.memory_consumption' => '128',
// This is to make sure that we don't strip comments from source code since it would break annotations
'opcache.save_comments' => '1',
// The code is readonly on lambdas so it never changes
'opcache.validate_timestamps' => '0',
'short_open_tag' => '',
'zend.assertions' => '-1',
'zend.enable_gc' => '1',
// Check POST configuration
'post_max_size' => '6M',
'upload_max_filesize' => '6M',
], $this->getJsonBody($response), false);
}

public function test environment variables()
{
$response = $this->http->request('GET', '?env=1');

self::assertEquals([
'$_ENV' => 'bar',
'$_SERVER' => 'bar',
'getenv' => 'bar',
], $this->getJsonBody($response));
}

public function test error on invalid URL()
{
$response = $this->http->request('GET', 'missing-handler');

self::assertSame(403, $response->getStatusCode());
self::assertEquals(['message' => 'Missing Authentication Token'], $this->getJsonBody($response));
}

/**
* The API Gateway limit is 10Mb, but Lambda is 6Mb.
*
* @see https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html
* We check with 4Mb because this works. 5Mb fails, maybe because the whole size of the event
* is larger (because of the whole JSON formatting plus headers?).
*/
public function test max upload size is 6Mb()
{
$body4Mb = str_repeat(' ', 1024 * 1024 * 4);
$response = $this->http->request('POST', '', [
'body' => $body4Mb,
]);
$this->assertResponseSuccessful($response);
self::assertEquals('Received 4Mb', $this->getBody($response));
}

private function assertResponseSuccessful(ResponseInterface $response): void
{
self::assertSame(200, $response->getStatusCode(), $this->getBody($response));
}

private function getBody(ResponseInterface $response): string
{
return $response->getBody()->__toString();
}

private function getJsonBody(ResponseInterface $response)
{
return json_decode($response->getBody()->getContents(), true);
}

private function responseAsString(ResponseInterface $response): string
{
$string = '';
foreach ($response->getHeaders() as $name => $values) {
$string .= $name . ': ' . implode(', ', $values) . "\n";
}
$string .= "\n" . $this->getBody($response) . "\n";

return $string;
}
}
58 changes: 58 additions & 0 deletions tests/Functional/fpm/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php declare(strict_types=1);

if ($_GET['extensions'] ?? false) {
header('Content-Type: application/json');
echo json_encode(get_loaded_extensions(), JSON_PRETTY_PRINT);
return;
}

if ($_GET['php-config'] ?? false) {
header('Content-Type: application/json');
echo json_encode(ini_get_all(null, false), JSON_PRETTY_PRINT);
return;
}

if ($_GET['env'] ?? false) {
header('Content-Type: application/json');
echo json_encode([
'$_ENV' => $_ENV['FOO'] ?? null,
'$_SERVER' => $_SERVER['FOO'] ?? null,
'getenv' => getenv('FOO'),
], JSON_PRETTY_PRINT);
return;
}

if ($_GET['stderr'] ?? false) {
$stderr = fopen('php://stderr', 'a');
fwrite($stderr, 'This is a test log into stderr');
fclose($stderr);
}

if ($_GET['error_log'] ?? false) {
error_log('This is a test log from error_log');
}

if ($_GET['exception'] ?? false) {
throw new Exception('This is an uncaught exception');
}

if ($_GET['error'] ?? false) {
strlen();
}

if ($_GET['fatal_error'] ?? false) {
require 'foo';
}

if ($_GET['warning'] ?? false) {
trigger_error('This is a test warning', E_USER_WARNING);
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$body = file_get_contents('php://input');
$bodySize = round(strlen($body) / 1024 / 1024);
echo "Received {$bodySize}Mb";
return;
}

echo 'Hello ' . ($_GET['name'] ?? 'world!');
Loading

0 comments on commit b222348

Please sign in to comment.