backend: implement HttpContextService and middleware for managing HTTP context. Refactor base URL retrieval logic

This commit is contained in:
juancarmore 2025-09-26 10:15:12 +02:00
parent 8894174eb9
commit 43839fdb9d
17 changed files with 96 additions and 21 deletions

View File

@ -8,6 +8,7 @@ import {
FrontendEventService, FrontendEventService,
GCSService, GCSService,
GCSStorageProvider, GCSStorageProvider,
HttpContextService,
LiveKitService, LiveKitService,
LivekitWebhookService, LivekitWebhookService,
LoggerService, LoggerService,
@ -52,6 +53,7 @@ export const registerDependencies = () => {
container.bind(DistributedEventService).toSelf().inSingletonScope(); container.bind(DistributedEventService).toSelf().inSingletonScope();
container.bind(MutexService).toSelf().inSingletonScope(); container.bind(MutexService).toSelf().inSingletonScope();
container.bind(TaskSchedulerService).toSelf().inSingletonScope(); container.bind(TaskSchedulerService).toSelf().inSingletonScope();
container.bind(HttpContextService).toSelf().inSingletonScope();
configureStorage(MEET_BLOB_STORAGE_MODE); configureStorage(MEET_BLOB_STORAGE_MODE);
container.bind(StorageFactory).toSelf().inSingletonScope(); container.bind(StorageFactory).toSelf().inSingletonScope();

View File

@ -11,7 +11,7 @@ import {
rejectRequestFromMeetError rejectRequestFromMeetError
} from '../models/error.model.js'; } from '../models/error.model.js';
import { AuthService, LoggerService, TokenService, UserService } from '../services/index.js'; import { AuthService, LoggerService, TokenService, UserService } from '../services/index.js';
import { getCookieOptions } from '../utils/cookie-utils.js'; import { getCookieOptions } from '../utils/index.js';
export const login = async (req: Request, res: Response) => { export const login = async (req: Request, res: Response) => {
const logger = container.get(LoggerService); const logger = container.get(LoggerService);

View File

@ -9,7 +9,7 @@ import {
rejectRequestFromMeetError rejectRequestFromMeetError
} from '../models/error.model.js'; } from '../models/error.model.js';
import { LoggerService, ParticipantService, RoomService, TokenService } from '../services/index.js'; import { LoggerService, ParticipantService, RoomService, TokenService } from '../services/index.js';
import { getCookieOptions } from '../utils/cookie-utils.js'; import { getCookieOptions } from '../utils/index.js';
export const generateParticipantToken = async (req: Request, res: Response) => { export const generateParticipantToken = async (req: Request, res: Response) => {
const logger = container.get(LoggerService); const logger = container.get(LoggerService);

View File

@ -3,7 +3,6 @@ import { Request, Response } from 'express';
import { Readable } from 'stream'; import { Readable } from 'stream';
import { container } from '../config/index.js'; import { container } from '../config/index.js';
import INTERNAL_CONFIG from '../config/internal-config.js'; import INTERNAL_CONFIG from '../config/internal-config.js';
import { getBaseUrl } from '../environment.js';
import { RecordingHelper } from '../helpers/index.js'; import { RecordingHelper } from '../helpers/index.js';
import { import {
errorRecordingNotFound, errorRecordingNotFound,
@ -13,6 +12,7 @@ import {
rejectRequestFromMeetError rejectRequestFromMeetError
} from '../models/error.model.js'; } from '../models/error.model.js';
import { LoggerService, MeetStorageService, RecordingService } from '../services/index.js'; import { LoggerService, MeetStorageService, RecordingService } from '../services/index.js';
import { getBaseUrl } from '../utils/index.js';
export const startRecording = async (req: Request, res: Response) => { export const startRecording = async (req: Request, res: Response) => {
const logger = container.get(LoggerService); const logger = container.get(LoggerService);
@ -125,10 +125,7 @@ export const stopRecording = async (req: Request, res: Response) => {
const recordingService = container.get(RecordingService); const recordingService = container.get(RecordingService);
const recordingInfo = await recordingService.stopRecording(recordingId); const recordingInfo = await recordingService.stopRecording(recordingId);
res.setHeader( res.setHeader('Location', `${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${recordingId}`);
'Location',
`${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${recordingId}`
);
return res.status(202).json(recordingInfo); return res.status(202).json(recordingInfo);
} catch (error) { } catch (error) {
handleError(res, error, `stopping recording '${recordingId}'`); handleError(res, error, `stopping recording '${recordingId}'`);

View File

@ -10,10 +10,9 @@ import {
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { container } from '../config/index.js'; import { container } from '../config/index.js';
import INTERNAL_CONFIG from '../config/internal-config.js'; import INTERNAL_CONFIG from '../config/internal-config.js';
import { getBaseUrl } from '../environment.js';
import { handleError } from '../models/error.model.js'; import { handleError } from '../models/error.model.js';
import { LoggerService, ParticipantService, RoomService } from '../services/index.js'; import { LoggerService, ParticipantService, RoomService } from '../services/index.js';
import { getCookieOptions } from '../utils/cookie-utils.js'; import { getBaseUrl, getCookieOptions } from '../utils/index.js';
export const createRoom = async (req: Request, res: Response) => { export const createRoom = async (req: Request, res: Response) => {
const logger = container.get(LoggerService); const logger = container.get(LoggerService);

View File

@ -20,7 +20,7 @@ export const {
SERVER_CORS_ORIGIN = '*', SERVER_CORS_ORIGIN = '*',
MEET_LOG_LEVEL = 'info', MEET_LOG_LEVEL = 'info',
MEET_NAME_ID = 'openviduMeet', MEET_NAME_ID = 'openviduMeet',
MEET_BASE_URL = `http://localhost:${SERVER_PORT}`, MEET_BASE_URL = '',
/** /**
* Authentication configuration * Authentication configuration
@ -86,13 +86,6 @@ export const {
ENABLED_MODULES = '' ENABLED_MODULES = ''
} = process.env; } = process.env;
/**
* Gets the base URL without trailing slash
*/
export const getBaseUrl = (): string => {
return MEET_BASE_URL.endsWith('/') ? MEET_BASE_URL.slice(0, -1) : MEET_BASE_URL;
};
export function checkModuleEnabled() { export function checkModuleEnabled() {
if (MODULES_FILE) { if (MODULES_FILE) {
const moduleName = MODULE_NAME; const moduleName = MODULE_NAME;

View File

@ -0,0 +1,15 @@
import { NextFunction, Request, Response } from 'express';
import { container } from '../config/dependency-injector.config.js';
import { HttpContextService } from '../services/index.js';
export const httpContextMiddleware = (req: Request, res: Response, next: NextFunction) => {
const httpContextService = container.get(HttpContextService);
httpContextService.setContext(req);
// Clear context after response is finished
res.on('finish', () => {
httpContextService.clearContext();
});
next();
};

View File

@ -1,4 +1,5 @@
export * from './content-type.middleware.js'; export * from './content-type.middleware.js';
export * from './http-context.middleware.js';
export * from './auth.middleware.js'; export * from './auth.middleware.js';
export * from './room.middleware.js'; export * from './room.middleware.js';
export * from './participant.middleware.js'; export * from './participant.middleware.js';

View File

@ -5,7 +5,7 @@ import express, { Express, Request, Response } from 'express';
import { initializeEagerServices, registerDependencies } from './config/index.js'; import { initializeEagerServices, registerDependencies } from './config/index.js';
import INTERNAL_CONFIG from './config/internal-config.js'; import INTERNAL_CONFIG from './config/internal-config.js';
import { SERVER_CORS_ORIGIN, SERVER_PORT, logEnvVars } from './environment.js'; import { SERVER_CORS_ORIGIN, SERVER_PORT, logEnvVars } from './environment.js';
import { jsonSyntaxErrorHandler } from './middlewares/index.js'; import { httpContextMiddleware, jsonSyntaxErrorHandler } from './middlewares/index.js';
import { import {
authRouter, authRouter,
configRouter, configRouter,
@ -24,7 +24,7 @@ import {
internalApiHtmlFilePath, internalApiHtmlFilePath,
publicApiHtmlFilePath, publicApiHtmlFilePath,
webcomponentBundlePath webcomponentBundlePath
} from './utils/path-utils.js'; } from './utils/path.utils.js';
const createApp = () => { const createApp = () => {
const app: Express = express(); const app: Express = express();
@ -41,10 +41,14 @@ const createApp = () => {
// Serve static files // Serve static files
app.use(express.static(frontendDirectoryPath)); app.use(express.static(frontendDirectoryPath));
app.use(express.json()); app.use(express.json());
app.use(jsonSyntaxErrorHandler); app.use(jsonSyntaxErrorHandler);
app.use(cookieParser()); app.use(cookieParser());
// Middleware to set HTTP context
app.use(httpContextMiddleware);
// Public API routes // Public API routes
app.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) => app.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) =>
res.sendFile(publicApiHtmlFilePath) res.sendFile(publicApiHtmlFilePath)

View File

@ -0,0 +1,39 @@
import { Request } from 'express';
import { injectable } from 'inversify';
import { SERVER_PORT } from '../environment.js';
@injectable()
export class HttpContextService {
private baseUrl: string;
constructor() {
this.baseUrl = this.getDefaultBaseUrl();
}
/**
* Sets the current HTTP context from the request
*/
setContext(req: Request): void {
const protocol = req.protocol;
const host = req.get('host');
this.baseUrl = `${protocol}://${host}`;
}
/**
* Gets the base URL from the current context
*/
getBaseUrl(): string {
return this.baseUrl;
}
/**
* Clears the current context
*/
clearContext(): void {
this.baseUrl = this.getDefaultBaseUrl();
}
private getDefaultBaseUrl(): string {
return `http://localhost:${SERVER_PORT}`;
}
}

View File

@ -3,6 +3,7 @@ export * from './redis.service.js';
export * from './distributed-event.service.js'; export * from './distributed-event.service.js';
export * from './mutex.service.js'; export * from './mutex.service.js';
export * from './task-scheduler.service.js'; export * from './task-scheduler.service.js';
export * from './http-context.service.js';
export * from './storage/index.js'; export * from './storage/index.js';

View File

@ -23,7 +23,7 @@ import {
internalError, internalError,
OpenViduMeetError OpenViduMeetError
} from '../models/error.model.js'; } from '../models/error.model.js';
import { chunkArray } from '../utils/array.utils.js'; import { chunkArray } from '../utils/index.js';
import { LoggerService } from './index.js'; import { LoggerService } from './index.js';
@injectable() @injectable()

View File

@ -3,7 +3,6 @@ import { inject, injectable } from 'inversify';
import ms from 'ms'; import ms from 'ms';
import { Readable } from 'stream'; import { Readable } from 'stream';
import { import {
getBaseUrl,
MEET_INITIAL_ADMIN_PASSWORD, MEET_INITIAL_ADMIN_PASSWORD,
MEET_INITIAL_ADMIN_USER, MEET_INITIAL_ADMIN_USER,
MEET_INITIAL_API_KEY, MEET_INITIAL_API_KEY,
@ -20,6 +19,7 @@ import {
OpenViduMeetError, OpenViduMeetError,
RedisKeyName RedisKeyName
} from '../../models/index.js'; } from '../../models/index.js';
import { getBaseUrl } from '../../utils/index.js';
import { LoggerService, MutexService, RedisService } from '../index.js'; import { LoggerService, MutexService, RedisService } from '../index.js';
import { StorageFactory } from './storage.factory.js'; import { StorageFactory } from './storage.factory.js';
import { StorageKeyBuilder, StorageProvider } from './storage.interface.js'; import { StorageKeyBuilder, StorageProvider } from './storage.interface.js';

View File

@ -0,0 +1,3 @@
export * from './array.utils.js';
export * from './cookie.utils.js';
export * from './url.utils.js';

View File

@ -0,0 +1,21 @@
import { container } from '../config/dependency-injector.config.js';
import { MEET_BASE_URL } from '../environment.js';
import { HttpContextService } from '../services/http-context.service.js';
/**
* Returns the base URL for the application.
*
* If the global `MEET_BASE_URL` variable is defined, it returns its value,
* ensuring there is no trailing slash. Otherwise, it retrieves the base URL
* from the `HttpContextService` instance.
*
* @returns {string} The base URL as a string.
*/
export const getBaseUrl = (): string => {
if (MEET_BASE_URL) {
return MEET_BASE_URL.endsWith('/') ? MEET_BASE_URL.slice(0, -1) : MEET_BASE_URL;
}
const httpContextService = container.get(HttpContextService);
return httpContextService.getBaseUrl();
};