-
Notifications
You must be signed in to change notification settings - Fork 87
/
Copy pathgetHandlerFunction.js
209 lines (171 loc) · 6.47 KB
/
getHandlerFunction.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// TEMPLATE: This file will be copied to the Netlify functions directory
// Render function for the Next.js page
const { Buffer } = require('buffer')
const http = require('http')
const queryString = require('querystring')
const Stream = require('stream')
// Mock a HTTP IncomingMessage object from the Netlify Function event parameters
// Based on API Gateway Lambda Compat
// Source: https://github.com/serverless-nextjs/serverless-next.js/blob/master/packages/compat-layers/apigw-lambda-compat/lib/compatLayer.js
const createRequestObject = ({ event, context }) => {
const {
requestContext = {},
path = '',
multiValueQueryStringParameters,
pathParameters,
httpMethod,
multiValueHeaders = {},
body,
isBase64Encoded,
} = event
const newStream = new Stream.Readable()
const req = Object.assign(newStream, http.IncomingMessage.prototype)
req.url = (requestContext.path || path || '').replace(new RegExp(`^/${requestContext.stage}`), '') || '/'
let qs = ''
if (multiValueQueryStringParameters) {
qs += queryString.stringify(multiValueQueryStringParameters)
}
if (pathParameters) {
const pathParametersQs = queryString.stringify(pathParameters)
qs += qs.length === 0 ? pathParametersQs : `&${pathParametersQs}`
}
const hasQueryString = qs.length !== 0
if (hasQueryString) {
req.url += `?${qs}`
}
req.method = httpMethod
req.rawHeaders = []
req.headers = {}
// Expose Netlify Function event and callback on request object.
// This makes it possible to access the clientContext, for example.
// See: https://github.com/netlify/next-on-netlify/issues/20
// It also allows users to change the behavior of waiting for empty event
// loop.
// See: https://github.com/netlify/next-on-netlify/issues/66#issuecomment-719988804
req.netlifyFunctionParams = { event, context }
for (const key of Object.keys(multiValueHeaders)) {
for (const value of multiValueHeaders[key]) {
req.rawHeaders.push(key)
req.rawHeaders.push(value)
}
req.headers[key.toLowerCase()] = multiValueHeaders[key].toString()
}
req.getHeader = (name) => req.headers[name.toLowerCase()]
req.getHeaders = () => req.headers
req.connection = {}
if (body) {
req.push(body, isBase64Encoded ? 'base64' : undefined)
}
req.push(null)
return req
}
// Mock a HTTP ServerResponse object that returns a Netlify Function-compatible
// response via the onResEnd callback when res.end() is called.
// Based on API Gateway Lambda Compat
// Source: https://github.com/serverless-nextjs/serverless-next.js/blob/master/packages/compat-layers/apigw-lambda-compat/lib/compatLayer.js
const createResponseObject = ({ onResEnd }) => {
const response = {
isBase64Encoded: true,
multiValueHeaders: {},
}
const res = new Stream()
Object.defineProperty(res, 'statusCode', {
get() {
return response.statusCode
},
set(statusCode) {
response.statusCode = statusCode
},
})
res.headers = {}
res.writeHead = (status, headers) => {
response.statusCode = status
if (headers) res.headers = Object.assign(res.headers, headers)
// Return res object to allow for chaining
// Fixes: https://github.com/netlify/next-on-netlify/pull/74
return res
}
res.write = (chunk) => {
if (!response.body) {
response.body = Buffer.from('')
}
response.body = Buffer.concat([
Buffer.isBuffer(response.body) ? response.body : Buffer.from(response.body),
Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk),
])
}
res.setHeader = (name, value) => {
res.headers[name.toLowerCase()] = value
}
res.removeHeader = (name) => {
delete res.headers[name.toLowerCase()]
}
res.getHeader = (name) => res.headers[name.toLowerCase()]
res.getHeaders = () => res.headers
res.hasHeader = (name) => Boolean(res.getHeader(name))
res.end = (text) => {
if (text) res.write(text)
if (!res.statusCode) {
res.statusCode = 200
}
if (response.body) {
response.body = Buffer.from(response.body).toString('base64')
}
response.multiValueHeaders = res.headers
res.writeHead(response.statusCode)
// Convert all multiValueHeaders into arrays
for (const key of Object.keys(response.multiValueHeaders)) {
if (!Array.isArray(response.multiValueHeaders[key])) {
response.multiValueHeaders[key] = [response.multiValueHeaders[key]]
}
}
res.finished = true
res.writableEnded = true
// Call onResEnd handler with the response object
onResEnd(response)
}
return res
}
// Render the Next.js page
const renderNextPage = ({ event, context }, nextPage) => {
// The Next.js page is rendered inside a promise that is resolved when the
// Next.js page ends the response via `res.end()`
const promise = new Promise((resolve) => {
// Create a Next.js-compatible request and response object
// These mock the ClientRequest and ServerResponse classes from node http
// See: https://nodejs.org/api/http.html
const req = createRequestObject({ event, context })
const res = createResponseObject({
onResEnd: (response) => resolve(response),
})
// Check if page is a Next.js page or an API route
const isNextPage = nextPage.render instanceof Function
const isApiRoute = !isNextPage
// Perform the render: render() for Next.js page or default() for API route
if (isNextPage) return nextPage.render(req, res)
if (isApiRoute) return nextPage.default(req, res)
})
// Return the promise
return promise
}
const getHandlerFunction = (nextPage) => async (event, context) => {
// x-forwarded-host is undefined on Netlify for proxied apps that need it
// fixes https://github.com/netlify/next-on-netlify/issues/46
if (!event.multiValueHeaders.hasOwnProperty('x-forwarded-host')) {
// eslint-disable-next-line no-param-reassign
event.multiValueHeaders['x-forwarded-host'] = [event.headers.host]
}
// Get the request URL
const { path } = event
console.log('[request]', path)
// Render the Next.js page
const response = await renderNextPage({ event, context }, nextPage)
// Convert header values to string. Netlify does not support integers as
// header values. See: https://github.com/netlify/cli/issues/451
Object.keys(response.multiValueHeaders).forEach((key) => {
response.multiValueHeaders[key] = response.multiValueHeaders[key].map((value) => String(value))
})
response.multiValueHeaders['Cache-Control'] = ['no-cache']
return response
}
exports.getHandlerFunction = getHandlerFunction