1 | var express = require('express');
|
2 | var bodyParser = require('body-parser');
|
3 | var expressLogging = require('express-logging');
|
4 | var queryString = require('querystring');
|
5 | var path = require('path');
|
6 | var conf = require('./config');
|
7 | var jwtDecode = require('jwt-decode');
|
8 |
|
9 | function handleErr(err, response) {
|
10 | response.statusCode = 500;
|
11 | response.write('Function invocation failed: ' + err.toString());
|
12 | response.end();
|
13 | console.log('Error during invocation: ', err);
|
14 | return;
|
15 | }
|
16 |
|
17 | function handleInvocationTimeout(response, timeout) {
|
18 | response.statusCode = 500;
|
19 | response.write(`Function invocation took longer than ${timeout} seconds.`);
|
20 | response.end();
|
21 | console.log(
|
22 | `Your lambda function took longer than ${timeout} seconds to finish.
|
23 | If you need a longer execution time, you can increase the timeout using the -t or --timeout flag.
|
24 | Please note that default function invocation is 10 seconds, check our documentation for more information (https://www.netlify.com/docs/functions/#custom-deployment-options).
|
25 | `,
|
26 | );
|
27 | }
|
28 |
|
29 | function createCallback(response) {
|
30 | return function callback(err, lambdaResponse) {
|
31 | if (err) {
|
32 | return handleErr(err, response);
|
33 | }
|
34 |
|
35 | response.statusCode = lambdaResponse.statusCode;
|
36 | for (const key in lambdaResponse.headers) {
|
37 | response.setHeader(key, lambdaResponse.headers[key]);
|
38 | }
|
39 | for (const key in lambdaResponse.multiValueHeaders) {
|
40 | const items = lambdaResponse.multiValueHeaders[key];
|
41 | response.setHeader(key, items);
|
42 | }
|
43 |
|
44 | if (lambdaResponse.body) {
|
45 | response.write(
|
46 | lambdaResponse.isBase64Encoded
|
47 | ? Buffer.from(lambdaResponse.body, 'base64')
|
48 | : lambdaResponse.body,
|
49 | );
|
50 | } else {
|
51 | if (
|
52 | response.statusCode !== 204 &&
|
53 | process.env.CONTEXT !== 'production' &&
|
54 | !process.env.SILENCE_EMPTY_LAMBDA_WARNING
|
55 | )
|
56 | console.log(
|
57 | `Your lambda function didn't return a body, which may be a mistake. Check our Usage docs for examples (https://github.com/netlify/netlify-lambda#usage).
|
58 | If this is intentional, you can silence this warning by setting process.env.SILENCE_EMPTY_LAMBDA_WARNING to a truthy value or process.env.CONTEXT to 'production'`,
|
59 | );
|
60 | }
|
61 | response.end();
|
62 | };
|
63 | }
|
64 |
|
65 | function promiseCallback(promise, callback) {
|
66 | if (!promise) return;
|
67 | if (typeof promise.then !== 'function') return;
|
68 | if (typeof callback !== 'function') return;
|
69 |
|
70 | return promise.then(
|
71 | function (data) {
|
72 | callback(null, data);
|
73 | },
|
74 | function (err) {
|
75 | callback(err, null);
|
76 | },
|
77 | );
|
78 | }
|
79 |
|
80 | function buildClientContext(headers) {
|
81 |
|
82 | if (!headers['authorization']) return;
|
83 |
|
84 | const parts = headers['authorization'].split(' ');
|
85 | if (parts.length !== 2 || parts[0] !== 'Bearer') return;
|
86 |
|
87 | try {
|
88 | return {
|
89 | identity: {
|
90 | url: 'NETLIFY_LAMBDA_LOCALLY_EMULATED_IDENTITY_URL',
|
91 | token: 'NETLIFY_LAMBDA_LOCALLY_EMULATED_IDENTITY_TOKEN',
|
92 | },
|
93 | user: jwtDecode(parts[1]),
|
94 | };
|
95 | } catch (e) {
|
96 | return;
|
97 | }
|
98 | }
|
99 |
|
100 | function createHandler(dir, isStatic, timeout) {
|
101 | return function (request, response) {
|
102 |
|
103 | var cleanPath = request.path.replace(/^\/.netlify\/functions/, '');
|
104 |
|
105 | var func = cleanPath.split('/').filter((e) => !!e)[0];
|
106 | if (typeof func === 'undefined') {
|
107 | console.error(
|
108 | `Something went wrong and the function path derived from ${cleanPath} (raw form: ${request.path}) was undefined. Please doublecheck your function naming and toml configuration.`,
|
109 | );
|
110 | }
|
111 | if (typeof dir === 'undefined') {
|
112 | console.error(
|
113 | `Something went wrong and the function directory ${dir} was undefined. Please doublecheck your toml configuration.`,
|
114 | );
|
115 | }
|
116 | var module = path.join(process.cwd(), dir, func);
|
117 | if (isStatic) {
|
118 | delete require.cache[require.resolve(module)];
|
119 | }
|
120 | var handler;
|
121 | try {
|
122 | handler = require(module);
|
123 | } catch (err) {
|
124 | handleErr(err, response);
|
125 | return;
|
126 | }
|
127 |
|
128 | var isBase64 =
|
129 | request.body &&
|
130 | !(request.headers['content-type'] || '').match(
|
131 | /text|application|multipart\/form-data/,
|
132 | );
|
133 | var lambdaRequest = {
|
134 | path: request.path,
|
135 | httpMethod: request.method,
|
136 | queryStringParameters: queryString.parse(request.url.split(/\?(.+)/)[1]),
|
137 | headers: request.headers,
|
138 | body: isBase64
|
139 | ? Buffer.from(request.body.toString(), 'utf8').toString('base64')
|
140 | : request.body,
|
141 | isBase64Encoded: isBase64,
|
142 | };
|
143 |
|
144 | var callback = createCallback(response);
|
145 |
|
146 | var promise = handler.handler(
|
147 | lambdaRequest,
|
148 | { clientContext: buildClientContext(request.headers) || {} },
|
149 | callback,
|
150 | );
|
151 |
|
152 | var invocationTimeoutRef = null;
|
153 |
|
154 | Promise.race([
|
155 | promiseCallback(promise, callback),
|
156 | new Promise(function (resolve) {
|
157 | invocationTimeoutRef = setTimeout(function () {
|
158 | handleInvocationTimeout(response, timeout);
|
159 | resolve();
|
160 | }, timeout * 1000);
|
161 | }),
|
162 | ]).then(
|
163 | (result) => {
|
164 | clearTimeout(invocationTimeoutRef);
|
165 | return result;
|
166 | },
|
167 | (err) => {
|
168 | clearTimeout(invocationTimeoutRef);
|
169 | throw err;
|
170 | },
|
171 | );
|
172 | };
|
173 | }
|
174 |
|
175 | exports.listen = async function (port, isStatic, timeout) {
|
176 | var config = await conf.load();
|
177 | var app = express();
|
178 | var dir = config.build.functions || config.build.Functions;
|
179 | app.use(bodyParser.raw({ limit: '6mb' }));
|
180 | app.use(bodyParser.text({ limit: '6mb', type: '*/*' }));
|
181 | app.use(
|
182 | expressLogging(console, {
|
183 | blacklist: ['/favicon.ico'],
|
184 | }),
|
185 | );
|
186 |
|
187 | app.get('/favicon.ico', function (req, res) {
|
188 | res.status(204).end();
|
189 | });
|
190 | app.get('/', function (req, res) {
|
191 | res
|
192 | .status(404)
|
193 | .send(
|
194 | `You have requested the root of http://localhost:${port}. This is likely a mistake. netlify-lambda serves functions at http://localhost:${port}/.netlify/functions/your-function-name; please fix your code.`,
|
195 | );
|
196 | });
|
197 | app.all('*', createHandler(dir, isStatic, timeout));
|
198 |
|
199 | const server = app.listen(port, function (err) {
|
200 | if (err) {
|
201 | console.error('Unable to start lambda server: ', err);
|
202 | process.exit(1);
|
203 | }
|
204 |
|
205 | console.log(`Lambda server is listening on ${port}`);
|
206 | });
|
207 |
|
208 | return {
|
209 | clearCache: function (chunk) {
|
210 | var module = path.join(process.cwd(), dir, chunk);
|
211 | delete require.cache[require.resolve(module)];
|
212 | },
|
213 | stopServer: () => server.close(),
|
214 | };
|
215 | };
|