Skip to content

Commit 8db7b63

Browse files
committed
Initial commit
0 parents  commit 8db7b63

File tree

5 files changed

+282
-0
lines changed

5 files changed

+282
-0
lines changed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# PHP Layer For AWS Lambda
2+
3+
Ever want to run PHP websites in AWS Lambda? It's your lucky day!
4+
5+
**:warning: This is an experimental AWS Lambda Layer and Runtime. It should not be used for production services. Obligatory legal statement follows:**
6+
7+
> STACKERY INC. AND/OR ITS RESPECTIVE SUPPLIERS MAKE NO REPRESENTATIONS ABOUT THE SUITABILITY, RELIABILITY, AVAILABILITY, TIMELINESS, AND ACCURACY OF THE INFORMATION, SOFTWARE, PRODUCTS, SERVICES AND RELATED GRAPHICS CONTAINED ON THE MSN WEB SITES FOR ANY PURPOSE. ALL SUCH INFORMATION, SOFTWARE, PRODUCTS, SERVICES AND RELATED GRAPHICS ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT AND/OR ITS RESPECTIVE SUPPLIERS HEREBY DISCLAIM ALL WARRANTIES AND CONDITIONS WITH REGARD TO THIS INFORMATION, SOFTWARE, PRODUCTS, SERVICES AND RELATED GRAPHICS, INCLUDING ALL IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL MICROSOFT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, PUNITIVE, INCIDENTAL, SPECIAL, CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF USE, DATA OR PROFITS, ARISING OUT OF OR IN ANY WAY CONNECTED WITH THE USE OR PERFORMANCE OF THE MSN WEB SITES, WITH THE DELAY OR INABILITY TO USE THE MSN WEB SITES OR RELATED SERVICES, THE PROVISION OF OR FAILURE TO PROVIDE SERVICES, OR FOR ANY INFORMATION, SOFTWARE, PRODUCTS, SERVICES AND RELATED GRAPHICS OBTAINED THROUGH THE MSN WEB SITES, OR OTHERWISE ARISING OUT OF THE USE OF THE MSN WEB SITES, WHETHER BASED ON CONTRACT, TORT, NEGLIGENCE, STRICT LIABILITY OR OTHERWISE, EVEN IF MICROSOFT OR ANY OF ITS SUPPLIERS HAS BEEN ADVISED OF THE POSSIBILITY OF DAMAGES. BECAUSE SOME STATES/JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU. IF YOU ARE DISSATISFIED WITH ANY PORTION OF THE MSN WEB SITES, OR WITH ANY OF THESE TERMS OF USE, YOUR SOLE AND EXCLUSIVE REMEDY IS TO DISCONTINUE USING THE MSN WEB SITES.
8+
9+
### Usage
10+
Simply configure your function as follows:
11+
12+
1. Set the `Runtime` to `provided`
13+
1. Determine the latest version of the layer: `aws lambda list-layer-versions --layer-name arn:aws:lambda:<your region>:887080169480:layer:php71`
14+
1. Add the following Lambda Layer: `arn:aws:lambda:<your region>:887080169480:layer:php71:<latest version>`
15+
16+
If you are using AWS SAM it's even easier! Update your function:
17+
18+
```yaml
19+
Resources:
20+
MyFunction:
21+
Type: AWS::Serverless::Function
22+
Properties:
23+
...
24+
Runtime: provided
25+
Layers:
26+
- !Sub arn:aws:lambda:${AWS::Region}:887080169480:layer:php71
27+
```
28+
29+
### Development
30+
31+
The layer is built by:
32+
33+
1. Installing a Docker environment
34+
1. Running `make` in the layers directory
35+
36+
This will launch a Docker container that will build php71.zip.

layer/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
2+
3+
all:
4+
rm -f php-layer.zip
5+
echo $(ROOT_DIR)
6+
docker run --rm -v $(ROOT_DIR):/opt/layer lambci/lambda:build-nodejs8.10 /opt/layer/build.sh

