Skip to content
This repository has been archived by the owner on Sep 12, 2019. It is now read-only.

Commit

Permalink
move serveFunctions into netlify dev plugin from ZISI
Browse files Browse the repository at this point in the history
  • Loading branch information
sw-yx committed Apr 16, 2019
1 parent 8b03110 commit 27da118
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 26 deletions.
53 changes: 29 additions & 24 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@
"@oclif/command": "^1",
"@oclif/config": "^1",
"ascii-table": "0.0.9",
"body-parser": "^1.18.3",
"boxen": "^3.0.0",
"chalk": "^2.4.2",
"chokidar": "^2.1.5",
"copy-template-dir": "^1.4.0",
"execa": "^1.0.0",
"express": "^4.16.4",
"express-logging": "^1.1.1",
"fs-extra": "^7.0.1",
"fuzzy": "^0.1.3",
"get-port": "^4.1.0",
"get-port": "^4.2.0",
"gh-release-fetch": "^1.0.3",
"http-proxy": "^1.17.0",
"inquirer": "^6.2.2",
Expand All @@ -27,6 +30,7 @@
"netlify-cli-logo": "^1.0.0",
"node-fetch": "^2.3.0",
"ora": "^3.4.0",
"querystring": "^0.2.0",
"safe-join": "^0.1.2",
"static-server": "^2.2.1",
"wait-port": "^0.2.2",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const httpProxy = require("http-proxy");
const waitPort = require("wait-port");
const getPort = require("get-port");
const chokidar = require("chokidar");
const { serveFunctions } = require("@netlify/zip-it-and-ship-it");
const { serveFunctions } = require("../../utils/serveFunctions");
const { serverSettings } = require("../../detect-server");
const { detectFunctionsBuilder } = require("../../detect-functions-builder");
const Command = require("@netlify/cli-utils");
Expand Down
214 changes: 214 additions & 0 deletions src/utils/serveFunctions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
const fs = require("fs");
const express = require("express");
const bodyParser = require("body-parser");
const expressLogging = require("express-logging");
const queryString = require("querystring");
const path = require("path");
const getPort = require("get-port");
const chokidar = require("chokidar");
const chalk = require("chalk");

const NETLIFYDEVLOG = `${chalk.greenBright("◈")}`;
const NETLIFYDEVWARN = `${chalk.yellowBright("◈")}`;
const NETLIFYDEVERR = `${chalk.redBright("◈")}`;

const { findModuleDir, findHandler } = require("./finders");

const defaultPort = 34567;

function handleErr(err, response) {
response.statusCode = 500;
response.write(
`${NETLIFYDEVERR} Function invocation failed: ` + err.toString()
);
response.end();
console.log(`${NETLIFYDEVERR} Error during invocation: `, err);
return;
}

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]);
}
response.write(
lambdaResponse.isBase64Encoded
? Buffer.from(lambdaResponse.body, "base64")
: lambdaResponse.body
);
response.end();
};
}

function promiseCallback(promise, callback) {
if (!promise) return;
if (typeof promise.then !== "function") return;
if (typeof callback !== "function") return;

promise.then(
function(data) {
callback(null, data);
},
function(err) {
callback(err, null);
}
);
}

function getHandlerPath(functionPath) {
if (functionPath.match(/\.js$/)) {
return functionPath;
}

return path.join(functionPath, `${path.basename(functionPath)}.js`);
}

function createHandler(dir, options) {
const functions = {};
fs.readdirSync(dir).forEach(file => {
if (dir === "node_modules") {
return;
}
const functionPath = path.resolve(path.join(dir, file));
const handlerPath = findHandler(functionPath);
if (!handlerPath) {
return;
}
if (path.extname(functionPath) === ".js") {
functions[file.replace(/\.js$/, "")] = {
functionPath,
moduleDir: findModuleDir(functionPath)
};
} else if (fs.lstatSync(functionPath).isDirectory()) {
functions[file] = {
functionPath: handlerPath,
moduleDir: findModuleDir(functionPath)
};
}
});

Object.keys(functions).forEach(name => {
const fn = functions[name];
const clearCache = () => {
const before = module.paths;
module.paths = [fn.moduleDir];
delete require.cache[require.resolve(fn.functionPath)];
module.paths = before;
};
fn.watcher = chokidar.watch(
[fn.functionPath, path.join(fn.moduleDir, "package.json")],
{
ignored: /node_modules/
}
);
fn.watcher
.on("add", clearCache)
.on("change", clearCache)
.on("unlink", clearCache);
});

return function(request, response) {
// handle proxies without path re-writes (http-servr)
const cleanPath = request.path.replace(/^\/.netlify\/functions/, "");

const func = cleanPath.split("/").filter(function(e) {
return e;
})[0];
if (!functions[func]) {
response.statusCode = 404;
response.end("Function not found...");
return;
}
const { functionPath, moduleDir } = functions[func];
let handler;
let before = module.paths;
try {
module.paths = [moduleDir];
handler = require(functionPath);
module.paths = before;
} catch (err) {
module.paths = before;
handleErr(err, response);
return;
}

const isBase64 =
request.body &&
!(request.headers["content-type"] || "").match(
/text|application|multipart\/form-data/
);
const 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
};

const callback = createCallback(response);
const promise = handler.handler(lambdaRequest, {}, callback);
promiseCallback(promise, callback);
};
}

async function serveFunctions(settings, options) {
options = options || {};
const app = express();
const dir = settings.functionsDir;
const port = await getPort({
port: assignLoudly(settings.port, defaultPort)
});

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.all("*", createHandler(dir, options));

app.listen(port, function(err) {
if (err) {
console.error(`${NETLIFYDEVERR} Unable to start lambda server: `, err);
process.exit(1);
}

// add newline because this often appears alongside the client devserver's output
console.log(`\n${NETLIFYDEVLOG} Lambda server is listening on ${port}`);
});

return Promise.resolve({
port
});
}

module.exports = { serveFunctions };

// if first arg is undefined, use default, but tell user about it in case it is unintentional
function assignLoudly(
optionalValue,
fallbackValue,
tellUser = dV =>
console.log(`${NETLIFYDEVLOG} No port specified, using defaultPort of `, dV)
) {
if (fallbackValue === undefined) throw new Error("must have a fallbackValue");
if (fallbackValue !== optionalValue && optionalValue === undefined) {
tellUser(fallbackValue);
return fallbackValue;
} else {
return optionalValue;
}
}

0 comments on commit 27da118

Please sign in to comment.