Skip to content

Commit 02c56c9

Browse files
committed
feat: Handling multi value query params and headers
1 parent ecc9291 commit 02c56c9

File tree

6 files changed

+189
-13
lines changed

6 files changed

+189
-13
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ Properties:
8989
| [protocol](https://expressjs.com/en/4x/api.html#req.protocol) | - |
9090
| [secure](https://expressjs.com/en/4x/api.html#req.secure) | - |
9191
| [method](https://expressjs.com/en/4x/api.html#req.method) | - |
92-
| [query](https://expressjs.com/en/4x/api.html#req.query) | Doesn't include repeated query parameters. |
92+
| [query](https://expressjs.com/en/4x/api.html#req.query) | - |
9393
| [params](https://expressjs.com/en/4x/api.html#req.params) | - |
94-
| [headers](https://expressjs.com/en/4x/api.html#req.headers) | Doesn't include repeated headers values |
94+
| [headers](https://expressjs.com/en/4x/api.html#req.headers) | - |
9595

9696
Methods:
9797

src/lambda-wrapper.spec.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ describe('Lambda Wrapper', () => {
6767
'Content-Type': 'application/json',
6868
'Content-Length': requestObject.length
6969
},
70-
multiValueHeaders: {},
70+
multiValueHeaders: {
71+
'Content-Type': [ 'application/json' ],
72+
'Content-Length': [ requestObject.length ]
73+
},
7174
httpMethod: 'POST',
7275
isBase64Encoded: false,
7376
path: '/path',

src/request.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@ class Request extends ReadableStream {
99
constructor(event) {
1010
super();
1111

12-
this.headers = Object.keys(event.headers).reduce((headers, key) => {
13-
headers[key.toLowerCase()] = event.headers[key];
12+
this.headers = Object.keys(event.multiValueHeaders).reduce((headers, key) => {
13+
const value = event.multiValueHeaders[key];
14+
headers[key.toLowerCase()] = value.length > 1 ? value : value[0];
1415
return headers;
1516
}, {});
1617

1718
this.hostname = this.headers.host
1819
this.method = event.httpMethod;
19-
this.query = event.queryStringParameters;
20+
this.query = Object.keys(event.multiValueQueryStringParameters).reduce((queryParams, key) => {
21+
const value = event.multiValueQueryStringParameters[key];
22+
queryParams[key] = value.length > 1 ? value : value[0];
23+
return queryParams;
24+
}, {});
25+
2026
this.path = event.path;
2127
this.params = event.pathParameters;
2228

src/request.spec.js

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,57 @@ describe('Request object', () => {
66
body: JSON.stringify(requestObject),
77
headers: {
88
'Content-Type': 'application/json',
9-
'Content-Length': JSON.stringify(requestObject).length
9+
'Content-Length': JSON.stringify(requestObject).length,
10+
'X-Header': 'value2'
11+
},
12+
multiValueHeaders: {
13+
'Content-Type': [ 'application/json' ],
14+
'Content-Length': [ JSON.stringify(requestObject).length ],
15+
'X-Header': [ 'value1', 'value2' ]
1016
},
11-
multiValueHeaders: {},
1217
httpMethod: 'POST',
1318
isBase64Encoded: false,
1419
path: '/path',
1520
pathParameters: { },
16-
queryStringParameters: { },
17-
multiValueQueryStringParameters: { },
21+
queryStringParameters: {
22+
a: '1',
23+
b: '2'
24+
},
25+
multiValueQueryStringParameters: {
26+
a: [ '1' ],
27+
b: [ '1', '2']
28+
},
1829
stageVariables: { },
1930
requestContext: {},
2031
resource: ''
2132
};
2233

34+
it('should read query parameter', () => {
35+
const request = new Request(event);
36+
37+
expect(request.query.a).toBe('1');
38+
});
39+
40+
41+
it('should read query parameter with multiple values', () => {
42+
const request = new Request(event);
43+
44+
expect(request.query.b).toEqual(['1', '2']);
45+
});
46+
2347
it('should read header', () => {
2448
const request = new Request(event);
2549

2650
expect(request.get('Content-Type')).toBe('application/json');
2751
expect(request.get('content-type')).toBe('application/json');
2852
});
2953

54+
it('should read header with multiple values', () => {
55+
const request = new Request(event);
56+
57+
expect(request.get('X-Header')).toEqual(['value1', 'value2']);
58+
});
59+
3060
it('should handle weird header asks', () => {
3161
const request = new Request(event);
3262

@@ -37,6 +67,7 @@ describe('Request object', () => {
3767
it('should read referer/referrer header', () => {
3868
const referer = 'muratcorlu.com';
3969
event.headers['Referer'] = referer;
70+
event.multiValueHeaders['Referer'] = [ referer ];
4071

4172
const request = new Request(event);
4273
expect(request.get('referer')).toBe(referer);
@@ -52,6 +83,7 @@ describe('Request object', () => {
5283

5384
it('should check accept header', () => {
5485
event.headers['Accept'] = 'application/json';
86+
event.multiValueHeaders['Accept'] = [ 'application/json' ];
5587

5688
const request = new Request(event);
5789
expect(request.accepts('xml')).toBe(false);
@@ -63,6 +95,7 @@ describe('Request object', () => {
6395

6496
it('should check acceptEncodings', () => {
6597
event.headers['accept-encoding'] = 'gzip, compress;q=0.2';
98+
event.multiValueHeaders['accept-encoding'] = [ 'gzip, compress;q=0.2' ];
6699

67100
const request = new Request(event);
68101
expect(request.acceptsEncodings('gzip', 'compress')).toBe('gzip');
@@ -71,6 +104,7 @@ describe('Request object', () => {
71104

72105
it('should check acceptsCharsets', () => {
73106
event.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5';
107+
event.multiValueHeaders['accept-charset'] = [ 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5' ];
74108

75109
const request = new Request(event);
76110
expect(request.acceptsCharsets('utf-7', 'utf-8')).toBe('utf-8');
@@ -79,6 +113,7 @@ describe('Request object', () => {
79113

80114
it('should check acceptsLanguages', () => {
81115
event.headers['accept-charset'] = 'en;q=0.8, es, tr';
116+
event.multiValueHeaders['accept-charset'] = [ 'en;q=0.8, es, tr' ];
82117

83118
const request = new Request(event);
84119
expect(request.acceptsLanguages('tr', 'en')).toBe('tr');

src/response.spec.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ describe('Response object', () => {
9494
'Accept': 'text/xml',
9595
'Content-Length': 0
9696
},
97-
multiValueHeaders: {},
97+
multiValueHeaders: {
98+
'Accept': [ 'text/xml' ],
99+
'Content-Length': [ 0 ]
100+
},
98101
httpMethod: 'POST',
99102
isBase64Encoded: false,
100103
path: '/path',
@@ -136,7 +139,10 @@ describe('Response object', () => {
136139
'Accept': 'text/html',
137140
'Content-Length': 0
138141
},
139-
multiValueHeaders: {},
142+
multiValueHeaders: {
143+
'Accept': [ 'text/html' ],
144+
'Content-Length': [ 0 ]
145+
},
140146
httpMethod: 'POST',
141147
isBase64Encoded: false,
142148
path: '/path',
@@ -182,7 +188,10 @@ describe('Response object', () => {
182188
'Accept': 'image/jpeg',
183189
'Content-Length': 0
184190
},
185-
multiValueHeaders: {},
191+
multiValueHeaders: {
192+
'Accept': [ 'image/jpeg' ],
193+
'Content-Length': [ 0 ]
194+
},
186195
httpMethod: 'POST',
187196
isBase64Encoded: false,
188197
path: '/path',

src/test-event.json

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
{
2+
"body": "eyJ0ZXN0IjoiYm9keSJ9",
3+
"resource": "/{proxy+}",
4+
"path": "/path/to/resource",
5+
"httpMethod": "POST",
6+
"isBase64Encoded": true,
7+
"queryStringParameters": {
8+
"foo": "bar"
9+
},
10+
"multiValueQueryStringParameters": {
11+
"foo": [
12+
"bar"
13+
]
14+
},
15+
"pathParameters": {
16+
"proxy": "/path/to/resource"
17+
},
18+
"stageVariables": {
19+
"baz": "qux"
20+
},
21+
"headers": {
22+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
23+
"Accept-Encoding": "gzip, deflate, sdch",
24+
"Accept-Language": "en-US,en;q=0.8",
25+
"Cache-Control": "max-age=0",
26+
"CloudFront-Forwarded-Proto": "https",
27+
"CloudFront-Is-Desktop-Viewer": "true",
28+
"CloudFront-Is-Mobile-Viewer": "false",
29+
"CloudFront-Is-SmartTV-Viewer": "false",
30+
"CloudFront-Is-Tablet-Viewer": "false",
31+
"CloudFront-Viewer-Country": "US",
32+
"Host": "1234567890.execute-api.eu-central-1.amazonaws.com",
33+
"Upgrade-Insecure-Requests": "1",
34+
"User-Agent": "Custom User Agent String",
35+
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
36+
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
37+
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
38+
"X-Forwarded-Port": "443",
39+
"X-Forwarded-Proto": "https"
40+
},
41+
"multiValueHeaders": {
42+
"Accept": [
43+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
44+
],
45+
"Accept-Encoding": [
46+
"gzip, deflate, sdch"
47+
],
48+
"Accept-Language": [
49+
"en-US,en;q=0.8"
50+
],
51+
"Cache-Control": [
52+
"max-age=0"
53+
],
54+
"CloudFront-Forwarded-Proto": [
55+
"https"
56+
],
57+
"CloudFront-Is-Desktop-Viewer": [
58+
"true"
59+
],
60+
"CloudFront-Is-Mobile-Viewer": [
61+
"false"
62+
],
63+
"CloudFront-Is-SmartTV-Viewer": [
64+
"false"
65+
],
66+
"CloudFront-Is-Tablet-Viewer": [
67+
"false"
68+
],
69+
"CloudFront-Viewer-Country": [
70+
"US"
71+
],
72+
"Host": [
73+
"0123456789.execute-api.eu-central-1.amazonaws.com"
74+
],
75+
"Upgrade-Insecure-Requests": [
76+
"1"
77+
],
78+
"User-Agent": [
79+
"Custom User Agent String"
80+
],
81+
"Via": [
82+
"1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)"
83+
],
84+
"X-Amz-Cf-Id": [
85+
"cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA=="
86+
],
87+
"X-Forwarded-For": [
88+
"127.0.0.1, 127.0.0.2"
89+
],
90+
"X-Forwarded-Port": [
91+
"443"
92+
],
93+
"X-Forwarded-Proto": [
94+
"https"
95+
]
96+
},
97+
"requestContext": {
98+
"accountId": "123456789012",
99+
"resourceId": "123456",
100+
"stage": "prod",
101+
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
102+
"requestTime": "09/Apr/2015:12:34:56 +0000",
103+
"requestTimeEpoch": 1428582896000,
104+
"identity": {
105+
"cognitoIdentityPoolId": null,
106+
"accountId": null,
107+
"cognitoIdentityId": null,
108+
"caller": null,
109+
"accessKey": null,
110+
"sourceIp": "127.0.0.1",
111+
"cognitoAuthenticationType": null,
112+
"cognitoAuthenticationProvider": null,
113+
"userArn": null,
114+
"userAgent": "Custom User Agent String",
115+
"user": null
116+
},
117+
"path": "/prod/path/to/resource",
118+
"resourcePath": "/{proxy+}",
119+
"httpMethod": "POST",
120+
"apiId": "1234567890",
121+
"protocol": "HTTP/1.1"
122+
}
123+
}

0 commit comments

Comments
 (0)