Skip to content

Commit 15be013

Browse files
authored
Convert to proper Symfony requests (#31)
* Adding better convert to Symfony request * Update readme * fix laravel
1 parent ba7176e commit 15be013

File tree

7 files changed

+300
-69
lines changed

7 files changed

+300
-69
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Define the environment variable `APP_RUNTIME` for your application on Lambda.
2929

3030
## How to use
3131

32-
You need the extra lambda layer `arn:aws:lambda:eu-central-1:403367587399:layer:bref-symfony-runtime:3`
32+
You need the extra lambda layer `arn:aws:lambda:[region]:403367587399:layer:bref-sf-runtime:1`
3333
in serverless.yml.
3434

3535
```yaml
@@ -43,7 +43,7 @@ functions:
4343
timeout: 8
4444
layers:
4545
- ${bref:layer.php-74}
46-
- arn:aws:lambda:eu-central-1:403367587399:layer:bref-symfony-runtime:3
46+
- arn:aws:lambda:eu-central-1:403367587399:layer:bref-sf-runtime:1
4747
events:
4848
- httpApi: '*'
4949
```

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"bref/bref": "^1.2",
1414
"clue/arguments": "^2.1",
1515
"psr/http-server-handler": "^1.0",
16+
"riverline/multipart-parser": "^2.0",
1617
"symfony/runtime": "5.x-dev"
1718
},
1819
"require-dev": {

src/LaravelHttpHandler.php

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,42 +25,11 @@ public function __construct(Kernel $kernel)
2525

2626
public function handleRequest(HttpRequestEvent $event, Context $context): HttpResponse
2727
{
28-
Request::setTrustedProxies(['127.0.0.1'], Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO);
29-
30-
// CGI Version 1.1 - Section 4.1
31-
$server = array_filter([
32-
'AUTH_TYPE' => $event->getHeaders()['auth-type'] ?? null, // 4.1.1
33-
'CONTENT_LENGTH' => $event->getHeaders()['content-length'] ?? null, // 4.1.2
34-
'CONTENT_TYPE' => $event->getContentType(), // 4.1.3
35-
'QUERY_STRING' => $event->getQueryString(), // 4.1.7
36-
'REQUEST_METHOD' => $event->getMethod(), // 4.1.12
37-
'SERVER_PORT' => $event->getServerPort(), // 4.1.16
38-
'SERVER_PROTOCOL' => $event->getProtocolVersion(), // 4.1.16
39-
'DOCUMENT_ROOT' => getcwd(),
40-
'REQUEST_TIME' => time(),
41-
'REQUEST_TIME_FLOAT' => microtime(true),
42-
'REQUEST_URI' => $event->getUri(),
43-
], fn ($value) => null !== $value);
44-
45-
foreach ($event->getHeaders() as $name => $values) {
46-
$server['HTTP_'.strtoupper($name)] = $values[0];
47-
}
48-
49-
// TODO convert request better
50-
$request = Request::create(
51-
$event->getUri(),
52-
$event->getMethod(),
53-
[],
54-
[],
55-
[],
56-
$server,
57-
$event->getBody()
58-
);
59-
28+
$request = Request::createFromBase(SymfonyRequestBridge::convertRequest($event, $context));
6029
$response = $this->kernel->handle($request);
6130
$this->kernel->terminate($request, $response);
6231
$response->prepare($request);
6332

64-
return new HttpResponse($response->getContent(), $response->headers->all(), $response->getStatusCode());
33+
return SymfonyRequestBridge::convertResponse($response);
6534
}
6635
}

src/SymfonyHttpHandler.php

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use Bref\Event\Http\HttpHandler;
77
use Bref\Event\Http\HttpRequestEvent;
88
use Bref\Event\Http\HttpResponse;
9-
use Symfony\Component\HttpFoundation\Request;
109
use Symfony\Component\HttpKernel\HttpKernelInterface;
1110
use Symfony\Component\HttpKernel\TerminableInterface;
1211

@@ -26,44 +25,13 @@ public function __construct(HttpKernelInterface $kernel)
2625

