UNPKG

6.72 kBJavaScriptView Raw
1var express = require('express');
2var bodyParser = require('body-parser');
3var expressLogging = require('express-logging');
4var queryString = require('querystring');
5var path = require('path');
6var conf = require('./config');
7var jwtDecode = require('jwt-decode');
8
9function 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
17function 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.
23If you need a longer execution time, you can increase the timeout using the -t or --timeout flag.
24Please 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
29function 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
65function 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
80function buildClientContext(headers) {
81 // inject a client context based on auth header https://github.com/netlify/netlify-lambda/pull/57
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; // Ignore errors - bearer token is not a JWT, probably not intended for us
97 }
98}
99
100function createHandler(dir, isStatic, timeout) {
101 return function (request, response) {
102 // handle proxies without path re-writes (http-servr)
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; // not used, but writing this to avoid future footguns
166 },
167 (err) => {
168 clearTimeout(invocationTimeoutRef);
169 throw err;
170 },
171 );
172 };
173}
174
175exports.listen = function (port, isStatic, timeout) {
176 var config = 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};