UNPKG

netlify-lambda

Version:

Build and serve lambda function with webpack compilation

213 lines (190 loc) 6.48 kB
var express = require("express"); var bodyParser = require("body-parser"); var expressLogging = require("express-logging"); var queryString = require("querystring"); var path = require("path"); var conf = require("./config"); var jwtDecode = require("jwt-decode"); function handleErr(err, response) { response.statusCode = 500; response.write("Function invocation failed: " + err.toString()); response.end(); console.log("Error during invocation: ", err); return; } function handleInvocationTimeout(response, timeout) { response.statusCode = 500; response.write(`Function invocation took longer than ${timeout} seconds.`); response.end(); console.log( `Your lambda function took longer than ${timeout} seconds to finish. If you need a longer execution time, you can increase the timeout using the -t or --timeout flag. Please note that default function invocation is 10 seconds, check our documentation for more information (https://www.netlify.com/docs/functions/#custom-deployment-options). ` ); } function createCallback(response) { return function callback(err, lambdaResponse) { if (err) { return handleErr(err, response); } response.statusCode = lambdaResponse.statusCode; for (const key in lambdaResponse.headers) { response.setHeader(key, lambdaResponse.headers[key]); } if (lambdaResponse.body) { response.write( lambdaResponse.isBase64Encoded ? Buffer.from(lambdaResponse.body, "base64") : lambdaResponse.body ); } else { if ( response.statusCode !== 204 && process.env.CONTEXT !== "production" && !process.env.SILENCE_EMPTY_LAMBDA_WARNING ) console.log( `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). 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'` ); } response.end(); }; } function promiseCallback(promise, callback) { if (!promise) return; if (typeof promise.then !== "function") return; if (typeof callback !== "function") return; return promise.then( function(data) { callback(null, data); }, function(err) { callback(err, null); } ); } function buildClientContext(headers) { // inject a client context based on auth header https://github.com/netlify/netlify-lambda/pull/57 if (!headers["authorization"]) return; const parts = headers["authorization"].split(" "); if (parts.length !== 2 || parts[0] !== "Bearer") return; try { return { identity: { url: "NETLIFY_LAMBDA_LOCALLY_EMULATED_IDENTITY_URL", token: "NETLIFY_LAMBDA_LOCALLY_EMULATED_IDENTITY_TOKEN" }, user: jwtDecode(parts[1]) }; } catch (e) { return; // Ignore errors - bearer token is not a JWT, probably not intended for us } } function createHandler(dir, static, timeout) { return function(request, response) { // handle proxies without path re-writes (http-servr) var cleanPath = request.path.replace(/^\/.netlify\/functions/, ""); var func = cleanPath.split("/").filter(e => !!e)[0]; if (typeof func === "undefined") { console.error( `Something went wrong and the function path derived from ${cleanPath} (raw form: ${ request.path }) was undefined. Please doublecheck your function naming and toml configuration.` ); } if (typeof dir === "undefined") { console.error( `Something went wrong and the function directory ${dir} was undefined. Please doublecheck your toml configuration.` ); } var module = path.join(process.cwd(), dir, func); if (static) { delete require.cache[require.resolve(module)]; } var handler; try { handler = require(module); } catch (err) { handleErr(err, response); return; } var isBase64 = request.body && !(request.headers["content-type"] || "").match( /text|application|multipart\/form-data/ ); var lambdaRequest = { path: request.path, httpMethod: request.method, queryStringParameters: queryString.parse(request.url.split(/\?(.+)/)[1]), headers: request.headers, body: isBase64 ? Buffer.from(request.body.toString(), "utf8").toString("base64") : request.body, isBase64Encoded: isBase64 }; var callback = createCallback(response); var promise = handler.handler( lambdaRequest, { clientContext: buildClientContext(request.headers) || {} }, callback ); var invocationTimeoutRef = null; Promise.race([ promiseCallback(promise, callback), new Promise(function(resolve) { invocationTimeoutRef = setTimeout(function() { handleInvocationTimeout(response, timeout); resolve(); }, timeout * 1000); }) ]).then( result => { clearTimeout(invocationTimeoutRef); return result; // not used, but writing this to avoid future footguns }, err => { clearTimeout(invocationTimeoutRef); throw err; } ); }; } exports.listen = function(port, static, timeout) { var config = conf.load(); var app = express(); var dir = config.build.functions || config.build.Functions; app.use(bodyParser.raw({ limit: "6mb" })); app.use(bodyParser.text({ limit: "6mb", type: "*/*" })); app.use( expressLogging(console, { blacklist: ["/favicon.ico"] }) ); app.get("/favicon.ico", function(req, res) { res.status(204).end(); }); app.get("/", function(req, res) { res .status(404) .send( `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.` ); }); app.all("*", createHandler(dir, static, timeout)); app.listen(port, function(err) { if (err) { console.error("Unable to start lambda server: ", err); process.exit(1); } console.log(`Lambda server is listening on ${port}`); }); return { clearCache: function(chunk) { var module = path.join(process.cwd(), dir, chunk); delete require.cache[require.resolve(module)]; } }; };