2726
public function handleRequest(HttpRequestEvent $event, Context $context): HttpResponse
2827
{
29-
Request::setTrustedProxies(['127.0.0.1'], Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO);
30-
31-
// CGI Version 1.1 - Section 4.1
32-
$server = array_filter([
33-
'AUTH_TYPE' => $event->getHeaders()['auth-type'] ?? null, // 4.1.1
34-
'CONTENT_LENGTH' => $event->getHeaders()['content-length'] ?? null, // 4.1.2
35-
'CONTENT_TYPE' => $event->getContentType(), // 4.1.3
36-
'QUERY_STRING' => $event->getQueryString(), // 4.1.7
37-
'REQUEST_METHOD' => $event->getMethod(), // 4.1.12
38-
'SERVER_PORT' => $event->getServerPort(), // 4.1.16
39-
'SERVER_PROTOCOL' => $event->getProtocolVersion(), // 4.1.16
40-
'DOCUMENT_ROOT' => getcwd(),
41-
'REQUEST_TIME' => time(),
42-
'REQUEST_TIME_FLOAT' => microtime(true),
43-
'REQUEST_URI' => $event->getUri(),
44-
], fn ($value) => null !== $value);
45-
46-
foreach ($event->getHeaders() as $name => $values) {
47-
$server['HTTP_'.strtoupper($name)] = $values[0];
48-
}
49-
50-
// TODO convert request better
51-
$request = Request::create(
52-
$event->getUri(),
53-
$event->getMethod(),
54-
[],
55-
[],
56-
[],
57-
$server,
58-
$event->getBody()
59-
);
60-
28+
$request = SymfonyRequestBridge::convertRequest($event, $context);
6129
$response = $this->kernel->handle($request);
6230

6331
if ($this->kernel instanceof TerminableInterface) {
6432
$this->kernel->terminate($request, $response);
6533
}
6634

67-
return new HttpResponse($response->getContent(), $response->headers->all(), $response->getStatusCode());
35+
return SymfonyRequestBridge::convertResponse($response);
6836
}
6937
}

