From 43839fdb9dec9e6b105b09830df59c18bc42b882 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Fri, 26 Sep 2025 10:15:12 +0200 Subject: [PATCH] backend: implement HttpContextService and middleware for managing HTTP context. Refactor base URL retrieval logic --- .../src/config/dependency-injector.config.ts | 2 + backend/src/controllers/auth.controller.ts | 2 +- .../src/controllers/participant.controller.ts | 2 +- .../src/controllers/recording.controller.ts | 7 +--- backend/src/controllers/room.controller.ts | 3 +- backend/src/environment.ts | 9 +---- .../middlewares/http-context.middleware.ts | 15 +++++++ backend/src/middlewares/index.ts | 1 + backend/src/server.ts | 8 +++- backend/src/services/http-context.service.ts | 39 +++++++++++++++++++ backend/src/services/index.ts | 1 + backend/src/services/livekit.service.ts | 2 +- .../src/services/storage/storage.service.ts | 2 +- .../{cookie-utils.ts => cookie.utils.ts} | 0 backend/src/utils/index.ts | 3 ++ .../utils/{path-utils.ts => path.utils.ts} | 0 backend/src/utils/url.utils.ts | 21 ++++++++++ 17 files changed, 96 insertions(+), 21 deletions(-) create mode 100644 backend/src/middlewares/http-context.middleware.ts create mode 100644 backend/src/services/http-context.service.ts rename backend/src/utils/{cookie-utils.ts => cookie.utils.ts} (100%) create mode 100644 backend/src/utils/index.ts rename backend/src/utils/{path-utils.ts => path.utils.ts} (100%) create mode 100644 backend/src/utils/url.utils.ts diff --git a/backend/src/config/dependency-injector.config.ts b/backend/src/config/dependency-injector.config.ts index 1c988fe..1ae8aa2 100644 --- a/backend/src/config/dependency-injector.config.ts +++ b/backend/src/config/dependency-injector.config.ts @@ -8,6 +8,7 @@ import { FrontendEventService, GCSService, GCSStorageProvider, + HttpContextService, LiveKitService, LivekitWebhookService, LoggerService, @@ -52,6 +53,7 @@ export const registerDependencies = () => { container.bind(DistributedEventService).toSelf().inSingletonScope(); container.bind(MutexService).toSelf().inSingletonScope(); container.bind(TaskSchedulerService).toSelf().inSingletonScope(); + container.bind(HttpContextService).toSelf().inSingletonScope(); configureStorage(MEET_BLOB_STORAGE_MODE); container.bind(StorageFactory).toSelf().inSingletonScope(); diff --git a/backend/src/controllers/auth.controller.ts b/backend/src/controllers/auth.controller.ts index fc2f5fb..5a58fca 100644 --- a/backend/src/controllers/auth.controller.ts +++ b/backend/src/controllers/auth.controller.ts @@ -11,7 +11,7 @@ import { rejectRequestFromMeetError } from '../models/error.model.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) => { const logger = container.get(LoggerService); diff --git a/backend/src/controllers/participant.controller.ts b/backend/src/controllers/participant.controller.ts index 2ed3791..dd3a63f 100644 --- a/backend/src/controllers/participant.controller.ts +++ b/backend/src/controllers/participant.controller.ts @@ -9,7 +9,7 @@ import { rejectRequestFromMeetError } from '../models/error.model.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) => { const logger = container.get(LoggerService); diff --git a/backend/src/controllers/recording.controller.ts b/backend/src/controllers/recording.controller.ts index fb0d3f3..963473c 100644 --- a/backend/src/controllers/recording.controller.ts +++ b/backend/src/controllers/recording.controller.ts @@ -3,7 +3,6 @@ import { Request, Response } from 'express'; import { Readable } from 'stream'; import { container } from '../config/index.js'; import INTERNAL_CONFIG from '../config/internal-config.js'; -import { getBaseUrl } from '../environment.js'; import { RecordingHelper } from '../helpers/index.js'; import { errorRecordingNotFound, @@ -13,6 +12,7 @@ import { rejectRequestFromMeetError } from '../models/error.model.js'; import { LoggerService, MeetStorageService, RecordingService } from '../services/index.js'; +import { getBaseUrl } from '../utils/index.js'; export const startRecording = async (req: Request, res: Response) => { const logger = container.get(LoggerService); @@ -125,10 +125,7 @@ export const stopRecording = async (req: Request, res: Response) => { const recordingService = container.get(RecordingService); const recordingInfo = await recordingService.stopRecording(recordingId); - res.setHeader( - 'Location', - `${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${recordingId}` - ); + res.setHeader('Location', `${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${recordingId}`); return res.status(202).json(recordingInfo); } catch (error) { handleError(res, error, `stopping recording '${recordingId}'`); diff --git a/backend/src/controllers/room.controller.ts b/backend/src/controllers/room.controller.ts index 3428c21..817874d 100644 --- a/backend/src/controllers/room.controller.ts +++ b/backend/src/controllers/room.controller.ts @@ -10,10 +10,9 @@ import { import { Request, Response } from 'express'; import { container } from '../config/index.js'; import INTERNAL_CONFIG from '../config/internal-config.js'; -import { getBaseUrl } from '../environment.js'; import { handleError } from '../models/error.model.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) => { const logger = container.get(LoggerService); diff --git a/backend/src/environment.ts b/backend/src/environment.ts index 1140fa1..128304e 100644 --- a/backend/src/environment.ts +++ b/backend/src/environment.ts @@ -20,7 +20,7 @@ export const { SERVER_CORS_ORIGIN = '*', MEET_LOG_LEVEL = 'info', MEET_NAME_ID = 'openviduMeet', - MEET_BASE_URL = `http://localhost:${SERVER_PORT}`, + MEET_BASE_URL = '', /** * Authentication configuration @@ -86,13 +86,6 @@ export const { ENABLED_MODULES = '' } = 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() { if (MODULES_FILE) { const moduleName = MODULE_NAME; diff --git a/backend/src/middlewares/http-context.middleware.ts b/backend/src/middlewares/http-context.middleware.ts new file mode 100644 index 0000000..da02bfe --- /dev/null +++ b/backend/src/middlewares/http-context.middleware.ts @@ -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(); +}; diff --git a/backend/src/middlewares/index.ts b/backend/src/middlewares/index.ts index 85fdae9..bc51063 100644 --- a/backend/src/middlewares/index.ts +++ b/backend/src/middlewares/index.ts @@ -1,4 +1,5 @@ export * from './content-type.middleware.js'; +export * from './http-context.middleware.js'; export * from './auth.middleware.js'; export * from './room.middleware.js'; export * from './participant.middleware.js'; diff --git a/backend/src/server.ts b/backend/src/server.ts index c65a1c6..982069e 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -5,7 +5,7 @@ import express, { Express, Request, Response } from 'express'; import { initializeEagerServices, registerDependencies } from './config/index.js'; import INTERNAL_CONFIG from './config/internal-config.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 { authRouter, configRouter, @@ -24,7 +24,7 @@ import { internalApiHtmlFilePath, publicApiHtmlFilePath, webcomponentBundlePath -} from './utils/path-utils.js'; +} from './utils/path.utils.js'; const createApp = () => { const app: Express = express(); @@ -41,10 +41,14 @@ const createApp = () => { // Serve static files app.use(express.static(frontendDirectoryPath)); + app.use(express.json()); app.use(jsonSyntaxErrorHandler); app.use(cookieParser()); + // Middleware to set HTTP context + app.use(httpContextMiddleware); + // Public API routes app.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) => res.sendFile(publicApiHtmlFilePath) diff --git a/backend/src/services/http-context.service.ts b/backend/src/services/http-context.service.ts new file mode 100644 index 0000000..50b3aed --- /dev/null +++ b/backend/src/services/http-context.service.ts @@ -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}`; + } +} diff --git a/backend/src/services/index.ts b/backend/src/services/index.ts index eaddf7d..84f29a5 100644 --- a/backend/src/services/index.ts +++ b/backend/src/services/index.ts @@ -3,6 +3,7 @@ export * from './redis.service.js'; export * from './distributed-event.service.js'; export * from './mutex.service.js'; export * from './task-scheduler.service.js'; +export * from './http-context.service.js'; export * from './storage/index.js'; diff --git a/backend/src/services/livekit.service.ts b/backend/src/services/livekit.service.ts index 583aeca..e4d9338 100644 --- a/backend/src/services/livekit.service.ts +++ b/backend/src/services/livekit.service.ts @@ -23,7 +23,7 @@ import { internalError, OpenViduMeetError } from '../models/error.model.js'; -import { chunkArray } from '../utils/array.utils.js'; +import { chunkArray } from '../utils/index.js'; import { LoggerService } from './index.js'; @injectable() diff --git a/backend/src/services/storage/storage.service.ts b/backend/src/services/storage/storage.service.ts index e688f98..0584a14 100644 --- a/backend/src/services/storage/storage.service.ts +++ b/backend/src/services/storage/storage.service.ts @@ -3,7 +3,6 @@ import { inject, injectable } from 'inversify'; import ms from 'ms'; import { Readable } from 'stream'; import { - getBaseUrl, MEET_INITIAL_ADMIN_PASSWORD, MEET_INITIAL_ADMIN_USER, MEET_INITIAL_API_KEY, @@ -20,6 +19,7 @@ import { OpenViduMeetError, RedisKeyName } from '../../models/index.js'; +import { getBaseUrl } from '../../utils/index.js'; import { LoggerService, MutexService, RedisService } from '../index.js'; import { StorageFactory } from './storage.factory.js'; import { StorageKeyBuilder, StorageProvider } from './storage.interface.js'; diff --git a/backend/src/utils/cookie-utils.ts b/backend/src/utils/cookie.utils.ts similarity index 100% rename from backend/src/utils/cookie-utils.ts rename to backend/src/utils/cookie.utils.ts diff --git a/backend/src/utils/index.ts b/backend/src/utils/index.ts new file mode 100644 index 0000000..fb9bdd0 --- /dev/null +++ b/backend/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './array.utils.js'; +export * from './cookie.utils.js'; +export * from './url.utils.js'; diff --git a/backend/src/utils/path-utils.ts b/backend/src/utils/path.utils.ts similarity index 100% rename from backend/src/utils/path-utils.ts rename to backend/src/utils/path.utils.ts diff --git a/backend/src/utils/url.utils.ts b/backend/src/utils/url.utils.ts new file mode 100644 index 0000000..d1dcf4d --- /dev/null +++ b/backend/src/utils/url.utils.ts @@ -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(); +};