UNPKG

6.44 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
40 if (lambdaResponse.body) {
41 response.write(
42 lambdaResponse.isBase64Encoded
43 ? Buffer.from(lambdaResponse.body, "base64")
44 : lambdaResponse.body
45 );
46 } else {
47 if (
48 process.env.CONTEXT !== "production" ||
49 !process.env.SILENCE_EMPTY_LAMBDA_WARNING
50 )
51 console.log(
52 `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).
53 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'`
54 );
55 }
56 response.end();
57 };
58}
59
60function promiseCallback(promise, callback) {
61 if (!promise) return;
62 if (typeof promise.then !== "function") return;
63 if (typeof callback !== "function") return;
64
65 return promise.then(
66 function(data) {
67 callback(null, data);
68 },
69 function(err) {
70 callback(err, null);
71 }
72 );
73}
74
75function buildClientContext(headers) {
76 // inject a client context based on auth header https://github.com/netlify/netlify-lambda/pull/57
77 if (!headers["authorization"]) return;
78
79 const parts = headers["authorization"].split(" ");
80 if (parts.length !== 2 || parts[0] !== "Bearer") return;
81
82 try {
83 return {
84 identity: {
85 url: "NETLIFY_LAMBDA_LOCALLY_EMULATED_IDENTITY_URL",
86 token: "NETLIFY_LAMBDA_LOCALLY_EMULATED_IDENTITY_TOKEN"
87 },
88 user: jwtDecode(parts[1])
89 };
90 } catch (e) {
91 return; // Ignore errors - bearer token is not a JWT, probably not intended for us
92 }
93}
94
95function createHandler(dir, static, timeout) {
96 return function(request, response) {
97 // handle proxies without path re-writes (http-servr)
98 var cleanPath = request.path.replace(/^\/.netlify\/functions/, "");
99
100 var func = cleanPath.split("/").filter(e => !!e)[0];
101 if (typeof func === "undefined") {
102 console.error(
103 `Something went wrong and the function path derived from ${cleanPath} (raw form: ${
104 request.path
105 }) was undefined. Please doublecheck your function naming and toml configuration.`
106 );
107 }
108 if (typeof dir === "undefined") {
109 console.error(
110 `Something went wrong and the function directory ${dir} was undefined. Please doublecheck your toml configuration.`
111 );
112 }
113 var module = path.join(process.cwd(), dir, func);
114 if (static) {
115 delete require.cache[require.resolve(module)];
116 }
117 var handler;
118 try {
119 handler = require(module);
120 } catch (err) {
121 handleErr(err, response);
122 return;
123 }
124
125 var isBase64 =
126 request.body &&
127 !(request.headers["content-type"] || "").match(
128 /text|application|multipart\/form-data/
129 );
130 var lambdaRequest = {
131 path: request.path,
132 httpMethod: request.method,
133 queryStringParameters: queryString.parse(request.url.split(/\?(.+)/)[1]),
134 headers: request.headers,
135 body: isBase64
136 ? Buffer.from(request.body.toString(), "utf8").toString("base64")
137 : request.body,
138 isBase64Encoded: isBase64
139 };
140
141 var callback = createCallback(response);
142
143 var promise = handler.handler(
144 lambdaRequest,
145 { clientContext: buildClientContext(request.headers) || {} },
146 callback
147 );
148
149 var invocationTimeoutRef = null;
150
151 Promise.race([
152 promiseCallback(promise, callback),
153 new Promise(function(resolve) {
154 invocationTimeoutRef = setTimeout(function() {
155 handleInvocationTimeout(response, timeout);
156 resolve();
157 }, timeout * 1000);
158 })
159 ]).then(
160 result => {
161 clearTimeout(invocationTimeoutRef);
162 return result; // not used, but writing this to avoid future footguns
163 },
164 err => {
165 clearTimeout(invocationTimeoutRef);
166 throw err;
167 }
168 );
169 };
170}
171
172exports.listen = function(port, static, timeout) {
173 var config = conf.load();
174 var app = express();
175 var dir = config.build.functions || config.build.Functions;
176 app.use(bodyParser.raw({ limit: "6mb" }));
177 app.use(bodyParser.text({ limit: "6mb", type: "*/*" }));
178 app.use(
179 expressLogging(console, {
180 blacklist: ["/favicon.ico"]
181 })
182 );
183
184 app.get("/favicon.ico", function(req, res) {
185 res.status(204).end();
186 });
187 app.get("/", function(req, res) {
188 res
189 .status(404)
190 .send(
191 `You have requested the root of http://localhost:${port}. This is likely a mistake. netlify-lambda serves functions at htttp://localhost:${port}/.netlify/functions/your-function-name, please fix your code.`
192 );
193 });
194 app.all("*", createHandler(dir, static, timeout));
195
196 app.listen(port, function(err) {
197 if (err) {
198 console.error("Unable to start lambda server: ", err);
199 process.exit(1);
200 }
201
202 console.log(`Lambda server is listening on ${port}`);
203 });
204
205 return {
206 clearCache: function(chunk) {
207 var module = path.join(process.cwd(), dir, chunk);
208 delete require.cache[require.resolve(module)];
209 }
210 };
211};