许多开发人员至少对 AWS Lambda 函数有所了解。它们设置起来相当简单,但广阔的 AWS 环境可能难以看到全局。由于有很多不同的部分,这可能令人生畏,并且令人沮丧地难以看到它们如何无缝地融入到普通的 Web 应用程序中。
Serverless 框架在这里提供了巨大的帮助。它简化了 Lambda 函数的创建、部署以及最重要的,将其集成到 Web 应用程序中。需要澄清的是,它不仅限于此,但这些是我将重点关注的部分。希望这篇文章能激发您的兴趣,并鼓励您查看 Serverless 支持的许多其他内容。如果您完全不熟悉 Lambda,您可能需要先查看 AWS 入门指南。
我无法比 快速入门指南 更好地介绍初始安装和设置,所以从那里开始,让您快速上手。假设您已经拥有 AWS 帐户,您可能在 5-10 分钟内就能运行起来;如果您没有,该指南也涵盖了这一点。
您的第一个 Serverless 服务
在我们开始处理文件上传和 S3 存储桶等酷炫的东西之前,让我们创建一个基本的 Lambda 函数,将其连接到 HTTP 端点,并从现有的 Web 应用程序中调用它。该 Lambda 不会执行任何有用的或有趣的操作,但这将为我们提供一个很好的机会来了解使用 Serverless 的便利性。
首先,让我们创建我们的服务。打开任何新的或您可能拥有的现有 Web 应用程序(create-react-app 是快速启动新应用程序的好方法)并找到创建我们的服务的位置。对我来说,这是我的 lambda
文件夹。无论您选择哪个目录,从终端中 cd
到该目录并运行以下命令
sls create -t aws-nodejs --path hello-world
这将创建一个名为 hello-world
的新目录。让我们打开它并查看里面有什么。

如果您查看 handler.js,您应该会看到一个返回消息的异步函数。我们现在可以在终端中运行 sls deploy
来部署该 Lambda 函数,然后可以调用它。但在我们这样做之前,让我们使其可通过 Web 调用。
手动使用 AWS 时,我们通常需要进入 AWS API Gateway,创建端点,然后创建阶段,并告诉它代理到我们的 Lambda。使用 Serverless,我们只需要一些配置。
仍然在 hello-world
目录中?打开在其中创建的 serverless.yaml 文件。

配置文件实际上带有最常见设置的样板代码。让我们取消对 http
条目的注释,并添加更合理的路径。类似于以下代码
functions:
hello:
handler: handler.hello
# The following are a few example events you can configure
# NOTE: Please make sure to change your handler code to work with those events
# Check the event documentation for details
events:
- http:
path: msg
method: get
就这样。Serverless 完成了上面描述的所有繁琐工作。
CORS 配置
理想情况下,我们希望使用前端 JavaScript 代码中的 Fetch API 来调用它,但这不幸地意味着我们需要配置 CORS。本节将引导您完成这一过程。
在上面的配置下方,添加 cors: true
,如下所示
functions:
hello:
handler: handler.hello
events:
- http:
path: msg
method: get
cors: true
就是这一部分!CORS 现在已在我们的 API 端点上配置,允许跨域通信。
CORS Lambda 调整
虽然我们的 HTTP 端点已配置为 CORS,但我们的 Lambda 需要返回正确的标头。这就是 CORS 的工作原理。让我们通过返回 handler.js 并添加以下函数来自动执行此操作
const CorsResponse = obj => ({
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "*"
},
body: JSON.stringify(obj)
});
在从 Lambda 返回之前,我们将通过该函数发送返回值。以下是包含我们到目前为止所做的所有操作的 handler.js 的完整内容
'use strict';
const CorsResponse = obj => ({
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "*"
},
body: JSON.stringify(obj)
});
module.exports.hello = async event => {
return CorsResponse("HELLO, WORLD!");
};
让我们运行它。在您的终端中,从 hello-world
文件夹中键入 sls deploy
。
运行完成后,我们将把 Lambda 函数部署到一个 HTTP 端点,我们可以通过 Fetch 调用它。但是,它在哪里?我们可以打开 AWS 控制台,找到 Serverless 为我们创建的网关 API,然后找到调用 URL。它看起来类似于以下代码。

幸运的是,有一种更简单的方法,那就是在终端中键入 sls info

就这样,我们可以看到我们的 Lambda 函数在以下路径可用
https://6xpmc3g0ch.execute-api.us-east-1.amazonaws.com/dev/ms
太棒了,现在让我们调用它!
现在让我们打开一个 Web 应用程序,并尝试获取它。以下是 Fetch 的样子
fetch("https://6xpmc3g0ch.execute-api.us-east-1.amazonaws.com/dev/msg")
.then(resp => resp.json())
.then(resp => {
console.log(resp);
});
我们应该在开发者控制台中看到我们的消息。

