diff --git a/src/Event/Http/HttpHandler.php b/src/Event/Http/HttpHandler.php index ebd7092d5..6e61793c1 100644 --- a/src/Event/Http/HttpHandler.php +++ b/src/Event/Http/HttpHandler.php @@ -25,9 +25,9 @@ public function handle($event, Context $context): array $response = $this->handleRequest($httpEvent, $context); if ($httpEvent->isFormatV2()) { - return $response->toApiGatewayFormatV2(); + return $response->toApiGatewayFormatV2($context->getAwsRequestId()); } - return $response->toApiGatewayFormat($httpEvent->hasMultiHeader()); + return $response->toApiGatewayFormat($httpEvent->hasMultiHeader(), $context->getAwsRequestId()); } } diff --git a/src/Event/Http/HttpResponse.php b/src/Event/Http/HttpResponse.php index 80a679bc2..76c24d5d7 100644 --- a/src/Event/Http/HttpResponse.php +++ b/src/Event/Http/HttpResponse.php @@ -21,7 +21,7 @@ public function __construct(string $body, array $headers = [], int $statusCode = $this->statusCode = $statusCode; } - public function toApiGatewayFormat(bool $multiHeaders = false): array + public function toApiGatewayFormat(bool $multiHeaders = false, ?string $awsRequestId = null): array { $base64Encoding = (bool) getenv('BREF_BINARY_RESPONSES'); @@ -38,6 +38,8 @@ public function toApiGatewayFormat(bool $multiHeaders = false): array } } + $this->checkHeadersSize($headers, $awsRequestId); + // The headers must be a JSON object. If the PHP array is empty it is // serialized to `[]` (we want `{}`) so we force it to an empty object. $headers = empty($headers) ? new \stdClass : $headers; @@ -58,7 +60,7 @@ public function toApiGatewayFormat(bool $multiHeaders = false): array /** * See https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.response */ - public function toApiGatewayFormatV2(): array + public function toApiGatewayFormatV2(?string $awsRequestId = null): array { $base64Encoding = (bool) getenv('BREF_BINARY_RESPONSES'); @@ -76,6 +78,11 @@ public function toApiGatewayFormatV2(): array } } + $this->checkHeadersSize(array_merge( + $headers, + ['Set-Cookie' => $cookies], // include cookies in the size check + ), $awsRequestId); + // The headers must be a JSON object. If the PHP array is empty it is // serialized to `[]` (we want `{}`) so we force it to an empty object. $headers = empty($headers) ? new \stdClass : $headers; @@ -98,4 +105,31 @@ private function capitalizeHeaderName(string $name): string $name = ucwords($name); return str_replace(' ', '-', $name); } + + /** + * API Gateway v1 and v2 have a headers total max size of 10 KB. + * ALB has a max size of 32 KB. + * It's hard to calculate the exact size of headers here, so we just + * estimate it roughly: if above 9.5 KB we log a warning. + * + * @param array $headers + */ + private function checkHeadersSize(array $headers, ?string $awsRequestId): void + { + $estimatedHeadersSize = 0; + foreach ($headers as $name => $values) { + $estimatedHeadersSize += strlen($name); + if (is_array($values)) { + foreach ($values as $value) { + $estimatedHeadersSize += strlen($value); + } + } else { + $estimatedHeadersSize += strlen($values); + } + } + + if ($estimatedHeadersSize > 9_500) { + echo "$awsRequestId\tWARNING\tThe total size of HTTP response headers is estimated to be above 10 KB, which is the API Gateway limit. If the limit is reached, the HTTP response will be a 500 error.\n"; + } + } }