mirror of
https://github.com/nestjs/nest.git
synced 2026-10-28 09:45:43 +08:00
fix(fastify): listen on a file descriptor (#18226)
This commit is contained in:
1 parent
237cab29d9
commit
fe2faa0ab8
7 files changed
+114
-8
No files matched your search
@@ -0,0 +1,52 @@
|
||||
import { ChildProcess, spawn } from 'child_process';
|
||||
import { AddressInfo, createServer } from 'net';
|
||||
import { join } from 'path';
|
||||
|
||||
const waitUntilListening = (child: ChildProcess) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
let stderr = '';
|
||||
child.stderr!.on('data', chunk => (stderr += chunk));
|
||||
child.stdout!.on('data', chunk => {
|
||||
if (String(chunk).includes('listening')) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
child.on('exit', code =>
|
||||
reject(new Error(`The app exited with code ${code}: ${stderr}`)),
|
||||
);
|
||||
});
|
||||
|
||||
// Node cannot listen on a file descriptor on Windows.
|
||||
describe.skipIf(process.platform === 'win32')(
|
||||
'Listen on a file descriptor (Fastify Application)',
|
||||
() => {
|
||||
let child: ChildProcess;
|
||||
|
||||
afterEach(() => {
|
||||
child?.kill();
|
||||
});
|
||||
|
||||
it('should listen on the socket the process inherits', async () => {
|
||||
// A process cannot listen on a descriptor that its own event loop
|
||||
// already watches (EEXIST), so the app runs in a child process that
|
||||
// inherits the socket as fd 3, as with systemd socket activation.
|
||||
const server = createServer();
|
||||
await new Promise<void>(resolve =>
|
||||
server.listen(0, '127.0.0.1', resolve),
|
||||
);
|
||||
const { port } = server.address() as AddressInfo;
|
||||
|
||||
child = spawn(
|
||||
process.execPath,
|
||||
[join(import.meta.dirname, '../src/fastify-fd-main.js')],
|
||||
{ stdio: ['ignore', 'pipe', 'pipe', (server as any)._handle.fd] },
|
||||
);
|
||||
server.close();
|
||||
await waitUntilListening(child);
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${port}`);
|
||||
expect(response.status).toBe(200);
|
||||
await expect(response.text()).resolves.toBe('Hello World!');
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Controller, Get, Module } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { FastifyAdapter } from '@nestjs/platform-fastify';
|
||||
|
||||
// Plain JS with decorators applied by hand: under `--import jiti/register`,
|
||||
// Fastify fails to load on some Node versions (22.16, for one).
|
||||
class AppController {
|
||||
getHello() {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
||||
Get()(
|
||||
AppController.prototype,
|
||||
'getHello',
|
||||
Object.getOwnPropertyDescriptor(AppController.prototype, 'getHello'),
|
||||
);
|
||||
Controller()(AppController);
|
||||
|
||||
class AppModule {}
|
||||
Module({ controllers: [AppController] })(AppModule);
|
||||
|
||||
const app = await NestFactory.create(AppModule, new FastifyAdapter(), {
|
||||
logger: false,
|
||||
});
|
||||
|
||||
// The socket is inherited as fd 3, as systemd passes it on socket activation.
|
||||
await app.listen({ fd: 3 });
|
||||
console.log('listening');
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
FastifyBodyParser,
|
||||
FastifyError,
|
||||
FastifyInstance,
|
||||
FastifyListenOptions,
|
||||
FastifyPluginAsync,
|
||||
FastifyPluginCallback,
|
||||
FastifyRegister,
|
||||
@@ -77,7 +76,10 @@ import {
|
||||
FastifyStaticOptions,
|
||||
FastifyViewOptions,
|
||||
} from '../interfaces/external/index.js';
|
||||
import { NestFastifyBodyParserOptions } from '../interfaces/index.js';
|
||||
import {
|
||||
NestFastifyBodyParserOptions,
|
||||
NestFastifyListenOptions,
|
||||
} from '../interfaces/index.js';
|
||||
const { safeDecodeURI } = urlSanitizer;
|
||||
|
||||
const MISSING_MULTIPART_PACKAGE_MESSAGE =
|
||||
@@ -567,7 +569,7 @@ export class FastifyAdapter<
|
||||
callback?: () => void,
|
||||
): void;
|
||||
public listen(
|
||||
listenOptions: string | number | FastifyListenOptions,
|
||||
listenOptions: string | number | NestFastifyListenOptions,
|
||||
...args: any[]
|
||||
): void {
|
||||
const isFirstArgTypeofFunction = typeof args[0] === 'function';
|
||||
@@ -584,9 +586,10 @@ export class FastifyAdapter<
|
||||
typeof listenOptions === 'object' &&
|
||||
(listenOptions.host !== undefined ||
|
||||
listenOptions.port !== undefined ||
|
||||
listenOptions.path !== undefined)
|
||||
listenOptions.path !== undefined ||
|
||||
listenOptions.fd !== undefined)
|
||||
) {
|
||||
// First parameter is an object with a path, port and/or host attributes
|
||||
// First parameter is an object with a path, port, fd and/or host attributes
|
||||
options = listenOptions;
|
||||
} else {
|
||||
options = {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './nest-fastify-application.interface.js';
|
||||
export * from './nest-fastify-body-parser-options.interface.js';
|
||||
export * from './nest-fastify-listen-options.interface.js';
|
||||
@@ -3,7 +3,6 @@ import type { HttpServer, INestApplication } from '@nestjs/common';
|
||||
import {
|
||||
FastifyBodyParser,
|
||||
FastifyInstance,
|
||||
FastifyListenOptions,
|
||||
FastifyPluginAsync,
|
||||
FastifyPluginCallback,
|
||||
FastifyPluginOptions,
|
||||
@@ -20,6 +19,7 @@ import {
|
||||
} from 'light-my-request';
|
||||
import { FastifyStaticOptions, FastifyViewOptions } from './external/index.js';
|
||||
import { NestFastifyBodyParserOptions } from './nest-fastify-body-parser-options.interface.js';
|
||||
import { NestFastifyListenOptions } from './nest-fastify-listen-options.interface.js';
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
@@ -103,10 +103,10 @@ export interface NestFastifyApplication<
|
||||
* @returns A Promise that, when resolved, is a reference to the underlying HttpServer.
|
||||
*/
|
||||
listen(
|
||||
opts: FastifyListenOptions,
|
||||
opts: NestFastifyListenOptions,
|
||||
callback?: (err: Error | null, address: string) => void,
|
||||
): Promise<TServer>;
|
||||
listen(opts?: FastifyListenOptions): Promise<TServer>;
|
||||
listen(opts?: NestFastifyListenOptions): Promise<TServer>;
|
||||
listen(
|
||||
callback?: (err: Error | null, address: string) => void,
|
||||
): Promise<TServer>;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { FastifyListenOptions } from 'fastify';
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
*/
|
||||
export interface NestFastifyListenOptions extends FastifyListenOptions {
|
||||
/**
|
||||
* A file descriptor of a socket that is already bound, such as the one
|
||||
* systemd passes on socket activation. Not supported on Windows.
|
||||
* @see [server.listen(handle)](https://nodejs.org/api/net.html#serverlistenhandle-backlog-callback)
|
||||
*/
|
||||
fd?: number;
|
||||
}
|
||||
@@ -1615,6 +1615,15 @@ describe('FastifyAdapter', () => {
|
||||
expect(listen).toHaveBeenCalledWith({ port: 3000 }, undefined);
|
||||
});
|
||||
|
||||
it('should keep an options object with a file descriptor as it is', () => {
|
||||
const listen = spyOnListen();
|
||||
const callback = vi.fn();
|
||||
|
||||
fastifyAdapter.listen({ fd: 3 }, callback);
|
||||
|
||||
expect(listen).toHaveBeenCalledWith({ fd: 3 }, callback);
|
||||
});
|
||||
|
||||
it('should keep a number as a port', () => {
|
||||
const listen = spyOnListen();
|
||||
|
||||
|
||||
Reference in new issue
Block a user