src/SymfonyRequestBridge.php

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
namespace Runtime\Bref;
4+
5+
use Bref\Context\Context;
6+
use Bref\Event\Http\HttpRequestEvent;
7+
use Bref\Event\Http\HttpResponse;
8+
use Riverline\MultiPartParser\StreamedPart;
9+
use Symfony\Component\HttpFoundation\File\UploadedFile;
10+
use Symfony\Component\HttpFoundation\Request;
11+
use Symfony\Component\HttpFoundation\Response;
12+
13+
/**
14+
* Bridges Symfony requests and responses with API Gateway or ALB event/response
15+
* formats.
16+
*
17+
* @author Tobias Nyholm <[email protected]>
18+
*
19+
* @internal
20+
*/
21+
class SymfonyRequestBridge
22+
{
23+
public static function convertRequest(HttpRequestEvent $event, Context $context): Request
24+
{
25+
Request::setTrustedProxies(['127.0.0.1'], Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO);
26+
27+
// CGI Version 1.1 - Section 4.1
28+
$server = array_filter([
29+
'AUTH_TYPE' => $event->getHeaders()['auth-type'] ?? null, // 4.1.1
30+
'CONTENT_LENGTH' => $event->getHeaders()['content-length'][0] ?? null, // 4.1.2
31+
'CONTENT_TYPE' => $event->getContentType(), // 4.1.3
32+
'QUERY_STRING' => $event->getQueryString(), // 4.1.7
33+
'REQUEST_METHOD' => $event->getMethod(), // 4.1.12
34+
'SERVER_PORT' => $event->getServerPort(), // 4.1.16
35+
'SERVER_PROTOCOL' => 'HTTP/'.$event->getProtocolVersion(), // 4.1.16
36+
'DOCUMENT_ROOT' => getcwd(),
37+
'REQUEST_TIME' => time(),
38+
'REQUEST_TIME_FLOAT' => microtime(true),
39+
'REQUEST_URI' => $event->getUri(),
40+
'REMOTE_ADDR' => '127.0.0.1',
41+
], fn ($value) => null !== $value);
42+
43+
foreach ($event->getHeaders() as $name => $values) {
44+
$server['HTTP_'.strtoupper($name)] = $values[0];
45+
}
46+
47+
[$files, $parsedBody, $bodyString] = self::parseBodyAndUploadedFiles($event);
48+
49+
return new Request(
50+
$event->getQueryParameters(),
51+
$parsedBody ?? [],
52+
[], // Attributes
53+
$event->getCookies(),
54+
$files,
55+
$server,
56+
$bodyString
57+
);
58+
}
59+
60+
public static function convertResponse(Response $response): HttpResponse
61+
{
62+
return new HttpResponse($response->getContent(), $response->headers->all(), $response->getStatusCode());
63+
}
64+
65+
private static function parseBodyAndUploadedFiles(HttpRequestEvent $event): array
66+
{
67+
$bodyString = $event->getBody();
68+
$files = [];
69+
$parsedBody = null;
70+
$contentType = $event->getContentType();
71+
if (null !== $contentType && 'POST' === $event->getMethod()) {
72+
if ('application/x-www-form-urlencoded' === $contentType) {
73+
parse_str($bodyString, $parsedBody);
74+
} else {
75+
$stream = fopen('php://temp', 'rw');
76+
fwrite($stream, "Content-type: $contentType\r\n\r\n".$bodyString);
77+
rewind($stream);
78+
$document = new StreamedPart($stream);
79+
if ($document->isMultiPart()) {
80+
$bodyString = '';
81+
$parsedBody = [];
82+
foreach ($document->getParts() as $part) {
83+
if ($part->isFile()) {
84+
$tmpPath = tempnam(sys_get_temp_dir(), 'bref_upload_');
85+
if (false === $tmpPath) {
86+
throw new \RuntimeException('Unable to create a temporary directory');
87+
}
88+
file_put_contents($tmpPath, $part->getBody());
89+
if (0 !== filesize($tmpPath) && '' !== $part->getFileName()) {
90+
$file = new UploadedFile($tmpPath, $part->getFileName(), $part->getMimeType(), UPLOAD_ERR_OK, true);
91+
} else {
92+
$file = null;
93+
}
94+
95+
self::parseKeyAndInsertValueInArray($files, $part->getName(), $file);
96+
} else {
97+
self::parseKeyAndInsertValueInArray($parsedBody, $part->getName(), $part->getBody());
98+
}
99+
}
100+
}
101+
}
102+
}
103+
104+
return [$files, $parsedBody, $bodyString];
105+
}
106+
107+
/**
108+
* Parse a string key like "files[id_cards][jpg][]" and do $array['files']['id_cards']['jpg'][] = $value.
109+
*
110+
* @param mixed $value
111+
*/
112+
private static function parseKeyAndInsertValueInArray(array &$array, string $key, $value): void
113+
{
114+
if (false === strpos($key, '[')) {
115+
$array[$key] = $value;
116+
117+
return;
118+
}
119+
120+
$parts = explode('[', $key); // files[id_cards][jpg][] => [ 'files', 'id_cards]', 'jpg]', ']' ]
121+
$pointer = &$array;
122+
123+
foreach ($parts as $k => $part) {
124+
if (0 === $k) {
125+
$pointer = &$pointer[$part];
126+
127+
continue;
128+
}
129+
130+
// Skip two special cases:
131+
// [[ in the key produces empty string
132+
// [test : starts with [ but does not end with ]
133+
if ('' === $part || ']' !== substr($part, -1)) {
134+
// Malformed key, we use it "as is"
135+
$array[$key] = $value;
136+
137+
return;
138+
}
139+
140+
$part = substr($part, 0, -1); // The last char is a ] => remove it to have the real key
141+
142+
if ('' === $part) { // [] case
143+
$pointer = &$pointer[];
144+
} else {
145+
$pointer = &$pointer[$part];
146+
}
147+
}
148+
149+
$pointer = $value;
150+
}
151+
}

tests/.gitignore

Whitespace-only changes.

0 commit comments

Comments
 (0)