Skip to content

Commit 70f71c0

Browse files
danielcondemarinsclaughl
authored andcommitted
feat(next-aws-lambda,next-aws-cloudfront): fix status code and body bugs (serverless-nextjs#437)
1 parent a6f136e commit 70f71c0

File tree

4 files changed

+171
-13
lines changed

4 files changed

+171
-13
lines changed

packages/apigw-lambda-compat/lib/__tests__/compatLayer.response.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,23 @@ describe("compatLayer.response", () => {
5656
res.end();
5757
});
5858

59+
it("[Promise] statusCode statusCode=200 by default", () => {
60+
expect.assertions(1);
61+
62+
const { res, responsePromise } = create({
63+
requestContext: {
64+
path: "/"
65+
},
66+
headers: {}
67+
});
68+
69+
res.end();
70+
71+
return responsePromise.then(response => {
72+
expect(response.statusCode).toEqual(200);
73+
});
74+
});
75+
5976
it("[Promise] statusCode statusCode=200", () => {
6077
expect.assertions(1);
6178

@@ -452,4 +469,21 @@ describe("compatLayer.response", () => {
452469
expect(response.isBase64Encoded).toEqual(true);
453470
});
454471
});
472+
473+
it("response does not have a body if only statusCode is set", () => {
474+
const { res, responsePromise } = create({
475+
requestContext: {
476+
path: "/"
477+
},
478+
headers: {}
479+
});
480+
481+
res.statusCode = 204;
482+
res.end();
483+
484+
return responsePromise.then(response => {
485+
expect(response.body).not.toBeDefined();
486+
expect(response.statusCode).toEqual(204);
487+
});
488+
});
455489
});

