Skip to content

Commit ba14a57

Browse files
committed
Support for HTTP API request v2.0
1 parent 1cbb4ed commit ba14a57

File tree

10 files changed

+286
-41
lines changed

10 files changed

+286
-41
lines changed

src/eventToRequestOptions.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,15 @@ const eventToRequestOptions = (event: APIGatewayEvent, ctx?: LambdaContext): InP
5252
remoteAddress = items[items.length - 1];
5353
}
5454
}
55+
let method: string | undefined= event.httpMethod;
56+
let path: string | undefined = event.path;
57+
if (event.requestContext && typeof event.requestContext.http === 'object') {
58+
method = event.requestContext.http.method
59+
path = event.requestContext.http.path
60+
}
5561
return {
56-
method: event.httpMethod,
57-
path: url.format({ pathname: event.path, query: queryStringParams }),
62+
method,
63+
path: url.format({ pathname: path, query: queryStringParams }),
5864
headers: headers,
5965
body: Buffer.from(event.body || '', event.isBase64Encoded ? 'base64' : 'utf8'),
6066
ssl,

src/fixResponseHeaders.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@ import { OutgoingHttpHeaders } from 'http';
22
import { LambdaResponseHeaders, StringMap } from './types'
33
import variations from './setCookieVariations';
44

5-
const fixResponseHeaders = (headers: OutgoingHttpHeaders, supportMultiHeaders: boolean): LambdaResponseHeaders => {
5+
const fixResponseHeaders = (headers: OutgoingHttpHeaders, supportMultiHeaders: boolean, supportCookies: boolean): LambdaResponseHeaders => {
66
const multiValueHeaders: StringMap<string[]> = {};
77
const singleValueHeaders: StringMap<string> = {};
8+
let cookies: string[] | undefined = undefined
89
Object.keys(headers).forEach(k => {
910
if (Array.isArray(headers[k])) {
1011
const values = headers[k] as string[];
1112
multiValueHeaders[k] = values;
1213
if (k === 'set-cookie') {
13-
values.forEach((value, i) => {
14-
singleValueHeaders[variations[i]] = value
15-
});
14+
if (supportCookies) {
15+
cookies = values
16+
} else {
17+
values.forEach((value, i) => {
18+
singleValueHeaders[variations[i]] = value
19+
});
20+
}
1621
} else {
1722
singleValueHeaders[k] = values.join(',');
1823
}
@@ -36,7 +41,7 @@ const fixResponseHeaders = (headers: OutgoingHttpHeaders, supportMultiHeaders: b
3641
if (supportMultiHeaders) {
3742
return { multiValueHeaders };
3843
} else {
39-
return { headers: singleValueHeaders }
44+
return { headers: singleValueHeaders, cookies }
4045
}
4146
};
4247

src/lambda.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ const eventHasMultiValueHeaders = (event: handler.APIGatewayEvent): boolean => {
2121
return event.multiValueHeaders !== null && typeof event.multiValueHeaders === 'object';
2222
}
2323

24+
const eventSupportsCookies = (event: handler.APIGatewayEvent): boolean => {
25+
return event.version === "2.0" && !eventHasMultiValueHeaders(event)
26+
}
27+
2428
const handlerBuilder = (appFn: PromiseFactory<RequestListener>): handler.APIGatewayEventHandler => {
2529
let appHandler: Nullable<F<MockRequestOptions, Promise<MockResponse>>>;
2630
return async (event, ctx) => {
@@ -31,7 +35,7 @@ const handlerBuilder = (appFn: PromiseFactory<RequestListener>): handler.APIGate
3135
try {
3236
const reqOptions = eventToRequestOptions(event, ctx);
3337
const mockResponse = await appHandler(reqOptions);
34-
return inProcessResponseToLambdaResponse(mockResponse, eventHasMultiValueHeaders(event));
38+
return inProcessResponseToLambdaResponse(mockResponse, eventHasMultiValueHeaders(event), eventSupportsCookies(event));
3539
} catch (e) {
3640
console.error(e);
3741
return errorResponse();

src/response.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { InProcessResponse } from 'in-process-request';
22
import { LambdaResponse } from './types';
33
import fixResponseHeaders from './fixResponseHeaders'
44

5-
export const inProcessResponseToLambdaResponse = (response: InProcessResponse, supportMultiHeaders: boolean): LambdaResponse => {
5+
export const inProcessResponseToLambdaResponse = (response: InProcessResponse, supportMultiHeaders: boolean, supportCookies: boolean): LambdaResponse => {
66
const encoding = response.isUTF8 ? 'utf8' : 'base64';
77
return {
88
statusCode: response.statusCode,
99
body: response.body.toString(encoding),
1010
isBase64Encoded: encoding === 'base64',
11-
...fixResponseHeaders(response.headers, supportMultiHeaders),
11+
...fixResponseHeaders(response.headers, supportMultiHeaders, supportCookies),
1212
};
1313
};
1414

src/types.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,42 @@ export interface StringMap<T> {
22
[k: string]: T
33
}
44

5+
interface HttpRequestContext {
6+
method: string
7+
path: string
8+
protocol: string
9+
sourceIp: string
10+
userAgent: string
11+
}
12+
513
export interface APIGatewayEvent {
6-
path: string,
7-
queryStringParameters?: StringMap<string> | null | undefined,
8-
multiValueQueryStringParameters?: StringMap<string[]> | null | undefined,
9-
body: string | null | undefined,
10-
headers?: StringMap<string> | null | undefined,
11-
multiValueHeaders?: StringMap<string[]> | null | undefined,
14+
path?: string,
15+
version?: number | string,
16+
queryStringParameters?: StringMap<string> | null,
17+
multiValueQueryStringParameters?: StringMap<string[]> | null,
18+
body?: string | null,
19+
headers?: StringMap<string> | null,
20+
multiValueHeaders?: StringMap<string[]> | null,
1221
isBase64Encoded: boolean,
13-
httpMethod: string,
22+
httpMethod?: string,
1423
requestContext?: {
1524
elb?: {
1625
targetGroupArn: string,
1726
},
27+
http?: HttpRequestContext,
1828
stage?: string,
1929
identity?: {
2030
sourceIp: string,
2131
},
2232

23-
[k: string]: any
33+
// [k: string]: any
2434
}
2535
}
2636

2737
export interface LambdaResponseHeaders {
28-
headers?: StringMap<string> | undefined
29-
multiValueHeaders?: StringMap<string[]> | undefined
38+
headers?: StringMap<string>
39+
multiValueHeaders?: StringMap<string[]>
40+
cookies?: string[]
3041
}
3142

3243
export interface LambdaResponse extends LambdaResponseHeaders {

test/app/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ app.get('/cookies', (_, res) => {
2323
res.cookie('chocolate', '10');
2424
res.cookie('peanut_butter', '20');
2525
res.cookie('cinnamon', '30');
26-
res.end();
26+
res.send("cookies set");
2727
});
2828

2929
app.get('/user/:id', (req, res) => {

test/fixtures/event-http-api-1.0.json

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
{
2+
"version": "1.0",
3+
"resource": "$default",
4+
"path": "/cookies",
5+
"httpMethod": "GET",
6+
"headers": {
7+
"Content-Length": "0",
8+
"Host": "myapiid.execute-api.ap-southeast-2.amazonaws.com",
9+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36",
10+
"X-Amzn-Trace-Id": "Root=1-5eac36bd-7a4863e4e05a7ca67c0a68ae",
11+
"X-Forwarded-For": "14.201.132.129",
12+
"X-Forwarded-Port": "443",
13+
"X-Forwarded-Proto": "https",
14+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
15+
"accept-encoding": "gzip, deflate, br",
16+
"accept-language": "en-US,en;q=0.9,pl-PL;q=0.8,pl;q=0.7,ru;q=0.6",
17+
"sec-fetch-dest": "document",
18+
"sec-fetch-mode": "navigate",
19+
"sec-fetch-site": "none",
20+
"sec-fetch-user": "?1",
21+
"upgrade-insecure-requests": "1"
22+
},
23+
"multiValueHeaders": {
24+
"Content-Length": [
25+
"0"
26+
],
27+
"Host": [
28+
"myapiid.execute-api.ap-southeast-2.amazonaws.com"
29+
],
30+
"User-Agent": [
31+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36"
32+
],
33+
"X-Amzn-Trace-Id": [
34+
"Root=1-5eac36bd-7a4863e4e05a7ca67c0a68ae"
35+
],
36+
"X-Forwarded-For": [
37+
"14.201.132.129"
38+
],
39+
"X-Forwarded-Port": [
40+
"443"
41+
],
42+
"X-Forwarded-Proto": [
43+
"https"
44+
],
45+
"accept": [
46+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
47+
],
48+
"accept-encoding": [
49+
"gzip, deflate, br"
50+
],
51+
"accept-language": [
52+
"en-US,en;q=0.9,pl-PL;q=0.8,pl;q=0.7,ru;q=0.6"
53+
],
54+
"sec-fetch-dest": [
55+
"document"
56+
],
57+
"sec-fetch-mode": [
58+
"navigate"
59+
],
60+
"sec-fetch-site": [
61+
"none"
62+
],
63+
"sec-fetch-user": [
64+
"?1"
65+
],
66+
"upgrade-insecure-requests": [
67+
"1"
68+
]
69+
},
70+
"queryStringParameters": null,
71+
"multiValueQueryStringParameters": null,
72+
"requestContext": {
73+
"accountId": "356111732087",
74+
"apiId": "myapiid",
75+
"domainName": "myapiid.execute-api.ap-southeast-2.amazonaws.com",
76+
"domainPrefix": "myapiid",
77+
"extendedRequestId": "L219niDmywMEJPQ=",
78+
"httpMethod": "GET",
79+
"identity": {
80+
"accessKey": null,
81+
"accountId": null,
82+
"caller": null,
83+
"cognitoAuthenticationProvider": null,
84+
"cognitoAuthenticationType": null,
85+
"cognitoIdentityId": null,
86+
"cognitoIdentityPoolId": null,
87+
"principalOrgId": null,
88+
"sourceIp": "14.201.132.129",
89+
"user": null,
90+
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36",
91+
"userArn": null
92+
},
93+
"path": "/cookies",
94+
"protocol": "HTTP/1.1",
95+
"requestId": "L219niDmywMEJPQ=",
96+
"requestTime": "01/May/2020:14:48:29 +0000",
97+
"requestTimeEpoch": 1588344509401,
98+
"resourceId": "$default",
99+
"resourcePath": "$default",
100+
"stage": "$default"
101+
},
102+
"pathParameters": null,
103+
"stageVariables": null,
104+
"body": null,
105+
"isBase64Encoded": false
106+
}

test/fixtures/event-http-api-2.0.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"version": "2.0",
3+
"routeKey": "$default",
4+
"rawPath": "/inspects",
5+
"rawQueryString": "",
6+
"headers": {
7+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
8+
"accept-encoding": "gzip, deflate, br",
9+
"accept-language": "en-US,en;q=0.9,pl-PL;q=0.8,pl;q=0.7,ru;q=0.6",
10+
"cache-control": "max-age=0",
11+
"content-length": "0",
12+
"host": "myapiid.execute-api.ap-southeast-2.amazonaws.com",
13+
"sec-fetch-dest": "document",
14+
"sec-fetch-mode": "navigate",
15+
"sec-fetch-site": "none",
16+
"sec-fetch-user": "?1",
17+
"upgrade-insecure-requests": "1",
18+
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36",
19+
"x-amzn-trace-id": "Root=1-5eac37e3-8e447320ce745898566b3300",
20+
"x-forwarded-for": "123.123.123.123",
21+
"x-forwarded-port": "443",
22+
"x-forwarded-proto": "https"
23+
},
24+
"requestContext": {
25+
"accountId": "356111732087",
26+
"apiId": "myapiid",
27+
"domainName": "myapiid.execute-api.ap-southeast-2.amazonaws.com",
28+
"domainPrefix": "myapiid",
29+
"http": {
30+
"method": "GET",
31+
"path": "/cookies",
32+
"protocol": "HTTP/1.1",
33+
"sourceIp": "123.123.123.123",
34+
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36"
35+
},
36+
"requestId": "L22rohfjSwMEMHA=",
37+
"routeKey": "$default",
38+
"stage": "$default",
39+
"time": "01/May/2020:14:53:23 +0000",
40+
"timeEpoch": 1588344803984
41+
},
42+
"isBase64Encoded": false
43+
}

test/integration.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import lambda from '../src/lambda';
44

55
import event from './fixtures/event.json';
66
import eventHttpApi from './fixtures/event-http-api.json';
7+
import eventHttpApiV1 from './fixtures/event-http-api-1.0.json';
8+
import eventHttpApiV2 from './fixtures/event-http-api-2.0.json';
79
import eventAlb from './fixtures/alb-event.json';
810

911
const handler = lambda(app);
@@ -134,7 +136,7 @@ describe('integration', () => {
134136
})
135137
})
136138

137-
it('handles HTTP API event', async () => {
139+
it('handles HTTP API legacy event', async () => {
138140
const response = await handler(eventHttpApi)
139141
expect(response.statusCode).toEqual(200);
140142
expect(response.isBase64Encoded).toEqual(false);
@@ -175,6 +177,40 @@ describe('integration', () => {
175177
})
176178
})
177179

180+
it('handles HTTP API v1.0 event', async () => {
181+
const response = await handler(eventHttpApiV1)
182+
expect(response.statusCode).toEqual(200);
183+
expect(response.isBase64Encoded).toEqual(false);
184+
expect(response.multiValueHeaders!["content-type"][0]).toEqual('text/html; charset=utf-8');
185+
expect(response.multiValueHeaders!["x-powered-by"][0]).toEqual('Express');
186+
expect(response.multiValueHeaders!["set-cookie"]).toEqual([
187+
'chocolate=10; Path=/',
188+
'peanut_butter=20; Path=/',
189+
'cinnamon=30; Path=/',
190+
])
191+
expect(response.headers).toBeUndefined()
192+
expect(response.cookies).toBeUndefined()
193+
expect(response.body).toEqual('cookies set');
194+
})
195+
196+
it('handles HTTP API v2.0 event', async () => {
197+
const response = await handler(eventHttpApiV2)
198+
expect(response.statusCode).toEqual(200);
199+
expect(response.isBase64Encoded).toEqual(false);
200+
expect(response.headers!["content-type"]).toEqual('text/html; charset=utf-8');
201+
expect(response.headers!["x-powered-by"]).toEqual('Express');
202+
expect(response.headers!["set-cookie"]).toBeUndefined()
203+
expect(response.multiValueHeaders).toBeUndefined()
204+
expect(response.cookies).toEqual([
205+
'chocolate=10; Path=/',
206+
'peanut_butter=20; Path=/',
207+
'cinnamon=30; Path=/',
208+
])
209+
expect(response.body).toEqual('cookies set');
210+
211+
})
212+
213+
178214
it('handles ALB event', async () => {
179215
const response = await handler(eventAlb)
180216
expect(response.statusCode).toEqual(200);

0 commit comments

Comments
 (0)