Node 服务端
要生成独立的 Node 服务端,请使用 adapter-node
。
使用方法
使用 npm i -D @sveltejs/adapter-node
安装,然后将适配器添加到您的 svelte.config.js
:
import import adapter
adapter from '@sveltejs/adapter-node';
export default {
kit: {
adapter: any;
}
kit: {
adapter: any
adapter: import adapter
adapter()
}
};
部署
首先,使用 npm run build
构建您的应用。这将在适配器选项中指定的输出目录(默认为 build
)中创建生产服务端。
要运行应用程序,您需要输出目录、项目的 package.json
和 node_modules
中的生产依赖项。生产依赖项可以通过复制 package.json
和 package-lock.json
然后运行 npm ci --omit dev
来生成(如果您的应用没有任何依赖项,可以跳过此步骤)。然后您可以使用以下命令启动您的应用:
node build
开发依赖项将使用 Rollup 打包到您的应用中。要控制某个包是打包还是外部化,请将其分别放在 package.json
的 devDependencies
或 dependencies
中。
压缩响应
通常您会希望压缩来自服务端的响应。如果您已经在为 SSL 或负载均衡部署了反向代理服务端,那么在该层处理压缩通常会带来更好的性能,因为 Node.js 是单线程的。
但是,如果您正在构建自定义服务端并确实想在那里添加压缩中间件,请注意我们建议使用 @polka/compression
,因为 SvelteKit 会流式传输响应,而更流行的 compression
包不支持流式传输,使用时可能会导致错误。
环境变量
在 dev
和 preview
模式下,SvelteKit 将从您的 .env
文件(或 .env.local
,或 .env.[mode]
,由 Vite 决定)中读取环境变量。
在生产环境中,不会自动加载 .env
文件。要做到这一点,请在您的项目中安装 dotenv
...
npm install dotenv
...并在运行构建的应用之前调用它:
node -r dotenv/config build
如果您使用的是 Node.js v20.6+,您可以使用 --env-file
标志代替:
node --env-file=.env build
PORT, HOST 和 SOCKET_PATH
默认情况下,服务端将使用 0.0.0.0
并在端口 3000 上接受连接。可以使用 PORT
和 HOST
环境变量对其进行自定义:
HOST=127.0.0.1 PORT=4000 node build
或者,还可以配置服务端在指定的 socket 路径上接受连接。如果您使用 SOCKET_PATH
环境变量来执行此操作,则会忽略 HOST
和 PORT
环境变量。
SOCKET_PATH=/tmp/socket node build
ORIGIN, PROTOCOL_HEADER, HOST_HEADER 和 PORT_HEADER
HTTP 并不会为 SvelteKit 提供一种可靠的方法来获取当前请求的 URL。最简单的方式是设置 ORIGIN
环境变量来告诉 SvelteKit 应用在哪里被提供服务:
ORIGIN=https://my.site node build
# 或者,例如本地预览和测试
ORIGIN=http://localhost:3000 node build
这样,当请求 /stuff
路径名时,就能正确解析到 https://my.site/stuff
。或者,您可以指定用于告诉 SvelteKit 关于请求协议和主机的标头,由此 SvelteKit 可以构建 origin URL:
PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host node build
x-forwarded-proto
和x-forwarded-host
是事实上的标准请求头,用于在使用反向代理(如负载均衡器和 CDN)时转发原始协议和主机。只有在您的服务端位于受信任的反向代理之后时,才应设置这些变量;否则,客户端可能会伪造这些标头。
如果您在非标准端口托管代理,并且您的反向代理支持x-forwarded-port
,您也可以设置PORT_HEADER=x-forwarded-port
。
如果 adapter-node
无法正确确定您的部署的 URL,在使用 表单 actions 时可能会出现以下错误:
Cross-site POST form submissions are forbidden
ADDRESS_HEADER 和 XFF_DEPTH
传递给 hooks 和端点的 RequestEvent 对象包含一个 event.getClientAddress()
函数,用于返回客户端的 IP 地址。默认情况下,这是发起连接的 remoteAddress
。如果您的服务器位于一个或多个代理(如负载均衡器)之后,这个值将包含最内部代理的 IP 地址,而不是客户端的 IP 地址,因此我们需要指定一个 ADDRESS_HEADER
读取地址:
ADDRESS_HEADER=True-Client-IP node build
标头很容易被伪造。与
PROTOCOL_HEADER
和HOST_HEADER
一样,只有在您了解相关风险的情况下才应设置这些变量。
如果 ADDRESS_HEADER
是 X-Forwarded-For
,其值会包含用逗号分隔的 IP 地址列表。此时应通过 XFF_DEPTH
环境变量指定在您的服务器前有多少个受信任的代理。例如,如果有三个受信任的代理,代理 3 会转发客户端原始连接和前两个代理的地址:
<client address>, <proxy 1 address>, <proxy 2 address>
有些指南会告诉您读取最左边的地址,但这样会容易被伪造:
<spoofed address>, <client address>, <proxy 1 address>, <proxy 2 address>
因此,我们从右侧读取,并依据受信任的代理数量进行处理。在这个示例里,我们会使用 XFF_DEPTH=3
。
如果您确实需要读取最左侧的地址(并且不在意被伪造)——例如提供地理位置服务,在此情况下,IP 地址“真实性”比“可信度”更重要,您可以在应用中自行检查
x-forwarded-for
标头来实现这一点。
BODY_SIZE_LIMIT
接受的最大请求体大小,以字节为单位(包括流式传输时)。
请求体大小也可以使用单位后缀指定,包括千字节(K)、兆字节(M)或千兆字节(G)。例如 512K
或 1M
。默认值为 512kb。
您可以设置值为 Infinity
(在适配器的旧版本中为 0)来禁用此选项,如果需要更高级的功能,可以在 handle
中自行实现更高级的检查逻辑。
SHUTDOWN_TIMEOUT
接收到 SIGTERM
或 SIGINT
信号后,在强制关闭任何剩余连接之前等待的秒数。默认值是 30
。在内部,适配器会调用 closeAllConnections
。更多细节请参见 优雅关闭。
IDLE_TIMEOUT
在使用 systemd 套接字激活时,IDLE_TIMEOUT
用于指定当应用在没有请求的情况下经过多少秒会自动休眠。如果未设置,则应用会一直运行。详见 套接字激活 获取更多信息。
Options
该适配器可以通过多种选项进行配置:
import import adapter
adapter from '@sveltejs/adapter-node';
export default {
kit: {
adapter: any;
}
kit: {
adapter: any
adapter: import adapter
adapter({
// 以下为默认选项
out: string
out: 'build',
precompress: boolean
precompress: true,
envPrefix: string
envPrefix: ''
})
}
};
out
构建服务端输出的目录,默认为 build
—— 也就是说,如果您使用默认目录,执行 node build
将在本地启动服务端。
precompress
使用 gzip 和 brotli 对资源和预渲染页面进行预压缩。默认值为 true
。
envPrefix
如果您需要更改用于配置部署的环境变量名称(例如,与您无法控制的环境变量冲突),可以指定一个前缀:
envPrefix: 'MY_CUSTOM_';
MY_CUSTOM_HOST=127.0.0.1 \
MY_CUSTOM_PORT=4000 \
MY_CUSTOM_ORIGIN=https://my.site \
node build
优雅关闭
默认情况下,当接收到 SIGTERM
或 SIGINT
信号时,adapter-node
会优雅地关闭 HTTP 服务器。它将:
- 拒绝新的请求(
server.close
) - 等待已经发出的请求但尚未收到响应的请求完成,并在连接变为空闲后关闭连接(
server.closeIdleConnections
) - 最后,在超过
SHUTDOWN_TIMEOUT
秒后强制关闭所有仍处于活动状态的连接(server.closeAllConnections
)。
如果您想自定义这一行为,您可以使用自定义服务端。
您还可以监听 sveltekit:shutdown
事件,该事件会在 HTTP 服务器关闭全部连接后触发。与 Node 的 exit
事件不同,sveltekit:shutdown
事件支持异步操作,并且无论服务器是否有未完成的任务(如未关闭的数据库连接),在所有连接都关闭后都会被触发:
var process: NodeJS.Process
process.NodeJS.Process.on(event: string | symbol, listener: (...args: any[]) => void): NodeJS.Process (+12 overloads)
Adds the listener
function to the end of the listeners array for the event
named eventName
. No checks are made to see if the listener
has already
been added. Multiple calls passing the same combination of eventName
and
listener
will result in the listener
being added, and called, multiple times.
server.on('connection', (stream) => {
console.log('someone connected!');
});
Returns a reference to the EventEmitter
, so that calls can be chained.
By default, event listeners are invoked in the order they are added. The emitter.prependListener()
method can be used as an alternative to add the
event listener to the beginning of the listeners array.
import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a
on('sveltekit:shutdown', async (reason: any
reason) => {
await jobs.stop();
await db.close();
});
参数 reason
的可能取值包括:
SIGINT
- 关机由SIGINT
信号触发SIGTERM
- 关机由SIGTERM
信号触发IDLE
- 关机由IDLE_TIMEOUT
触发
套接字激活
当今大多数 Linux 操作系统都使用名为 systemd 的现代进程管理器来启动、运行和管理服务。您可以配置服务器来分配一个套接字,并在需要时按需启动应用。这被称为 套接字激活。在这种情况下,操作系统会向您的应用传递两个环境变量:LISTEN_PID
和 LISTEN_FDS
。然后,适配器会在文件描述符 3 上进行监听,该描述符对应您创建的 systemd 套接字单元。
您仍然可以在 systemd 套接字激活中使用
envPrefix
。LISTEN_PID
和LISTEN_FDS
始终无需前缀即可读取。
要利用套接字激活,请按以下步骤操作:
- 让您的应用作为一个 systemd 服务 运行。它既可以直接运行在主机系统上,也可以在容器内(例如使用 Docker 或 systemd 可移植服务)运行。
如果您额外向应用传递一个 IDLE_TIMEOUT
环境变量,它将在没有请求持续 IDLE_TIMEOUT
秒时,优雅地关闭。之后如果有新的请求到来,systemd 将自动重新启动您的应用。
[Service]
Environment=NODE_ENV=production IDLE_TIMEOUT=60
ExecStart=/usr/bin/node /usr/bin/myapp/build
- 创建一个配套的 socket 单元。适配器仅接受单个 socket。
[Socket]
ListenStream=3000
[Install]
WantedBy=sockets.target
- 通过运行
sudo systemctl daemon-reload
确保 systemd 识别了这两个单元。然后使用sudo systemctl enable --now myapp.socket
在启动时启用该 socket 并立即启动它。这样当第一个请求到达localhost:3000
时,应用将自动启动。
自定义服务端
该适配器会在您的构建目录中创建两个文件——index.js
和 handler.js
。运行 index.js
(例如,如果您使用默认 build
目录,那么执行 node build
)将会在指定端口上启动服务器。
或者,您可以导入 handler.js
文件,它导出一个兼容 Express、Connect 或 Polka (甚至是内置的 http.createServer
)的处理程序,并且设置你自己的服务器:
import { import handler
handler } from './build/handler.js';
import import express
express from 'express';
const const app: any
app = import express
express();
// 添加一个独立于 SvelteKit 应用的路由
const app: any
app.get('/healthcheck', (req, res) => {
res: any
res.end('ok');
});
// 让 SvelteKit 处理其他所有内容,包括提供预渲染页面和静态资源
const app: any
app.use(import handler
handler);
const app: any
app.listen(3000, () => {
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object’s methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log('listening on port 3000');
});