现在我们已经熟悉了,让我们重复此过程。不过,这次让我们创建一个更有意思、更有用的服务。具体来说,让我们创建典型的“调整图像大小”Lambda,但不是通过新的 S3 存储桶上传触发,而是让用户直接将图像上传到我们的 Lambda。这样就无需在客户端捆绑包中捆绑任何类型的 aws-sdk
资源。
构建一个有用的 Lambda
好的,从头开始!这个特定的 Lambda 将获取一个图像,调整其大小,然后将其上传到 S3 存储桶。首先,让我们创建一个新的服务。我将其称为 cover-art
,但它可以是任何其他名称。
sls create -t aws-nodejs --path cover-art
和之前一样,我们将添加一个到 HTTP 端点的路径(在这种情况下,它将是 POST,而不是 GET,因为我们正在发送文件,而不是接收它),并启用 CORS
// Same as before
events:
- http:
path: upload
method: post
cors: true
接下来,让我们授予我们的 Lambda 访问我们将在上传过程中使用的任何 S3 存储桶的权限。查看您的 YAML 文件,应该有一个 iamRoleStatements
部分,其中包含已注释掉的样板代码。我们可以通过取消注释它来利用其中的一些代码。以下是我们用于启用所需 S3 存储桶的配置
iamRoleStatements:
- Effect: "Allow"
Action:
- "s3:*"
Resource: ["arn:aws:s3:::your-bucket-name/*"]
注意结尾处的 /*
。我们不会单独列出特定的存储桶名称,而是列出资源的路径;在这种情况下,是 your-bucket-name
中可能存在的任何资源。
由于我们希望直接将文件上传到我们的 Lambda,因此我们需要进行一项额外的调整。具体来说,我们需要将 API 端点配置为接受 multipart/form-data
作为二进制媒体类型。在 YAML 文件中找到 provider
部分
provider:
name: aws
runtime: nodejs12.x
…并将其修改为
provider:
name: aws
runtime: nodejs12.x
apiGateway:
binaryMediaTypes:
- 'multipart/form-data'
为了保险起见,让我们给我们的函数一个智能的名称。将 handler: handler.hello
替换为 handler: handler.upload
,然后将 handler.js 中的 module.exports.hello
更改为 module.exports.upload
。
现在我们可以编写一些代码了
首先,让我们获取一些辅助程序。
npm i jimp uuid lambda-multipart-parser
等等,什么是 Jimp?它是我用来调整上传图像大小的库。uuid 用于在上传到 S3 之前创建新的、唯一的已调整大小资源文件名。哦,还有 lambda-multipart-parser
?它用于解析我们 Lambda 中的文件信息。
接下来,让我们为 S3 上传创建一个方便的辅助程序
const uploadToS3 = (fileName, body) => {
const s3 = new S3({});
const params = { Bucket: "your-bucket-name", Key: `/${fileName}`, Body: body };
return new Promise(res => {
s3.upload(params, function(err, data) {
if (err) {
return res(CorsResponse({ error: true, message: err }));
}
res(CorsResponse({
success: true,
url: `https://${params.Bucket}.s3.amazonaws.com/${params.Key}`
}));
});
});
};
最后,我们将插入一些代码,这些代码将读取上传的文件,使用 Jimp(如果需要)调整它们的大小,并将结果上传到 S3。最终结果如下所示。
'use strict';
const AWS = require("aws-sdk");
const { S3 } = AWS;
const path = require("path");
const Jimp = require("jimp");
const uuid = require("uuid/v4");
const awsMultiPartParser = require("lambda-multipart-parser");
const CorsResponse = obj => ({
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "*"
},
body: JSON.stringify(obj)
});
const uploadToS3 = (fileName, body) => {
const s3 = new S3({});
var params = { Bucket: "your-bucket-name", Key: `/${fileName}`, Body: body };
return new Promise(res => {
s3.upload(params, function(err, data) {
if (err) {
return res(CorsResponse({ error: true, message: err }));
}
res(CorsResponse({
success: true,
url: `https://${params.Bucket}.s3.amazonaws.com/${params.Key}`
}));
});
});
};
module.exports.upload = async event => {
const formPayload = await awsMultiPartParser.parse(event);
const MAX_WIDTH = 50;
return new Promise(res => {
Jimp.read(formPayload.files[0].content, function(err, image) {
if (err || !image) {
return res(CorsResponse({ error: true, message: err }));
}
const newName = `${uuid()}${path.extname(formPayload.files[0].filename)}`;
if (image.bitmap.width > MAX_WIDTH) {
image.resize(MAX_WIDTH, Jimp.AUTO);
image.getBuffer(image.getMIME(), (err, body) => {
if (err) {
return res(CorsResponse({ error: true, message: err }));
}
return res(uploadToS3(newName, body));
});
} else {
image.getBuffer(image.getMIME(), (err, body) => {
if (err) {
return res(CorsResponse({ error: true, message: err }));
}
return res(uploadToS3(newName, body));
});
}
});
});
};
抱歉给您发布了这么多代码,但是,这是一篇关于 Amazon Lambda 和无服务器的文章,我宁愿不详细说明无服务器函数中的繁琐工作。当然,如果您使用的是 Jimp 以外的图像库,您的代码可能看起来完全不同。
让我们通过从我们的客户端上传文件来运行它。我使用的是 react-dropzone 库,所以我的 JSX 如下所示
<Dropzone
onDrop={files => onDrop(files)}
multiple={false}
>
<div>Click or drag to upload a new cover</div>
</Dropzone>
onDrop
函数如下所示
const onDrop = files => {
let request = new FormData();
request.append("fileUploaded", files[0]);
fetch("https://yb1ihnzpy8.execute-api.us-east-1.amazonaws.com/dev/upload", {
method: "POST",
mode: "cors",
body: request
})
.then(resp => resp.json())
.then(res => {
if (res.error) {
// handle errors
} else {
// success - woo hoo - update state as needed
}
});
};
就这样,我们可以上传文件,并看到它出现在我们的 S3 存储桶中!

可选的绕行:捆绑
我们可以在设置中进行一项可选的增强。目前,当我们部署服务时,Serverless 会将整个服务文件夹压缩并发送到我们的 Lambda。当前内容重达 10MB,因为所有 node_modules
都被拖进了 Lambda。我们可以使用捆绑器来大幅减少这个大小。不仅如此,捆绑器还可以缩短部署时间、减少数据使用量、改善冷启动性能等等。换句话说,这是一个不错的选择。
幸运的是,有一个插件可以轻松地将 webpack 集成到 serverless 构建过程中。让我们用以下命令安装它:
npm i serverless-webpack --save-dev
…并通过我们的 YAML 配置文件添加它。我们可以在最后添加它
// Same as before
plugins:
- serverless-webpack
当然,我们需要一个 webpack.config.js 文件,所以让我们把它添加到 mix 中
const path = require("path");
module.exports = {
entry: "./handler.js",
output: {
libraryTarget: 'commonjs2',
path: path.join(__dirname, '.webpack'),
filename: 'handler.js',
},
target: "node",
mode: "production",
externals: ["aws-sdk"],
resolve: {
mainFields: ["main"]
}
};
注意,我们正在设置 target: node
,以便 Node 特定的资产能够得到正确的处理。还要注意,您可能需要将输出文件名设置为 handler.js
。我还将 aws-sdk
添加到 externals 数组中,这样 webpack 就不再捆绑它;相反,它会保留对 const AWS = require("aws-sdk");
的调用,让它在运行时由我们的 Lamdba 处理。这是可以的,因为 Lambda 已经隐式地提供了 aws-sdk
,这意味着我们不需要将它发送到网络中。最后,mainFields: ["main"]
是为了告诉 webpack 忽略任何 ESM module
字段。这对于解决 Jimp 库的一些问题是必要的。
现在让我们重新部署,希望我们能看到 webpack 运行。

现在我们的代码被很好地捆绑到一个 935K 的单个文件中,压缩后仅为 337K。节省了大量空间!
杂项
如果您想知道如何将其他数据发送到 Lambda,您可以将想要发送的内容添加到请求对象中,该对象的类型为 FormData
,来自之前。例如
request.append("xyz", "Hi there");
…然后在 Lambda 中读取 formPayload.xyz
。如果您需要发送安全令牌或其他文件信息,这将很有用。
如果您想知道如何为您的 Lambda 配置环境变量,您可能已经猜到,这与向您的 serverless.yaml 文件添加一些字段一样简单。它甚至支持从外部文件(可能未提交到 git)中读取值。这篇博文 由 Philipp Müns 很好地介绍了它。
总结
Serverless 是一个令人难以置信的框架。我保证,我们只是触及了皮毛。希望这篇文章向您展示了它的潜力,并激励您进一步探索它。
如果您有兴趣了解更多信息,我建议您阅读来自 Netlify 工程师 David Wells 的学习资料,他也是 serverless 团队的前成员,以及由 Swizec Teller 编写的 Serverless 手册
- Serverless 工作坊: 一个学习 serverless 核心概念的仓库
- Serverless 身份验证策略: 一个介绍不同函数访问授权策略的仓库
- Netlify Functions 工作坊: Netlify 关于使用 serverless 函数核心概念的课程
- Serverless 手册: 开始使用 serverless 技术
总体来说这是一篇很棒的文章,因为我对 serverless 框架还很陌生。是否有可以比较的 git 仓库,特别是针对 dropzone 代码。提前感谢。