Serverless
使用现有的 Fastify 应用运行无服务器应用和 REST API。默认情况下,Fastify 无法在你选择的无服务器平台上运行,你需要进行一些小的更改来解决此问题。本文档包含有关最流行的无服务器提供商以及如何使用 Fastify 的小指南。
¥Run serverless applications and REST APIs using your existing Fastify application. By default, Fastify will not work on your serverless platform of choice, you will need to make some small changes to fix this. This document contains a small guide for the most popular serverless providers and how to use Fastify with them.
你应该在无服务器平台中使用 Fastify 吗?
¥Should you use Fastify in a serverless platform?
那取决于你!请记住,函数即服务应始终使用小型且集中的函数,但你也可以使用它们运行整个 Web 应用。重要的是要记住,应用越大,初始启动速度就越慢。在无服务器环境中运行 Fastify 应用的最佳方式是使用 Google Cloud Run、AWS Fargate 和 Azure Container Instances 等平台,其中服务器可以同时处理多个请求并充分利用 Fastify 的功能。
¥That is up to you! Keep in mind that functions as a service should always use small and focused functions, but you can also run an entire web application with them. It is important to remember that the bigger the application the slower the initial boot will be. The best way to run Fastify applications in serverless environments is to use platforms like Google Cloud Run, AWS Fargate, and Azure Container Instances, where the server can handle multiple requests at the same time and make full use of Fastify's features.
在无服务器应用中使用 Fastify 的最佳功能之一是易于开发。在你的本地环境中,你将始终直接运行 Fastify 应用,无需任何其他工具,而相同的代码将通过额外的代码片段在你选择的无服务器平台中执行。
¥One of the best features of using Fastify in serverless applications is the ease of development. In your local environment, you will always run the Fastify application directly without the need for any additional tools, while the same code will be executed in your serverless platform of choice with an additional snippet of code.
目录
¥Contents
AWS
要与 AWS 集成,你有两种库选择:
¥To integrate with AWS, you have two choices of library:
使用 @fastify/aws-lambda 仅添加 API 网关支持,但对 fastify 进行了大量优化。
¥Using @fastify/aws-lambda which only adds API Gateway support but has heavy optimizations for fastify.
使用 @h4ad/serverless-adapter 速度稍慢,因为它会为每个 AWS 事件创建 HTTP 请求,但支持更多 AWS 服务,例如:AWS SQS、AWS SNS 等。
¥Using @h4ad/serverless-adapter which is a little slower as it creates an HTTP request for each AWS event but has support for more AWS services such as: AWS SQS, AWS SNS and others.
因此,你可以决定哪个选项最适合你,但你可以测试这两个库。
¥So you can decide which option is best for you, but you can test both libraries.
使用 @fastify/aws-lambda
¥Using @fastify/aws-lambda
提供的示例允许你在 AWS Lambda 和 Amazon API Gateway 之上使用 Fastify 轻松构建无服务器 Web 应用/服务和 RESTful API。
¥The sample provided allows you to easily build serverless web applications/services and RESTful APIs using Fastify on top of AWS Lambda and Amazon API Gateway.
app.js
const fastify = require('fastify');
function init() {
const app = fastify();
app.get('/', (request, reply) => reply.send({ hello: 'world' }));
return app;
}
if (require.main === module) {
// called directly i.e. "node app"
init().listen({ port: 3000 }, (err) => {
if (err) console.error(err);
console.log('server listening on 3000');
});
} else {
// required as a module => executed on aws lambda
module.exports = init;
}
当在 lambda 函数中执行时,我们不需要监听特定端口,因此在这种情况下我们只需导出封装函数 init
。lambda.js
文件将使用此导出。
¥When executed in your lambda function we do not need to listen to a specific
port, so we just export the wrapper function init
in this case. The
lambda.js
file will use this export.
当你像往常一样执行 Fastify 应用时,即 node app.js
(对此的检测可能是 require.main === module
),你通常可以监听你的端口,因此你仍然可以在本地运行 Fastify 函数。
¥When you execute your Fastify application like always, i.e. node app.js
(the
detection for this could be require.main === module
), you can normally listen
to your port, so you can still run your Fastify function locally.
lambda.js
const awsLambdaFastify = require('@fastify/aws-lambda')
const init = require('./app');
const proxy = awsLambdaFastify(init())
// or
// const proxy = awsLambdaFastify(init(), { binaryMimeTypes: ['application/octet-stream'] })
exports.handler = proxy;
// or
// exports.handler = (event, context, callback) => proxy(event, context, callback);
// or
// exports.handler = (event, context) => proxy(event, context);
// or
// exports.handler = async (event, context) => proxy(event, context);
我们只需要 @fastify/aws-lambda(确保你安装了依赖 npm i @fastify/aws-lambda
)和我们的 app.js
文件,并以 app
作为唯一参数调用导出的 awsLambdaFastify
函数。生成的 proxy
函数具有正确的签名,可用作 lambda handler
函数。这样,所有传入事件(API 网关请求)都会传递给 @fastify/aws-lambda 的 proxy
函数。
¥We just require
@fastify/aws-lambda (make sure
you install the dependency npm i @fastify/aws-lambda
) and our
app.js
file and call the exported awsLambdaFastify
function with
the app
as the only parameter. The resulting proxy
function has the correct
signature to be used as a lambda handler
function. This way all the incoming
events (API Gateway requests) are passed to the proxy
function of
@fastify/aws-lambda.
示例
¥Example
可以找到一个可以使用 claudia.js 部署的示例 此处。
¥An example deployable with claudia.js can be found here.
注意事项
¥Considerations
API Gateway 尚不支持流,因此你无法处理 streams。
¥API Gateway does not support streams yet, so you are not able to handle streams.
API Gateway 的超时时间为 29 秒,因此在此期间提供响应非常重要。
¥API Gateway has a timeout of 29 seconds, so it is important to provide a reply during this time.
超越 API 网关
¥Beyond API Gateway
如果你需要与更多 AWS 服务集成,请查看 Fastify 上的 @h4ad/serverless-adapter 以了解如何集成。
¥If you need to integrate with more AWS services, take a look at @h4ad/serverless-adapter on Fastify to find out how to integrate.
谷歌云函数
¥Google Cloud Functions
创建 Fastify 实例
¥Creation of Fastify instance
const fastify = require("fastify")({
logger: true // you can also define the level passing an object configuration to logger: {level: 'debug'}
});
将自定义 contentTypeParser
添加到 Fastify 实例
¥Add Custom contentTypeParser
to Fastify instance
如 问题 #946 所述,由于 Google Cloud Functions 平台在请求到达 Fastify 实例之前会解析请求的主体,因此在使用 POST
和 PATCH
方法的情况下,主体请求会受到影响,因此你需要添加自定义 Content-Type Parser
来缓解此行为。
¥As explained in issue
#946,
since the Google Cloud Functions platform parses the body of the request before
it arrives at the Fastify instance, troubling the body request in case of POST
and PATCH
methods, you need to add a custom Content-Type
Parser
to mitigate this behavior.
fastify.addContentTypeParser('application/json', {}, (req, body, done) => {
done(null, body.body);
});
定义你的端点(示例)
¥Define your endpoint (examples)
一个简单的 GET
端点:
¥A simple GET
endpoint:
fastify.get('/', async (request, reply) => {
reply.send({message: 'Hello World!'})
})
或者更完整的带有架构验证的 POST
端点:
¥Or a more complete POST
endpoint with schema validation:
fastify.route({
method: 'POST',
url: '/hello',
schema: {
body: {
type: 'object',
properties: {
name: { type: 'string'}
},
required: ['name']
},
response: {
200: {
type: 'object',
properties: {
message: {type: 'string'}
}
}
},
},
handler: async (request, reply) => {
const { name } = request.body;
reply.code(200).send({
message: `Hello ${name}!`
})
}
})
实现并导出函数
¥Implement and export the function
最后一步,实现处理请求的函数并通过向 fastify.server
发出 request
事件将其传递给 Fastify:
¥Final step, implement the function to handle the request and pass it to Fastify
by emitting request
event to fastify.server
:
const fastifyFunction = async (request, reply) => {
await fastify.ready();
fastify.server.emit('request', request, reply)
}
exports.fastifyFunction = fastifyFunction;
本地测试
¥Local test
¥Install Google Functions Framework for Node.js.
你可以全局安装它:
¥You can install it globally:
npm i -g @google-cloud/functions-framework
或者作为开发库:
¥Or as a development library:
npm i -D @google-cloud/functions-framework
然后你可以使用 Functions Framework 在本地运行你的函数:
¥Then you can run your function locally with Functions Framework:
npx @google-cloud/functions-framework --target=fastifyFunction
或者将此命令添加到你的 package.json
脚本:
¥Or add this command to your package.json
scripts:
"scripts": {
...
"dev": "npx @google-cloud/functions-framework --target=fastifyFunction"
...
}
并使用 npm run dev
运行它。
¥and run it with npm run dev
.
部署
¥Deploy
gcloud functions deploy fastifyFunction \
--runtime nodejs14 --trigger-http --region $GOOGLE_REGION --allow-unauthenticated
读取日志
¥Read logs
gcloud functions logs read
对 /hello
端点的请求示例
¥Example request to /hello
endpoint
curl -X POST https://$GOOGLE_REGION-$GOOGLE_PROJECT.cloudfunctions.net/me \
-H "Content-Type: application/json" \
-d '{ "name": "Fastify" }'
{"message":"Hello Fastify!"}
参考
¥References
Google Firebase 函数
¥Google Firebase Functions
如果你想使用 Fastify 作为 Firebase Functions 的 HTTP 框架,而不是 onRequest(async (req, res) => {}
提供的 vanilla JavaScript 路由,请遵循本指南。
¥Follow this guide if you want to use Fastify as the HTTP framework for
Firebase Functions instead of the vanilla JavaScript router provided with
onRequest(async (req, res) => {}
.
onRequest() 处理程序
¥The onRequest() handler
我们使用 onRequest
函数封装我们的 Fastify 应用实例。
¥We use the onRequest
function to wrap our Fastify application instance.
因此,我们将从将其导入代码开始:
¥As such, we'll begin with importing it to the code:
const { onRequest } = require("firebase-functions/v2/https")
创建 Fastify 实例
¥Creation of Fastify instance
创建 Fastify 实例并将返回的应用实例封装在一个函数中,该函数将注册路由,等待服务器处理插件、钩子和其他设置。如下所示:
¥Create the Fastify instance and encapsulate the returned application instance in a function which will register routes, await the server's processing of plugins, hooks and other settings. As follows:
const fastify = require("fastify")({
logger: true,
})
const fastifyApp = async (request, reply) => {
await registerRoutes(fastify)
await fastify.ready()
fastify.server.emit("request", request, reply)
}
将自定义 contentTypeParser
添加到 Fastify 实例并定义端点
¥Add Custom contentTypeParser
to Fastify instance and define endpoints
Firebase Function 的 HTTP 层已经解析了请求并提供 JSON 有效负载。它还提供对未解析的原始主体的访问,这对于计算请求签名以验证 HTTP webhook 很有用。
¥Firebase Function's HTTP layer already parses the request and makes a JSON payload available. It also provides access to the raw body, unparsed, which is useful in order to calculate request signatures to validate HTTP webhooks.
将以下内容添加到 registerRoutes()
函数:
¥Add as follows to the registerRoutes()
function:
async function registerRoutes (fastify) {
fastify.addContentTypeParser("application/json", {}, (req, payload, done) => {
// useful to include the request's raw body on the `req` object that will
// later be available in your other routes so you can calculate the HMAC
// if needed
req.rawBody = payload.rawBody
// payload.body is already the parsed JSON so we just fire the done callback
// with it
done(null, payload.body)
})
// define your endpoints here...
fastify.post("/some-route-here", async (request, reply) => {}
fastify.get('/', async (request, reply) => {
reply.send({message: 'Hello World!'})
})
}
使用 Firebase onRequest 导出函数
¥Export the function using Firebase onRequest
最后一步是将 Fastify 应用实例导出到 Firebase 自己的 onRequest()
函数,以便它可以将请求和回复对象传递给它:
¥Final step is to export the Fastify app instance to Firebase's own
onRequest()
function so it can pass the request and reply objects to it:
exports.app = onRequest(fastifyApp)
本地测试
¥Local test
安装 Firebase 工具功能,以便你可以使用 CLI:
¥Install the Firebase tools functions so you can use the CLI:
npm i -g firebase-tools
然后你可以使用以下方式在本地运行你的函数:
¥Then you can run your function locally with:
firebase emulators:start --only functions
部署
¥Deploy
使用以下方式部署你的 Firebase 函数:
¥Deploy your Firebase Functions with:
firebase deploy --only functions
读取日志
¥Read logs
使用 Firebase 工具 CLI:
¥Use the Firebase tools CLI:
firebase functions:log
参考
¥References
谷歌云运行
¥Google Cloud Run
与 AWS Lambda 或 Google Cloud Functions 不同,Google Cloud Run 是一个无服务器容器环境。其主要目的是提供一个基础设施抽象环境来运行任意容器。因此,Fastify 可以部署到 Google Cloud Run,与通常编写 Fastify 应用的方式相比,几乎不需要任何代码更改。
¥Unlike AWS Lambda or Google Cloud Functions, Google Cloud Run is a serverless container environment. Its primary purpose is to provide an infrastructure-abstracted environment to run arbitrary containers. As a result, Fastify can be deployed to Google Cloud Run with little-to-no code changes from the way you would write your Fastify app normally.
如果你已经熟悉 gcloud,请按照以下步骤部署到 Google Cloud Run,或者只需遵循他们的 quickstart。
¥Follow the steps below to deploy to Google Cloud Run if you are already familiar with gcloud or just follow their quickstart.
调整 Fastify 服务器
¥Adjust Fastify server
为了让 Fastify 正确监听容器内的请求,请务必设置正确的端口和地址:
¥In order for Fastify to properly listen for requests within the container, be sure to set the correct port and address:
function build() {
const fastify = Fastify({ trustProxy: true })
return fastify
}
async function start() {
// Google Cloud Run will set this environment variable for you, so
// you can also use it to detect if you are running in Cloud Run
const IS_GOOGLE_CLOUD_RUN = process.env.K_SERVICE !== undefined
// You must listen on the port Cloud Run provides
const port = process.env.PORT || 3000
// You must listen on all IPV4 addresses in Cloud Run
const host = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined
try {
const server = build()
const address = await server.listen({ port, host })
console.log(`Listening on ${address}`)
} catch (err) {
console.error(err)
process.exit(1)
}
}
module.exports = build
if (require.main === module) {
start()
}
添加 Dockerfile
¥Add a Dockerfile
你可以添加任何打包并运行 Node 应用的有效 Dockerfile
。可以在官方 gcloud 文档 中找到基本的 Dockerfile
。
¥You can add any valid Dockerfile
that packages and runs a Node app. A basic
Dockerfile
can be found in the official gcloud
docs.
# Use the official Node.js 10 image.
# https://hub.docker.com/_/node
FROM node:10
# Create and change to the app directory.
WORKDIR /usr/src/app
# Copy application dependency manifests to the container image.
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
# Copying this separately prevents re-running npm install on every code change.
COPY package*.json ./
# Install production dependencies.
RUN npm i --production
# Copy local code to the container image.
COPY . .
# Run the web service on container startup.
CMD [ "npm", "start" ]
添加.dockerignore
¥Add a .dockerignore
为将构建工件保持在容器之外(这可使其保持较小并缩短构建时间),请添加如下所示的 .dockerignore
文件:
¥To keep build artifacts out of your container (which keeps it small and improves
build times) add a .dockerignore
file like the one below:
Dockerfile
README.md
node_modules
npm-debug.log
提交构建
¥Submit build
接下来,通过运行以下命令提交你的应用以构建到 Docker 映像中(将 PROJECT-ID
和 APP-NAME
替换为你的 GCP 项目 ID 和应用名称):
¥Next, submit your app to be built into a Docker image by running the following
command (replacing PROJECT-ID
and APP-NAME
with your GCP project id and an
app name):
gcloud builds submit --tag gcr.io/PROJECT-ID/APP-NAME
部署镜像
¥Deploy Image
构建镜像后,你可以使用以下命令部署它:
¥After your image has built, you can deploy it with the following command:
gcloud beta run deploy --image gcr.io/PROJECT-ID/APP-NAME --platform managed
你可以通过 GCP 提供的网址访问你的应用。
¥Your app will be accessible from the URL GCP provides.
netlify-lambda
首先,请执行与 AWS Lambda 相关的所有准备步骤。
¥First, please perform all preparation steps related to AWS Lambda.
创建一个名为 functions
的文件夹,然后在 functions
文件夹内创建 server.js
(你的端点路径将是 server.js
)。
¥Create a folder called functions
, then create server.js
(and your endpoint
path will be server.js
) inside the functions
folder.
functions/server.js
export { handler } from '../lambda.js'; // Change `lambda.js` path to your `lambda.js` path
netlify.toml
[build]
# This will be run the site build
command = "npm run build:functions"
# This is the directory is publishing to netlify's CDN
# and this is directory of your front of your app
# publish = "build"
# functions build directory
functions = "functions-build" # always appends `-build` folder to your `functions` folder for builds
webpack.config.netlify.js
不要忘记添加这个 Webpack 配置,否则可能会出现问题
¥Do not forget to add this Webpack config, or else problems may occur
const nodeExternals = require('webpack-node-externals');
const dotenv = require('dotenv-safe');
const webpack = require('webpack');
const env = process.env.NODE_ENV || 'production';
const dev = env === 'development';
if (dev) {
dotenv.config({ allowEmptyValues: true });
}
module.exports = {
mode: env,
devtool: dev ? 'eval-source-map' : 'none',
externals: [nodeExternals()],
devServer: {
proxy: {
'/.netlify': {
target: 'http://localhost:9000',
pathRewrite: { '^/.netlify/functions': '' }
}
}
},
module: {
rules: []
},
plugins: [
new webpack.DefinePlugin({
'process.env.APP_ROOT_PATH': JSON.stringify('/'),
'process.env.NETLIFY_ENV': true,
'process.env.CONTEXT': env
})
]
};
脚本
¥Scripts
将此命令添加到你的 package.json
脚本
¥Add this command to your package.json
scripts
"scripts": {
...
"build:functions": "netlify-lambda build functions --config ./webpack.config.netlify.js"
...
}
那么它应该可以正常工作
¥Then it should work fine
Platformatic 云
¥Platformatic Cloud
Platformatic 为 Node.js 应用提供零配置部署。要立即使用它,你应该通过运行以下命令将现有的 Fastify 应用封装在 平台化服务 中:
¥Platformatic provides zero-configuration deployment for Node.js applications. To use it now, you should wrap your existing Fastify application inside a Platformatic Service, by running the following:
npm create platformatic@latest -- service
向导会要求你填写一些答案:
¥The wizard would ask you to fill in a few answers:
? Where would you like to create your project? .
? Do you want to run npm install? yes
? Do you want to use TypeScript? no
? What port do you want to use? 3042
[13:04:14] INFO: Configuration file platformatic.service.json successfully created.
[13:04:14] INFO: Environment file .env successfully created.
[13:04:14] INFO: Plugins folder "plugins" successfully created.
[13:04:14] INFO: Routes folder "routes" successfully created.
? Do you want to create the github action to deploy this application to Platformatic Cloud dynamic workspace? no
? Do you want to create the github action to deploy this application to Platformatic Cloud static workspace? no
然后,前往 Platformatic 云 并使用你的 GitHub 账户登录。创建你的第一个应用和静态工作区:请小心将 API 密钥下载为 env 文件,例如 yourworkspace.txt
。
¥Then, head to Platformatic Cloud and sign in
with your GitHub account.
Create your first application and a static workspace: be careful to download the
API key as an env file, e.g. yourworkspace.txt
.
然后,你可以使用以下命令轻松部署应用:
¥Then, you can easily deploy your application with the following command:
platformatic deploy --keys `yourworkspace.txt`
查看 完整指南 了解如何在 Platformatic 中封装 Fastify 应用。
¥Check out the Full Guide on how to wrap Fastify application in Platformatic.
Vercel
Vercel 为 Node.js 应用提供零配置部署。要立即使用它,只需像下面这样配置你的 vercel.json
文件即可:
¥Vercel provides zero-configuration deployment for Node.js
applications. To use it now, it is as simple as configuring your vercel.json
file like the following:
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/api/serverless.js"
}
]
}
然后,像这样编写 api/serverless.js
:
¥Then, write api/serverless.js
like so:
"use strict";
// Read the .env file.
import * as dotenv from "dotenv";
dotenv.config();
// Require the framework
import Fastify from "fastify";
// Instantiate Fastify with some config
const app = Fastify({
logger: true,
});
// Register your application as a normal plugin.
app.register(import("../src/app.js"));
export default async (req, res) => {
await app.ready();
app.server.emit('request', req, res);
}
在 src/app.js
中定义插件。
¥In src/app.js
define the plugin.
async function routes (fastify, options) {
fastify.get('/', async (request, reply) => {
return { hello: 'world' }
})
}
export default routes;