feat: allow server to bind to Unix Domain Sockets

This commit is contained in:
Koen Luyten
2022-11-17 16:57:20 +01:00
committed by Joachim Van Herwegen
parent 0eb50891ec
commit bf0e35be37
15 changed files with 186 additions and 14 deletions

View File

@@ -51,6 +51,7 @@ export function getDefaultVariables(port: number, baseUrl?: string): Record<stri
return {
'urn:solid-server:default:variable:baseUrl': baseUrl ?? `http://localhost:${port}/`,
'urn:solid-server:default:variable:port': port,
'urn:solid-server:default:variable:socket': null,
'urn:solid-server:default:variable:loggingLevel': 'off',
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:seededPodConfigJson': null,

View File

@@ -22,6 +22,18 @@ describe('ServerInitializer', (): void => {
expect(serverFactory.startServer).toHaveBeenCalledWith(3000);
});
it('starts a server on the specified Unix Domain Socket.', async(): Promise<void> => {
initializer = new ServerInitializer(serverFactory, undefined, '/tmp/css.sock');
await initializer.handle();
expect(serverFactory.startServer).toHaveBeenCalledWith('/tmp/css.sock');
});
it('throws when neither port or socket are set.', async(): Promise<void> => {
expect((): void => {
initializer = new ServerInitializer(serverFactory, undefined, undefined);
}).toThrow('Either Port or Socket arguments must be set');
});
it('can stop the server.', async(): Promise<void> => {
await initializer.handle();
await expect(initializer.finalize()).resolves.toBeUndefined();

View File

@@ -16,6 +16,11 @@ describe('A BaseUrlExtractor', (): void => {
await expect(computer.handle({ port: 3333 })).resolves.toBe('http://localhost:3333/');
});
it('throws when a Unix Socket Path is provided without a baseUrl.', async(): Promise<void> => {
await expect(computer.handle({ socket: '/tmp/css.sock' })).rejects
.toThrow('BaseUrl argument should be provided when using Unix Domain Sockets.');
});
it('defaults to port 3000.', async(): Promise<void> => {
await expect(computer.handle({})).resolves.toBe('http://localhost:3000/');
});

View File

@@ -1,3 +1,4 @@
import { mkdirSync } from 'fs';
import type { Server } from 'http';
import request from 'supertest';
import type { BaseHttpServerOptions } from '../../../src/server/BaseHttpServerFactory';
@@ -5,7 +6,8 @@ import { BaseHttpServerFactory } from '../../../src/server/BaseHttpServerFactory
import type { HttpHandler } from '../../../src/server/HttpHandler';
import type { HttpResponse } from '../../../src/server/HttpResponse';
import { joinFilePath } from '../../../src/util/PathUtil';
import { getPort } from '../../util/Util';
import { getTestFolder, removeFolder } from '../../integration/Config';
import { getPort, getSocket } from '../../util/Util';
const port = getPort('BaseHttpServerFactory');
@@ -125,4 +127,91 @@ describe('A BaseHttpServerFactory', (): void => {
expect(res.text).toBe(`${error.stack}\n`);
});
});
describe('A Base HttpServerFactory (With Unix Sockets)', (): void => {
const socketFolder = getTestFolder('sockets');
const socket = joinFilePath(socketFolder, getSocket('BaseHttpServerFactory'));
const httpOptions = {
http: true,
showStackTrace: true,
};
beforeAll(async(): Promise<void> => {
mkdirSync(socketFolder, { recursive: true });
});
afterAll(async(): Promise<void> => {
server.close();
await removeFolder(socketFolder);
});
beforeEach(async(): Promise<void> => {
jest.clearAllMocks();
});
describe('On linux', (): void => {
if (process.platform === 'win32') {
return;
}
it('sends incoming requests to the handler.', async(): Promise<void> => {
const factory = new BaseHttpServerFactory(handler, httpOptions);
server = factory.startServer(socket);
await request(`http+unix://${socket.replace(/\//gui, '%2F')}`).get('/').set('Host', 'test.com').expect(200);
expect(handler.handleSafe).toHaveBeenCalledTimes(1);
expect(handler.handleSafe).toHaveBeenLastCalledWith({
request: expect.objectContaining({
headers: expect.objectContaining({ host: 'test.com' }),
}),
response: expect.objectContaining({}),
});
});
it('throws an error on windows.', async(): Promise<void> => {
const prevPlatform = process.platform;
Object.defineProperty(process, 'platform', {
value: 'win32',
});
const factory = new BaseHttpServerFactory(handler, httpOptions);
expect((): void => {
factory.startServer(socket);
}).toThrow();
Object.defineProperty(process, 'platform', {
value: prevPlatform,
});
});
});
describe('On Windows', (): void => {
if (process.platform !== 'win32') {
return;
}
it('throws an error when trying to start the server on windows.', async(): Promise<void> => {
const factory = new BaseHttpServerFactory(handler, httpOptions);
expect((): void => {
factory.startServer(socket);
}).toThrow();
});
});
describe('On any platform', (): void => {
it('throws an error when trying to start with an invalid socket Path.', async(): Promise<void> => {
const prevPlatform = process.platform;
Object.defineProperty(process, 'platform', {
value: 'linux',
});
const factory = new BaseHttpServerFactory(handler, httpOptions);
factory.startServer('/fake/path')
.on('error', (error): void => {
expect(error).toHaveProperty('code', 'EACCES');
});
Object.defineProperty(process, 'platform', {
value: prevPlatform,
});
});
});
});
});

View File

@@ -33,6 +33,11 @@ const portNames = [
'BaseHttpServerFactory',
] as const;
const socketNames = [
// Unit
'BaseHttpServerFactory',
];
export function getPort(name: typeof portNames[number]): number {
const idx = portNames.indexOf(name);
// Just in case something doesn't listen to the typings
@@ -42,6 +47,15 @@ export function getPort(name: typeof portNames[number]): number {
return 6000 + idx;
}
export function getSocket(name: typeof socketNames[number]): string {
const idx = socketNames.indexOf(name);
// Just in case something doesn't listen to the typings
if (idx < 0) {
throw new Error(`Unknown socket name ${name}`);
}
return `css${idx}.sock`;
}
export function describeIf(envFlag: string): Describe {
const flag = `TEST_${envFlag.toUpperCase()}`;
const enabled = !/^(|0|false)$/iu.test(process.env[flag] ?? '');