Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use fastify #661

Merged
merged 29 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,113 changes: 1,047 additions & 2,066 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
"@emotion/babel-plugin": "^11.11.0",
"@emotion/css": "^11.11.0",
"@emotion/react": "^11.11.1",
"@fastify/express": "^2.3.0",
"@fastify/static": "^6.10.2",
"@lingui/loader": "^3.17.2",
"@lingui/react": "^3.17.2",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
Expand All @@ -92,7 +94,6 @@
"@types/cookie-parser": "^1.4.3",
"@types/create-torrent": "^5.0.0",
"@types/d3": "^7.4.0",
"@types/debug": "^4.1.8",
"@types/express": "^4.17.17",
"@types/fs-extra": "^9.0.13",
"@types/geoip-country": "^4.0.0",
Expand All @@ -104,7 +105,7 @@
"@types/node": "^12.20.55",
"@types/parse-torrent": "^5.8.4",
"@types/passport": "^1.0.12",
"@types/passport-jwt": "^3.0.8",
"@types/passport-jwt": "^3.0.9",
"@types/react": "^18.2.11",
"@types/react-dom": "^18.2.4",
"@types/react-measure": "^2.0.8",
Expand Down Expand Up @@ -136,8 +137,6 @@
"d3-scale": "^4.0.2",
"d3-selection": "^3.0.0",
"d3-shape": "^3.2.0",
"debug": "^4.3.4",
"esbuild-jest": "^0.5.0",
"eslint": "^8.42.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0",
Expand All @@ -149,6 +148,8 @@
"express-rate-limit": "^6.7.0",
"fast-json-patch": "^3.1.1",
"fast-sort": "^3.4.0",
"fastify": "^4.21.0",
"fastify-type-provider-zod": "^1.1.9",
"feedsub": "^0.7.8",
"file-loader": "^6.2.0",
"form-data": "^4.0.0",
Expand All @@ -159,6 +160,7 @@
"html-webpack-plugin": "^5.5.3",
"http-errors": "^2.0.0",
"jest": "^28.1.3",
"jest-esbuild": "^0.3.0",
"js-file-download": "^0.4.12",
"jsonwebtoken": "^9.0.0",
"lodash": "^4.17.21",
Expand All @@ -176,7 +178,6 @@
"postcss": "^8.4.24",
"postcss-loader": "^7.3.3",
"prettier": "^2.8.8",
"promise": "^8.3.0",
"react": "^18.2.0",
"react-dev-utils": "^12.0.1",
"react-dom": "^18.2.0",
Expand Down
2 changes: 1 addition & 1 deletion server/.jest/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module.exports = {
transform: {
// transform ESM only package to CommonJS
'^.+\\.(t|j)sx?$': [
'esbuild-jest',
'jest-esbuild',
{
format: 'cjs',
},
Expand Down
129 changes: 0 additions & 129 deletions server/app.ts

This file was deleted.

7 changes: 2 additions & 5 deletions server/bin/start.ts
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import chalk from 'chalk';

import enforcePrerequisites from './enforce-prerequisites';
import migrateData from './migrations/run';
import startWebServer from './web-server';

if (process.env.NODE_ENV == 'production') {
// Catch unhandled rejections and exceptions
Expand All @@ -26,11 +27,7 @@ if (process.env.NODE_ENV == 'production') {

enforcePrerequisites()
.then(migrateData)
.then(() => {
// We do this because we don't want the side effects of importing server functions before migration is completed.
const startWebServer = require('./web-server').default; // eslint-disable-line @typescript-eslint/no-var-requires
return startWebServer();
})
.then(startWebServer)
.catch((error) => {
console.log(chalk.red('Failed to start Flood:'));
console.trace(error);
Expand Down
105 changes: 25 additions & 80 deletions server/bin/web-server.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,51 @@
import chalk from 'chalk';
import debug from 'debug';
import fastify from 'fastify';
import fs from 'fs';
import http from 'http';
import https from 'https';

import app from '../app';
import type {FastifyInstance} from 'fastify';
import type {Http2SecureServer} from 'http2';
import type {Server} from 'http';

import config from '../../config';
import constructRoutes from '../routes';
import packageJSON from '../../package.json';

const debugFloodServer = debug('flood:server');

// Normalize a port into a number, string, or false.
const normalizePort = (val: string | number): string | number => {
const port = parseInt(val as string, 10);

// Named pipe.
if (Number.isNaN(port)) {
return val;
}

// Port number.
if (port >= 0) {
return port;
}

console.error('Unexpected port or pipe');
process.exit(1);
};

const startWebServer = () => {
const port = normalizePort(config.floodServerPort);
const host = config.floodServerHost;
const useSSL = config.ssl ?? false;

app.set('port', port);
app.set('host', host);
const startWebServer = async () => {
const {ssl = false, floodServerHost: host, floodServerPort: port} = config;

// Create HTTP or HTTPS server.
let server: http.Server | https.Server;
let instance: FastifyInstance<Http2SecureServer> | FastifyInstance<Server>;

if (useSSL) {
if (ssl) {
if (!config.sslKey || !config.sslCert) {
console.error('Cannot start HTTPS server, `sslKey` or `sslCert` is missing in config.js.');
process.exit(1);
}

server = https.createServer(
{
instance = fastify({
bodyLimit: 100 * 1024 * 1024,
trustProxy: 'loopback',
http2: true,
https: {
allowHTTP1: true,
key: fs.readFileSync(config.sslKey),
cert: fs.readFileSync(config.sslCert),
},
app,
);
});
} else {
server = http.createServer(app);
instance = fastify({
bodyLimit: 100 * 1024 * 1024,
trustProxy: 'loopback',
});
}

const handleError = (error: NodeJS.ErrnoException) => {
if (error.syscall !== 'listen') {
throw error;
}

const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`;
await constructRoutes(instance as FastifyInstance);

// Handle specific listen errors with friendly messages.
switch (error.code) {
case 'EACCES':
console.error(`${bind} requires elevated privileges`);
process.exit(1);
case 'EADDRINUSE':
console.error(`${bind} is already in use`);
process.exit(1);
default:
throw error;
}
};

// Event listener for HTTP server "listening" event.
const handleListening = () => {
const addr = server.address();
if (addr == null) {
console.error('Unable to get listening address.');
process.exit(1);
}
const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`;
debugFloodServer(`Listening on ${bind}`);
};

// Listen on provided port, on all network interfaces.
if (typeof port === 'string') {
server.listen(port);
await instance.listen({path: port});
} else {
server.listen(port, host);
await instance.listen({port, host});
}

server.on('error', handleError);
server.on('listening', handleListening);
process.on('exit', () => {
server.close();
});

const address = chalk.underline(typeof port === 'string' ? port : `${useSSL ? 'https' : 'http'}://${host}:${port}`);
const address = chalk.underline(`${ssl ? 'https' : 'http'}://${host}:${port}`);

console.log(chalk.green(`Flood server ${packageJSON.version} starting on ${address}\n`));

Expand Down
18 changes: 15 additions & 3 deletions server/routes/api/auth.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import crypto from 'crypto';
import fastify from 'fastify';
import supertest from 'supertest';

import {AccessLevel} from '../../../shared/schema/constants/Auth';

import app from '../../app';
import constructRoutes from '..';
import {getAuthToken} from '../../util/authUtil';

import type {
Expand All @@ -13,8 +14,6 @@ import type {
} from '../../../shared/schema/api/auth';
import type {ClientConnectionSettings} from '../../../shared/schema/ClientConnectionSettings';

const request = supertest(app);

const testConnectionSettings: ClientConnectionSettings = {
client: 'rTorrent',
type: 'socket',
Expand All @@ -38,6 +37,19 @@ const testNonAdminUser = {
} as const;
let testNonAdminUserToken = '';

const app = fastify({disableRequestLogging: true, logger: false});
let request: supertest.SuperTest<supertest.Test>;

beforeAll(async () => {
await constructRoutes(app);
await app.ready();
request = supertest(app.server);
});

afterAll(async () => {
await app.close();
});

describe('GET /api/auth/verify (initial)', () => {
it('Verify without credential', (done) => {
request
Expand Down
Loading
Loading