Skip to content
This repository was archived by the owner on May 10, 2021. It is now read-only.

Commit ef1e7ae

Browse files
committed
Refactor: Split reqResMapper into createRequest/ResponseObject
Split the reqResMapper function into two separate files. One for creating the request object and one for creating the response object. This is easier to read and understand.
1 parent 224ad89 commit ef1e7ae

File tree

5 files changed

+174
-160
lines changed

5 files changed

+174
-160
lines changed

lib/helpers/setupNetlifyFunctionForPage.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ const setupNetlifyFunctionForPage = ({ filePath, functionsPath }) => {
2424
});
2525

2626
// Copy function helpers
27-
["renderNextPage.js", "reqResMapper.js"].forEach((helper) => {
27+
const functionHelpers = [
28+
"renderNextPage.js",
29+
"createRequestObject.js",
30+
"createResponseObject.js",
31+
];
32+
functionHelpers.forEach((helper) => {
2833
copySync(join(TEMPLATES_DIR, helper), join(functionDirectory, helper), {
2934
overwrite: false,
3035
errorOnExist: true,

lib/templates/createRequestObject.js

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const Stream = require("stream");
2+
const queryString = require("querystring");
3+
const http = require("http");
4+
5+
// Mock a HTTP IncomingMessage object from the Netlify Function event parameters
6+
// Based on API Gateway Lambda Compat
7+
// Source: https://github.com/serverless-nextjs/serverless-next.js/blob/master/packages/compat-layers/apigw-lambda-compat/lib/compatLayer.js
8+
9+
const createRequestObject = ({ event }) => {
10+
const {
11+
requestContext = {},
12+
path = "",
13+
multiValueQueryStringParameters,
14+
pathParameters,
15+
httpMethod,
16+
multiValueHeaders = {},
17+
body,
18+
isBase64Encoded,
19+
} = event;
20+
21+
const newStream = new Stream.Readable();
22+
const req = Object.assign(newStream, http.IncomingMessage.prototype);
23+
req.url =
24+
(requestContext.path || path || "").replace(
25+
new RegExp("^/" + requestContext.stage),
26+
""
27+
) || "/";
28+
29+
let qs = "";
30+
31+
if (multiValueQueryStringParameters) {
32+
qs += queryString.stringify(multiValueQueryStringParameters);
33+
}
34+
35+
if (pathParameters) {
36+
const pathParametersQs = queryString.stringify(pathParameters);
37+
38+
if (qs.length > 0) {
39+
qs += `&${pathParametersQs}`;
40+
} else {
41+
qs += pathParametersQs;
42+
}
43+
}
44+
45+
const hasQueryString = qs.length > 0;
46+
47+
if (hasQueryString) {
48+
req.url += `?${qs}`;
49+
}
50+
51+
req.method = httpMethod;
52+
req.rawHeaders = [];
53+
req.headers = {};
54+
55+
for (const key of Object.keys(multiValueHeaders)) {
56+
for (const value of multiValueHeaders[key]) {
57+
req.rawHeaders.push(key);
58+
req.rawHeaders.push(value);
59+
}
60+
req.headers[key.toLowerCase()] = multiValueHeaders[key].toString();
61+
}
62+
63+
req.getHeader = (name) => {
64+
return req.headers[name.toLowerCase()];
65+
};
66+
req.getHeaders = () => {
67+
return req.headers;
68+
};
69+
70+
req.connection = {};
71+
72+
if (body) {
73+
req.push(body, isBase64Encoded ? "base64" : undefined);
74+
}
75+
76+
req.push(null);
77+
78+
return req;
79+
};
80+
81+
module.exports = createRequestObject;

lib/templates/createResponseObject.js

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const Stream = require("stream");
2+
3+
// Mock a HTTP ServerResponse object that returns a Netlify Function-compatible
4+
// response via the onResEnd callback when res.end() is called.
5+
// Based on API Gateway Lambda Compat
6+
// Source: https://github.com/serverless-nextjs/serverless-next.js/blob/master/packages/compat-layers/apigw-lambda-compat/lib/compatLayer.js
7+
8+
const createResponseObject = ({ onResEnd }) => {
9+
const response = {
10+
isBase64Encoded: true,
11+
multiValueHeaders: {},
12+
};
13+
14+
const res = new Stream();
15+
Object.defineProperty(res, "statusCode", {
16+
get() {
17+
return response.statusCode;
18+
},
19+
set(statusCode) {
20+
response.statusCode = statusCode;
21+
},
22+
});
23+
res.headers = {};
24+
res.writeHead = (status, headers) => {
25+
response.statusCode = status;
26+
if (headers) res.headers = Object.assign(res.headers, headers);
27+
};
28+
res.write = (chunk) => {
29+
if (!response.body) {
30+
response.body = Buffer.from("");
31+
}
32+
33+
response.body = Buffer.concat([
34+
Buffer.isBuffer(response.body)
35+
? response.body
36+
: Buffer.from(response.body),
37+
Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk),
38+
]);
39+
};
40+
res.setHeader = (name, value) => {
41+
res.headers[name.toLowerCase()] = value;
42+
};
43+
res.removeHeader = (name) => {
44+
delete res.headers[name.toLowerCase()];
45+
};
46+
res.getHeader = (name) => {
47+
return res.headers[name.toLowerCase()];
48+
};
49+
res.getHeaders = () => {
50+
return res.headers;
51+
};
52+
res.hasHeader = (name) => {
53+
return !!res.getHeader(name);
54+
};
55+
res.end = (text) => {
56+
if (text) res.write(text);
57+
if (!res.statusCode) {
58+
res.statusCode = 200;
59+
}
60+
61+
if (response.body) {
62+
response.body = Buffer.from(response.body).toString("base64");
63+
}
64+
response.multiValueHeaders = res.headers;
65+
res.writeHead(response.statusCode);
66+
67+
// Convert all multiValueHeaders into arrays
68+
for (const key of Object.keys(response.multiValueHeaders)) {
69+
if (!Array.isArray(response.multiValueHeaders[key])) {
70+
response.multiValueHeaders[key] = [response.multiValueHeaders[key]];
71+
}
72+
}
73+
74+
// Call onResEnd handler with the response object
75+
onResEnd(response);
76+
};
77+
78+
return res;
79+
};
80+
81+
module.exports = createResponseObject;

lib/templates/renderNextPage.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Load the NextJS page
22
const nextPage = require("./nextPage");
3-
const reqResMapper = require("./reqResMapper");
3+
const createRequestObject = require("./createRequestObject");
4+
const createResponseObject = require("./createResponseObject");
45

56
// Render the Next.js page
67
const renderNextPage = (event) => {
@@ -10,8 +11,10 @@ const renderNextPage = (event) => {
1011
// Create a Next.js-compatible request and response object
1112
// These mock the ClientRequest and ServerResponse classes from node http
1213
// See: https://nodejs.org/api/http.html
13-
const callback = (_null, response) => resolve(response);
14-
const { req, res } = reqResMapper(event, callback);
14+
const req = createRequestObject({ event });
15+
const res = createResponseObject({
16+
onResEnd: (response) => resolve(response),
17+
});
1518

1619
// Check if page is a Next.js page or an API route
1720
const isNextPage = nextPage.render instanceof Function;

lib/templates/reqResMapper.js

-156
This file was deleted.

0 commit comments

Comments
 (0)