Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/website/node_modules/
.DS_Store
/.serverless/
.phpunit.cache
.phpunit.result.cache
node_modules/
package-lock.json
/.claude
Expand Down
5 changes: 5 additions & 0 deletions src/Bref.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public static function events(): EventDispatcher
return self::$eventDispatcher;
}

public static function isRunningInStreamingMode(): bool
{
return (bool) getenv('BREF_STREAMED_MODE');
}

/**
* @internal Used by the Bref runtime
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Event/Http/HttpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ abstract class HttpHandler implements Handler
abstract public function handleRequest(HttpRequestEvent $event, Context $context): HttpResponse;

/** {@inheritDoc} */
public function handle($event, Context $context): array
public function handle($event, Context $context): array|\Generator
{
// See https://bref.sh/docs/runtimes/http.html#cold-starts
if (isset($event['warmer']) && $event['warmer'] === true) {
Expand Down
71 changes: 65 additions & 6 deletions src/Event/Http/HttpResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,31 @@

namespace Bref\Event\Http;

use Bref\Bref;
use Generator;

/**
* Formats the response expected by AWS Lambda and the API Gateway integration.
*/
final class HttpResponse
{
private int $statusCode;
private array $headers;
private string $body;
private string|\Generator $body;

/**
* @param array<string|string[]> $headers
*/
public function __construct(string $body, array $headers = [], int $statusCode = 200)
public function __construct(string|\Generator $body, array $headers = [], int $statusCode = 200)
{
$this->body = $body;
$this->headers = $headers;
$this->statusCode = $statusCode;
}

public function toApiGatewayFormat(bool $multiHeaders = false, ?string $awsRequestId = null): array
public function toApiGatewayFormat(bool $multiHeaders = false, ?string $awsRequestId = null): array|\Generator
{
$isStreamedMode = Bref::isRunningInStreamingMode();
$base64Encoding = (bool) getenv('BREF_BINARY_RESPONSES');

$headers = [];
Expand All @@ -49,19 +53,28 @@ public function toApiGatewayFormat(bool $multiHeaders = false, ?string $awsReque

// This is the format required by the AWS_PROXY lambda integration
// See https://stackoverflow.com/questions/43708017/aws-lambda-api-gateway-error-malformed-lambda-proxy-response

if ($isStreamedMode) {
return $this->yieldBody([
'statusCode' => $this->statusCode,
$headersKey => $headers,
]);
}

return [
'isBase64Encoded' => $base64Encoding,
'statusCode' => $this->statusCode,
$headersKey => $headers,
'body' => $base64Encoding ? base64_encode($this->body) : $this->body,
'body' => $base64Encoding ? base64_encode($this->getBodyAsString()) : $this->getBodyAsString(),
];
}

/**
* See https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.response
*/
public function toApiGatewayFormatV2(?string $awsRequestId = null): array
public function toApiGatewayFormatV2(?string $awsRequestId = null): array|\Generator
{
$isStreamedMode = Bref::isRunningInStreamingMode();
$base64Encoding = (bool) getenv('BREF_BINARY_RESPONSES');

$headers = [];
Expand All @@ -87,15 +100,61 @@ public function toApiGatewayFormatV2(?string $awsRequestId = null): array
// serialized to `[]` (we want `{}`) so we force it to an empty object.
$headers = empty($headers) ? new \stdClass : $headers;

if ($isStreamedMode) {
return $this->yieldBody([
'cookies' => $cookies,
'statusCode' => $this->statusCode,
'headers' => $headers,
]);
}

return [
'cookies' => $cookies,
'isBase64Encoded' => $base64Encoding,
'statusCode' => $this->statusCode,
'headers' => $headers,
'body' => $base64Encoding ? base64_encode($this->body) : $this->body,
'body' => $base64Encoding ? base64_encode($this->getBodyAsString()) : $this->getBodyAsString(),
];
}

/**
* Yields the metadata, the null-byte separator and the body chunks in the
* Lambda Streaming Format.
*
* @param array $metadata
*/
private function yieldBody(array $metadata): Generator
{
yield json_encode($metadata);

yield "\0\0\0\0\0\0\0\0";

if ($this->body instanceof Generator) {
foreach ($this->body as $dataChunk) {
yield $dataChunk;
}
} else {
yield $this->body;
}
}

private function getBodyAsString(): string
{
if ($this->body instanceof Generator) {
$dataChunk = '';

while ($this->body->valid()) {
$dataChunk .= $this->body->current();

$this->body->next();
}

return $dataChunk;
}

return $this->body;
}

/**
* See https://github.com/zendframework/zend-diactoros/blob/754a2ceb7ab753aafe6e3a70a1fb0370bde8995c/src/Response/SapiEmitterTrait.php#L96
*/
Expand Down
Loading
Loading