diff --git a/client/src/pages/Student/JoinRoom/JoinRoom.tsx b/client/src/pages/Student/JoinRoom/JoinRoom.tsx index 343f883..4372655 100644 --- a/client/src/pages/Student/JoinRoom/JoinRoom.tsx +++ b/client/src/pages/Student/JoinRoom/JoinRoom.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { Socket } from 'socket.io-client'; +import { io,Socket } from 'socket.io-client'; //import { ENV_VARIABLES } from '../../../constants'; import StudentModeQuiz from '../../../components/StudentModeQuiz/StudentModeQuiz'; @@ -34,7 +34,15 @@ const JoinRoom: React.FC = () => { }, []); const handleCreateSocket = () => { - const socket = webSocketService.connect("localhost:4500"); + debugger; + const socket = io('/', { + path: `/api/room/${roomName}/socket`, + transports: ['websocket'], + autoConnect: true, + reconnection: true, + reconnectionAttempts: 5, + reconnectionDelay: 1000, + }); socket.on('join-success', () => { setIsWaitingForTeacher(true); diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index bb9f97e..78ec409 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -79,9 +79,10 @@ const ManageRoom: React.FC = () => { } }; - const createWebSocketRoom = () => { + const createWebSocketRoom = async() => { setConnectingError(''); - const socket = webSocketService.connect("localhost:4500"); + const room = await(await fetch('/api/room',{method:'post'})).json(); + const socket = webSocketService.connect(`/api/room/${room.id}/socket`); socket.on('connect', () => { webSocketService.createRoom(); diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 6597d48..9f1280d 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,3 +1,69 @@ -FROM nginx +# Stage 1: Build stage +FROM nginx:1.27-alpine AS builder -COPY ./default.conf /etc/nginx/conf.d/default.conf \ No newline at end of file +# Install required packages +RUN apk add --no-cache nginx-mod-http-js nginx-mod-http-keyval + +# Stage 2: Final stage +FROM alpine:3.19 + +# Copy Nginx and NJS modules from builder +COPY --from=builder /usr/sbin/nginx /usr/sbin/ +COPY --from=builder /usr/lib/nginx/modules/ /usr/lib/nginx/modules/ +COPY --from=builder /etc/nginx/ /etc/nginx/ +COPY --from=builder /usr/lib/nginx/ /usr/lib/nginx/ + +# Install required runtime dependencies +RUN apk add --no-cache \ + pcre2 \ + ca-certificates \ + pcre \ + libgcc \ + libstdc++ \ + zlib \ + libxml2 \ + libedit \ + geoip \ + libxslt \ + && mkdir -p /var/cache/nginx \ + && mkdir -p /var/log/nginx \ + && mkdir -p /etc/nginx/conf.d \ + && mkdir -p /etc/nginx/njs \ + && ln -sf /dev/stdout /var/log/nginx/access.log \ + && ln -sf /dev/stderr /var/log/nginx/error.log \ + && addgroup -S nginx \ + && adduser -D -S -h /var/cache/nginx -s /sbin/nologin -G nginx nginx + +# Copy necessary libraries from builder +COPY --from=builder /usr/lib/libxml2.so* /usr/lib/ +COPY --from=builder /usr/lib/libexslt.so* /usr/lib/ +COPY --from=builder /usr/lib/libgd.so* /usr/lib/ +COPY --from=builder /usr/lib/libxslt.so* /usr/lib/ + +# Modify nginx.conf to load modules +RUN echo 'load_module modules/ngx_http_js_module.so;' > /tmp/nginx.conf && \ + cat /etc/nginx/nginx.conf >> /tmp/nginx.conf && \ + mv /tmp/nginx.conf /etc/nginx/nginx.conf + +# Copy our configuration +COPY conf.d/default.conf /etc/nginx/conf.d/ +COPY njs/main.js /etc/nginx/njs/ + +# Set proper permissions +RUN chown -R nginx:nginx /var/cache/nginx \ + && chown -R nginx:nginx /var/log/nginx \ + && chown -R nginx:nginx /etc/nginx/conf.d \ + && touch /var/run/nginx.pid \ + && chown -R nginx:nginx /var/run/nginx.pid + +# Verify the configuration +# RUN nginx -t --dry-run + +# Switch to non-root user +USER nginx + +# Expose HTTP port +EXPOSE 80 + +# Start Nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/nginx/conf.d/default.conf b/nginx/conf.d/default.conf new file mode 100644 index 0000000..509a0a4 --- /dev/null +++ b/nginx/conf.d/default.conf @@ -0,0 +1,59 @@ +js_import njs/main.js; + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream frontend { + server frontend:5173; +} + +upstream backend { + server backend:3000; +} + +server { + listen 80; + + set $proxy_target ""; + + location /api { + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # Game WebSocket routing + location ~/api/room/([^/]+)/socket { + set $room_id $1; + js_content main.routeWebSocket; + } + + # WebSocket proxy location + location @websocket_proxy { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Timeouts + proxy_connect_timeout 7m; + proxy_send_timeout 7m; + proxy_read_timeout 7m; + proxy_buffering off; + + proxy_pass $proxy_target; + } + + location / { + proxy_pass http://frontend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} \ No newline at end of file diff --git a/nginx/conf.d/local.conf b/nginx/conf.d/local.conf new file mode 100644 index 0000000..083e8eb --- /dev/null +++ b/nginx/conf.d/local.conf @@ -0,0 +1,60 @@ +js_import njs/main.js; +js_set $quiz_room_host main.getQuizRoomHost; + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +# Cache for room information +# keyval_zone zone=rooms:10m; +# keyval_zone zone=room_hosts:10m; +# keyval $room_id $room_info zone=rooms; + +upstream frontend { + server localhost:5173; +} + +upstream backend { + server localhost:3000; +} + +server { + listen 80; + + location /api { + rewrite /backend/(.*) /$1 break; + proxy_pass http://backend; + } + + location /socket.io { + rewrite /backend/(.*) /$1 break; + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_hide_header 'Access-Control-Allow-Origin'; + } + + location /quiz/([^/]+)/socket { + # Routing logic + set $room_id $1; + js_content main.routeWebSocket; + + #Proxy headers + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + proxy_buffering off; + } + + location / { + proxy_pass http://frontend; + } + +} diff --git a/nginx/default.conf b/nginx/default.conf deleted file mode 100644 index b698731..0000000 --- a/nginx/default.conf +++ /dev/null @@ -1,31 +0,0 @@ -upstream frontend { - server frontend:5173; -} - -upstream backend { - server backend:3000; -} - -server { - listen 80; - - location /api { - rewrite /backend/(.*) /$1 break; - proxy_pass http://backend; - } - - location /socket.io { - rewrite /backend/(.*) /$1 break; - proxy_pass http://backend; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; - proxy_set_header Host $host; - proxy_hide_header 'Access-Control-Allow-Origin'; - } - - location / { - proxy_pass http://frontend; - } - -} diff --git a/nginx/njs/main.js b/nginx/njs/main.js new file mode 100644 index 0000000..82b6548 --- /dev/null +++ b/nginx/njs/main.js @@ -0,0 +1,54 @@ +async function fetchRoomInfo(r) { + try { + // Make request to API to get room info + let res = await r.subrequest('/api/room/' + r.variables.room_id, { + method: 'GET' + }); + + if (res.status !== 200) { + r.error(`Failed to fetch room info: ${res.status}`); + return null; + } + + let room = JSON.parse(res.responseText); + r.error(`Debug: Room info: ${JSON.stringify(room)}`); // Debug log + return room; + } catch (error) { + r.error(`Error fetching room info: ${error}`); + return null; + } +} + +async function routeWebSocket(r) { + try { + const roomInfo = await fetchRoomInfo(r); + + if (!roomInfo || !roomInfo.host) { + r.error(`Debug: Invalid room info: ${JSON.stringify(roomInfo)}`); + r.return(404, 'Room not found or invalid'); + return; + } + + // Make sure the host includes protocol if not already present + let proxyUrl = roomInfo.host; + if (!proxyUrl.startsWith('http://') && !proxyUrl.startsWith('https://')) { + proxyUrl = 'http://' + proxyUrl; + } + + r.error(`Debug: Original URL: ${r.uri}`); + r.error(`Debug: Setting proxy target to: ${proxyUrl}`); + r.error(`Debug: Headers: ${JSON.stringify(r.headersIn)}`); + + // Set the proxy target variable + r.variables.proxy_target = proxyUrl; + + // Redirect to the websocket proxy + r.internalRedirect('@websocket_proxy'); + + } catch (error) { + r.error(`WebSocket routing error: ${error}`); + r.return(500, 'Internal routing error'); + } +} + +export default { routeWebSocket }; \ No newline at end of file diff --git a/quizRoom/Dockerfile b/quizRoom/Dockerfile index 8e4af13..4a8d6cb 100644 --- a/quizRoom/Dockerfile +++ b/quizRoom/Dockerfile @@ -1,5 +1,5 @@ # Use the Node base image -FROM node:18 as quizroom +FROM node:18 AS quizroom # Create a working directory WORKDIR /usr/src/app diff --git a/quizRoom/app.ts b/quizRoom/app.ts index f6ec1dc..6848a0c 100644 --- a/quizRoom/app.ts +++ b/quizRoom/app.ts @@ -9,7 +9,7 @@ const port = 4500; // Create HTTP and WebSocket server const server = http.createServer(); const ioOptions: Partial = { - path: "/socket.io", + path: '/api/room/975239/socket', // TODO : use env variable to set room id cors: { origin: "*", methods: ["GET", "POST"], diff --git a/server/controllers/rooms.js b/server/controllers/rooms.js index d969776..af65b39 100644 --- a/server/controllers/rooms.js +++ b/server/controllers/rooms.js @@ -6,7 +6,7 @@ const DockerRoomProvider = require('../roomsProviders/docker-provider.js'); //const KubernetesRoomProvider = require('../roomsProviders/kubernetes-provider'); const NB_CODE_CHARS = 6; -const DEFAULT_HOST = "localhost:4500" +const DEFAULT_HOST = "172.18.0.5:4500" // must be room ip not name class RoomsController { constructor(options = {}, roomRepository) { diff --git a/server/routers/rooms.js b/server/routers/rooms.js index ac0cc13..df63a91 100644 --- a/server/routers/rooms.js +++ b/server/routers/rooms.js @@ -42,15 +42,6 @@ router.delete("/:id", async (req, res) => { }); router.get("/:id", async (req, res) => { - try { - const data = await roomsController.getRoomStatus(); - res.json(data); - } catch (error) { - res.status(500).json({ error: "Failed to join rooms" }); - } -}); - -router.get("/:id/status", async (req, res) => { try { const data = await roomsController.getRoomStatus(req.params.id); res.json(data); @@ -59,6 +50,4 @@ router.get("/:id/status", async (req, res) => { } }); - - module.exports = router;