packages/apigw-lambda-compat/lib/compatLayer.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ const http = require("http");
55
const reqResMapper = (event, callback) => {
66
const base64Support = process.env.BINARY_SUPPORT === "yes";
77
const response = {
8-
body: Buffer.from(""),
98
isBase64Encoded: base64Support,
10-
statusCode: 200,
119
multiValueHeaders: {}
1210
};
1311
let responsePromise;
@@ -80,6 +78,10 @@ const reqResMapper = (event, callback) => {
8078
if (headers) res.headers = Object.assign(res.headers, headers);
8179
};
8280
res.write = chunk => {
81+
if (!response.body) {
82+
response.body = Buffer.from("");
83+
}
84+
8385
response.body = Buffer.concat([
8486
Buffer.isBuffer(response.body)
8587
? response.body
@@ -105,9 +107,15 @@ const reqResMapper = (event, callback) => {
105107

106108
const onResEnd = (callback, resolve) => text => {
107109
if (text) res.write(text);
108-
response.body = Buffer.from(response.body).toString(
109-
base64Support ? "base64" : undefined
110-
);
110+
if (!res.statusCode) {
111+
res.statusCode = 200;
112+
}
113+
114+
if (response.body) {
115+
response.body = Buffer.from(response.body).toString(
116+
base64Support ? "base64" : undefined
117+
);
118+
}
111119
response.multiValueHeaders = res.headers;
112120
res.writeHead(response.statusCode);
113121
fixApiGatewayMultipleHeaders();

packages/lambda-at-edge-compat/__tests__/next-aws-cloudfront.response.test.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe("Response Tests", () => {
2121
});
2222

2323
it("statusCode statusCode=200", () => {
24-
expect.assertions(1);
24+
expect.assertions(2);
2525

2626
const { res, responsePromise } = create({
2727
request: {
@@ -33,6 +33,24 @@ describe("Response Tests", () => {
3333
res.statusCode = 200;
3434
res.end();
3535

36+
return responsePromise.then(response => {
37+
expect(response.status).toEqual(200);
38+
expect(response.statusDescription).toEqual("OK");
39+
});
40+
});
41+
42+
it("statusCode statusCode=200 by default", () => {
43+
expect.assertions(1);
44+
45+
const { res, responsePromise } = create({
46+
request: {
47+
uri: "/",
48+
headers: {}
49+
}
50+
});
51+
52+
res.end();
53+
3654
return responsePromise.then(response => {
3755
expect(response.status).toEqual(200);
3856
});
@@ -265,6 +283,8 @@ describe("Response Tests", () => {
265283
});
266284

267285
it(`res.write('ok')`, () => {
286+
expect.assertions(2);
287+
268288
const { res, responsePromise } = create({
269289
request: {
270290
path: "/",
@@ -277,10 +297,13 @@ describe("Response Tests", () => {
277297

278298
return responsePromise.then(response => {
279299
expect(response.body).toEqual("b2s=");
300+
expect(response.bodyEncoding).toEqual("base64");
280301
});
281302
});
282303

283304
it(`res.end('ok')`, () => {
305+
expect.assertions(1);
306+
284307
const { res, responsePromise } = create({
285308
request: {
286309
path: "/",
@@ -296,6 +319,8 @@ describe("Response Tests", () => {
296319
});
297320

298321
it(`gzips`, () => {
322+
expect.assertions(2);
323+
299324
const gzipSpy = jest.spyOn(zlib, "gzipSync");
300325
gzipSpy.mockReturnValueOnce(Buffer.from("ok-gzipped"));
301326

@@ -324,4 +349,25 @@ describe("Response Tests", () => {
324349
expect(response.body).toEqual("b2stZ3ppcHBlZA=="); // "ok-gzipped" base64 encoded
325350
});
326351
});
352+
353+
it("response does not have a body if only statusCode is set", () => {
354+
expect.assertions(4);
355+
356+
const { res, responsePromise } = create({
357+
request: {
358+
path: "/",
359+
headers: {}
360+
}
361+
});
362+
363+
res.statusCode = 204;
364+
res.end();
365+
366+
return responsePromise.then(response => {
367+
expect(response.body).not.toBeDefined();
368+
expect(response.bodyEncoding).not.toBeDefined();
369+
expect(response.status).toEqual(204);
370+
expect(response.statusDescription).toEqual("No Content");
371+
});
372+
});
327373
});

packages/lambda-at-edge-compat/next-aws-cloudfront.js

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,63 @@ const readOnlyCloudFrontHeaders = {
3333
via: true
3434
};
3535

36+
const HttpStatusCodes = {
37+
202: "Accepted",
38+
502: "Bad Gateway",
39+
400: "Bad Request",
40+
409: "Conflict",
41+
100: "Continue",
42+
201: "Created",
43+
417: "Expectation Failed",
44+
424: "Failed Dependency",
45+
403: "Forbidden",
46+
504: "Gateway Timeout",
47+
410: "Gone",
48+
505: "HTTP Version Not Supported",
49+
418: "I'm a teapot",
50+
419: "Insufficient Space on Resource",
51+
507: "Insufficient Storage",
52+
500: "Server Error",
53+
411: "Length Required",
54+
423: "Locked",
55+
420: "Method Failure",
56+
405: "Method Not Allowed",
57+
301: "Moved Permanently",
58+
302: "Moved Temporarily",
59+
207: "Multi-Status",
60+
300: "Multiple Choices",
61+
511: "Network Authentication Required",
62+
204: "No Content",
63+
203: "Non Authoritative Information",
64+
406: "Not Acceptable",
65+
404: "Not Found",
66+
501: "Not Implemented",
67+
304: "Not Modified",
68+
200: "OK",
69+
206: "Partial Content",
70+
402: "Payment Required",
71+
308: "Permanent Redirect",
72+
412: "Precondition Failed",
73+
428: "Precondition Required",
74+
102: "Processing",
75+
407: "Proxy Authentication Required",
76+
431: "Request Header Fields Too Large",
77+
408: "Request Timeout",
78+
413: "Request Entity Too Large",
79+
414: "Request-URI Too Long",
80+
416: "Requested Range Not Satisfiable",
81+
205: "Reset Content",
82+
303: "See Other",
83+
503: "Service Unavailable",
84+
101: "Switching Protocols",
85+
307: "Temporary Redirect",
86+
429: "Too Many Requests",
87+
401: "Unauthorized",
88+
422: "Unprocessable Entity",
89+
415: "Unsupported Media Type",
90+
305: "Use Proxy"
91+
};
92+
3693
const toCloudFrontHeaders = headers => {
3794
const result = {};
3895

@@ -83,10 +140,6 @@ const handler = event => {
83140
const { request: cfRequest } = event;
84141

85142
const response = {
86-
body: Buffer.from(""),
87-
bodyEncoding: "base64",
88-
status: 200,
89-
statusDescription: "OK",
90143
headers: {}
91144
};
92145

@@ -141,31 +194,48 @@ const handler = event => {
141194
},
142195
set(statusCode) {
143196
response.status = statusCode;
197+
response.statusDescription = HttpStatusCodes[statusCode];
144198
}
145199
});
146200

147201
res.headers = {};
148202
res.writeHead = (status, headers) => {
149203
response.status = status;
204+
150205
if (headers) {
151206
res.headers = Object.assign(res.headers, headers);
152207
}
153208
};
154209
res.write = chunk => {
210+
if (!response.body) {
211+
response.body = Buffer.from("");
212+
}
213+
155214
response.body = Buffer.concat([
156215
response.body,
157216
Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
158217
]);
159218
};
219+
160220
let gz = isGzipSupported(headers);
161221

162222
const responsePromise = new Promise(resolve => {
163223
res.end = text => {
164224
if (text) res.write(text);
225+
226+
if (!res.statusCode) {
227+
res.statusCode = 200;
228+
}
229+
165230
res.finished = true;
166-
response.body = gz
167-
? zlib.gzipSync(response.body).toString("base64")
168-
: Buffer.from(response.body).toString("base64");
231+
232+
if (response.body) {
233+
response.bodyEncoding = "base64";
234+
response.body = gz
235+
? zlib.gzipSync(response.body).toString("base64")
236+
: Buffer.from(response.body).toString("base64");
237+
}
238+
169239
response.headers = toCloudFrontHeaders(res.headers);
170240

171241
if (gz) {

0 commit comments

Comments
 (0)