layer/bootstrap

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
#!/opt/bin/php -c/opt/php.ini
2+
<?php
3+
4+
ini_set('display_errors', 'On');
5+
error_reporting(E_ALL | E_STRICT);
6+
7+
$AWS_LAMBDA_RUNTIME_API = getenv('AWS_LAMBDA_RUNTIME_API');
8+
9+
function start_webserver() {
10+
$pid = pcntl_fork();
11+
switch($pid) {
12+
case -1:
13+
die('Failed to fork webserver process');
14+
15+
case 0:
16+
// exec the command
17+
chdir('/var/task');
18+
exec('php -S localhost:8000 -c /opt/php.ini');
19+
exit;
20+
21+
// return the child pid to parent
22+
default:
23+
return $pid;
24+
}
25+
}
26+
27+
function fail($AWS_LAMBDA_RUNTIME_API, $invocation_id, $message) {
28+
$ch = curl_init("http://$AWS_LAMBDA_RUNTIME_API/2018-06-01/runtime/invocation/$invocation_id/response");
29+
30+
$response = array();
31+
32+
$response['statusCode'] = 500;
33+
$response['body'] = $message;
34+
35+
$response_json = json_encode($response);
36+
37+
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
38+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
39+
curl_setopt($ch, CURLOPT_POSTFIELDS, $response_json);
40+
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
41+
'Content-Type: application/json',
42+
'Content-Length: ' . strlen($response_json)
43+
));
44+
45+
curl_exec($ch);
46+
curl_close($ch);
47+
}
48+
49+
start_webserver();
50+
51+
while (true) {
52+
$ch = curl_init("http://$AWS_LAMBDA_RUNTIME_API/2018-06-01/runtime/invocation/next");
53+
54+
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
55+
curl_setopt($ch, CURLOPT_FAILONERROR, TRUE);
56+
57+
$invocation_id = '';
58+
59+
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$invocation_id) {
60+
if (!preg_match('/:\s*/', $header)) {
61+
return strlen($header);
62+
}
63+
64+
[$name, $value] = preg_split('/:\s*/', $header, 2);
65+
66+
if (strtolower($name) == 'x-amz-aws-request-id') {
67+
$invocation_id = trim($value);
68+
}
69+
70+
return strlen($header);
71+
});
72+
73+
$body = '';
74+
75+
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$body) {
76+
$body .= $chunk;
77+
78+
return strlen($chunk);
79+
});
80+
81+
curl_exec($ch);
82+
83+
if (curl_error($ch)) {
84+
die('Failed to fetch next Lambda invocation: ' . curl_error($ch) . "\n");
85+
}
86+
87+
curl_close($ch);
88+
89+
if (!$body) {
90+
die("Empty Lambda invocation response\n");
91+
}
92+
93+
$event = json_decode($body, TRUE);
94+
95+
if (!array_key_exists('requestContext', $event)) {
96+
fail($AWS_LAMBDA_RUNTIME_API, $invocation_id, 'Event is not an API Gateway request');
97+
continue;
98+
}
99+
100+
$uri = $event['path'];
101+
102+
if (count($event['multiValueQueryStringParameters']) > 0) {
103+
$first = TRUE;
104+
foreach ($event['multiValueQueryStringParameters'] as $name => $values) {
105+
foreach ($values as $value) {
106+
if ($first) {
107+
$uri .= "?";
108+
$first = FALSE;
109+
} else {
110+
$uri .= "&";
111+
}
112+
113+
$uri .= $name . '=' . $value;
114+
}
115+
}
116+
}
117+
118+
$ch = curl_init("http://localhost:8000$uri");
119+
120+
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
121+
122+
if (array_key_exists('multiValueHeaders', $event)) {
123+
$headers = array();
124+
125+
foreach ($event['multiValueHeaders'] as $name => $values) {
126+
foreach ($values as $value) {
127+
array_push($headers, "${name}: ${value}");
128+
}
129+
}
130+
131+
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
132+
}
133+
134+
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $event['httpMethod']);
135+
136+
if (array_key_exists('body', $event)) {
137+
$body = $event['body'];
138+
if (array_key_exists('isBase64Encoded', $event) && $event['isBase64Encoded']) {
139+
$body = base64_decode($body);
140+
}
141+
} else {
142+
$body = '';
143+
}
144+
145+
if (strlen($body) > 0) {
146+
curl_setopt($ch, CURLOPT_INFILESIZE, strlen($body));
147+
curl_setopt($ch, CURLOPT_READFUNCTION, function ($ch, $fd, $length) use ($body) {
148+
return $body;
149+
});
150+
}
151+
152+
$response = array();
153+
$response['multiValueHeaders'] = array();
154+
$response['body'] = '';
155+
156+
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$response) {
157+
if (preg_match('/HTTP\/1.1 (\d+) .*/', $header, $matches)) {
158+
$response['statusCode'] = intval($matches[1]);
159+
return strlen($header);
160+
}
161+
162+
if (!preg_match('/:\s*/', $header)) {
163+
return strlen($header);
164+
}
165+
166+
[$name, $value] = preg_split('/:\s*/', $header, 2);
167+
168+
$name = trim($name);
169+
$value = trim($value);
170+
171+
if ($name == '') {
172+
return strlen($header);
173+
}
174+
175+
if (!array_key_exists($name, $response['multiValueHeaders'])) {
176+
$response['multiValueHeaders'][$name] = array();
177+
}
178+
179+
array_push($response['multiValueHeaders'][$name], $value);
180+
181+
return strlen($header);
182+
});
183+
184+
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$response) {
185+
$response['body'] .= $chunk;
186+
187+
return strlen($chunk);
188+
});
189+
190+
curl_exec($ch);
191+
curl_close($ch);
192+
193+
if (preg_match('/[^\x00-\x7f]/', $response['body'])) {
194+
$response['body'] = base64_encode($response_body);
195+
$response['isBase64Encoded'] = TRUE;
196+
}
197+
198+
$ch = curl_init("http://$AWS_LAMBDA_RUNTIME_API/2018-06-01/runtime/invocation/$invocation_id/response");
199+
200+
$response_json = json_encode($response);
201+
202+
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
203+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
204+
curl_setopt($ch, CURLOPT_POSTFIELDS, $response_json);
205+
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
206+
'Content-Type: application/json',
207+
'Content-Length: ' . strlen($response_json)
208+
));
209+
210+
curl_exec($ch);
211+
curl_close($ch);
212+
}
213+
214+
?>

layer/build.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
3+
yum install -y php71-cli zip
4+
5+
mkdir /tmp/layer
6+
cd /tmp/layer
7+
cp /opt/layer/bootstrap .
8+
cp /opt/layer/php.ini .
9+
10+
mkdir bin
11+
cp /usr/bin/php bin/
12+
13+
mkdir lib
14+
for lib in libncurses.so.5 libtinfo.so.5 libpcre.so.0; do
15+
cp "/lib64/${lib}" lib/
16+
done
17+
18+
cp /usr/lib64/libedit.so.0 lib/
19+
20+
cp -a /usr/lib64/php lib/
21+
22+
zip -r /opt/layer/php-layer.zip .

layer/php.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
extension_dir=/opt/lib/php/7.1/modules
2+
extension=curl.so
3+
extension=json.so
4+
display_errors=On

0 commit comments

Comments
 (0)