Skip to content

Commit a501229

Browse files
committed
Support for API Gateway
closes #226 closes #221 closes #69
1 parent 3710aa2 commit a501229

File tree

5 files changed

+94
-20
lines changed

5 files changed

+94
-20
lines changed

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -160,22 +160,23 @@ lambda-local -l index.js -h handler -e examples/s3-put.js
160160
lambda-local -l index.js -h handler -e examples/s3-put.js -E '{"key":"value","key2":"value2"}'
161161
```
162162

163-
#### Running lambda functions as a HTTP Server
164-
A simple way you can run lambda functions locally, without the need to create any special template files (like Serverless plugin and SAM requires), just adding the parameter `--watch`. It will raise a http server listening to the specified port (default is 8008), then you can pass the event payload to the handler via request body.
163+
#### Running lambda functions as a HTTP Server (Amazon API Gateway payload format version 2.0.)
164+
165+
A simple way you can run lambda functions locally, without the need to create any special template files (like Serverless plugin and SAM requires), just adding the parameter `--watch`. It will raise a http server listening to the specified port (default is 8008). You can then call the lambda as mentionned here:
166+
https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html
165167

166168
```bash
167-
lambda-local -l examples/handler_helloworld.js -h handler --watch 8008
169+
lambda-local -l examples/handler_gateway2.js -h handler --watch 8008
168170

169171
curl --request POST \
170172
--url http://localhost:8008/ \
171173
--header 'content-type: application/json' \
172174
--data '{
173-
"event": {
174-
"key1": "value1",
175-
"key2": "value2",
176-
"key3": "value3"
177-
}
175+
"key1": "value1",
176+
"key2": "value2",
177+
"key3": "value3"
178178
}'
179+
{"message":"This is a response"}
179180
```
180181

181182
## About: Definitions
@@ -187,7 +188,7 @@ Event data are just JSON objects exported:
187188
```js
188189
// Sample event data
189190
module.exports = {
190-
foo: "bar"
191+
foo: "bar"
191192
};
192193
```
193194

examples/handler_gateway2.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Example Lambda function.
3+
*/
4+
5+
exports.handler = async function(event, context) {
6+
return { message: "Hello ! Here's a full copy of the event:", event };
7+
};

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lambda-local",
3-
"version": "2.0.3",
3+
"version": "2.0.0",
44
"description": "Commandline tool to run Lambda functions on your local machine.",
55
"main": "build/lambdalocal.js",
66
"types": "build/lambdalocal.d.ts",
@@ -43,7 +43,7 @@
4343
"devDependencies": {
4444
"@types/node": "^18.7.16",
4545
"chai": "^4.3.6",
46-
"mocha": "^10.0.0",
46+
"mocha": "^10.2.0",
4747
"sinon": "^14.0.0",
4848
"typescript": "^4.8.3"
4949
},

src/lambdalocal.ts

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import fs = require('fs');
1111
import path = require('path');
1212
import os = require('os');
1313
import { createServer, IncomingMessage, ServerResponse } from 'http';
14+
1415
import utils = require('./lib/utils.js');
1516
import Context = require('./lib/context.js');
1617
require("./lib/streaming.js");
@@ -64,12 +65,11 @@ export function watch(opts) {
6465
return res.end(JSON.stringify({ error }));
6566
}
6667
try {
67-
if(req.headers['content-type'] !== 'application/json') throw 'Invalid header Content-Type (Expected application/json)';
6868
_getRequestPayload(req, async (error, result) => {
6969
try {
7070
if(error) throw error;
71-
const data = await execute({ ...opts, event: () => result });
72-
const ans = JSON.stringify({ data });
71+
const data = await execute({ ...opts, event: result });
72+
const ans = _formatResponsePayload(res, data);
7373
logger.log('info', log_msg + ` -> OK (${ans.length * 2} bytes)`);
7474
return res.end(ans);
7575
} catch(error) {
@@ -86,26 +86,75 @@ export function watch(opts) {
8686
}
8787

8888
function _getRequestPayload(req, callback) {
89+
/*
90+
* Handle HTTP server functions.
91+
*/
8992
let body = '';
9093
req.on('data', chunk => {
9194
body += chunk.toString();
9295
});
9396
req.on('end', () => {
9497
let payload;
9598
try {
96-
payload = JSON.parse(body);
99+
payload = JSON.parse(body || '{}');
97100
} catch(err) {
98101
callback(err);
99102
return;
100103
}
101-
if(!payload.event) {
102-
callback('Invalid body (Expected "event" property)');
103-
return;
104-
}
105-
callback(null, payload.event);
104+
// Format: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.proxy-format
105+
const url = new URL(req.url, `http://${req.headers.host}`);
106+
const event = {
107+
version: "2.0",
108+
routeKey: "$default",
109+
rawPath: url.pathname,
110+
rawQueryString: url.search,
111+
cookies: utils.parseCookies(req),
112+
headers: req.headers,
113+
queryStringParameters: Object.fromEntries(url.searchParams),
114+
requestContext: {
115+
accountId: "123456789012",
116+
apiId: "api-id",
117+
authentication: {},
118+
authorizer: {},
119+
http: {
120+
method: req.method,
121+
path: url.pathname,
122+
protocol: "HTTP/" + req.httpVersion,
123+
sourceIp: req.socket.localAddress,
124+
userAgent: req.headers['user-agent'],
125+
},
126+
requestId: "id",
127+
routeKey: "$default",
128+
stage: "$default",
129+
time: new Date().toISOString(),
130+
timeEpoch: new Date().getTime(),
131+
},
132+
body: payload,
133+
isBase64Encoded: req.headers['content-type'] !== 'application/json',
134+
};
135+
callback(null, event);
106136
});
107137
}
108138

139+
function _formatResponsePayload(res, data) {
140+
/*
141+
* Handle HTTP server function output.
142+
*/
143+
// https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.response
144+
if (!data.statusCode) {
145+
data = {
146+
isBase64Encoded: false,
147+
statusCode: 200,
148+
body: data,
149+
headers: {
150+
"content-type": "application/json",
151+
}
152+
}
153+
}
154+
res.writeHead(data.statusCode, data.headers);
155+
return JSON.stringify(data.body);
156+
}
157+
109158
function updateEnv(env) {
110159
/*
111160
* Update environment vars if not already in place

src/lib/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,23 @@ export function processJSON(json) {
9696
}
9797
};
9898

99+
export function parseCookies (request) {
100+
const list = {};
101+
const cookieHeader = request.headers?.cookie;
102+
if (!cookieHeader) return list;
103+
104+
cookieHeader.split(`;`).forEach(function(cookie) {
105+
let [ name, ...rest] = cookie.split(`=`);
106+
name = name?.trim();
107+
if (!name) return;
108+
const value = rest.join(`=`).trim();
109+
if (!value) return;
110+
list[name] = decodeURIComponent(value);
111+
});
112+
113+
return list;
114+
}
115+
99116
export class TimeoutError extends Error {
100117
constructor(m: string) {
101118
super(m);

0 commit comments

Comments
 (0)