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
22 changes: 22 additions & 0 deletions docs/use-cases/http/advanced-use-cases.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,28 @@ functions:
url: true
```

#### Invoke Method RESPONSE_STREAM

In order to support Lambda Function URLs invoke method as `RESPONSE_STREAM` you need to change to settings:

* BREF_STREAMED_MODE = 1 in the lambda environment
* invokeMode: RESPONSE_STREAM in the function url settings

Like the following sample config:

```yml filename="serverless.yml"
functions:
hello:
handler: MyApp\Handlers\MyLambdaUrlHandler
# ...
environment:
BREF_STREAMED_MODE: 1
url:
invokeMode: RESPONSE_STREAM
```

Be aware that you must implement an `HttpHandler` handler, or use something like Laravel Octane handler if you are using Laravel.

### API Gateway v1 REST API

The syntax is slightly different from API Gateway v2 HTTP APIs as we must use a different `events` configuration. Here is an example that sends all requests to a single Lambda function:
Expand Down
10 changes: 10 additions & 0 deletions src/Bref.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ class Bref
];
private static EventDispatcher $eventDispatcher;

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

public static function doesStreamingSupportsFibers(): bool
{
return PHP_VERSION_ID >= 80100 && ! (bool) getenv('BREF_STREAM_NO_FIBER') && class_exists('Fiber');
}

/**
* Configure the container that provides Lambda handlers.
*
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
103 changes: 86 additions & 17 deletions src/Event/Http/HttpResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,30 @@

namespace Bref\Event\Http;

use Bref\Bref;

/**
* 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): array
public function toApiGatewayFormat(bool $multiHeaders = false): array|\Generator
{
$isStreamedMode = Bref::isRunningInStreamingMode();
$base64Encoding = (bool) getenv('BREF_BINARY_RESPONSES');

$headers = [];
Expand All @@ -47,19 +50,40 @@ public function toApiGatewayFormat(bool $multiHeaders = false): array

// 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
return [
'isBase64Encoded' => $base64Encoding,
'statusCode' => $this->statusCode,
$headersKey => $headers,
'body' => $base64Encoding ? base64_encode($this->body) : $this->body,
];

if ($isStreamedMode) {
return $this->yieldBody([
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably early return

'statusCode' => $this->statusCode,
$headersKey => $headers,
]);
} else {
if ($this->body instanceof \Generator) {
$dataChunk = '';

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

$this->body->next();
}
} else {
$dataChunk = $this->body;
}

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

/**
* 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(): array|\Generator
{
$isStreamedMode = Bref::isRunningInStreamingMode();
$base64Encoding = (bool) getenv('BREF_BINARY_RESPONSES');

$headers = [];
Expand All @@ -80,13 +104,33 @@ public function toApiGatewayFormatV2(): array
// serialized to `[]` (we want `{}`) so we force it to an empty object.
$headers = empty($headers) ? new \stdClass : $headers;

return [
'cookies' => $cookies,
'isBase64Encoded' => $base64Encoding,
'statusCode' => $this->statusCode,
'headers' => $headers,
'body' => $base64Encoding ? base64_encode($this->body) : $this->body,
];
if ($isStreamedMode) {
return $this->yieldBody([
'cookies' => $cookies,
'statusCode' => $this->statusCode,
'headers' => $headers,
]);
} else {
if ($this->body instanceof \Generator) {
$dataChunk = '';

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

$this->body->next();
}
} else {
$dataChunk = $this->body;
}

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

/**
Expand All @@ -98,4 +142,29 @@ private function capitalizeHeaderName(string $name): string
$name = ucwords($name);
return str_replace(' ', '-', $name);
}

/**
* Yields headers and response body in Lambda Streaming Format.
*
* @param array $headersFormat
*/
private function yieldBody(array $headersFormat): \Generator
{
/*
* Lambda Streaming response requires you to send the headers in API Gateway format
* followed by a set of 8 NULL bits, and only then you can start sending the actual
* response body.
*/
yield json_encode($headersFormat);

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;
}
}
}
Loading