|
@@ -1,16 +1,20 @@
|
|
|
import { Socket as ClientSocket, io } from 'socket.io-client'
|
|
|
import { Server, Socket as SocketForConnectedClient } from "socket.io"
|
|
|
-import { Observable, Subject } from "rxjs";
|
|
|
+import { Observable, Observer, Subject } from "rxjs";
|
|
|
import { createServer, request as httpRequest } from "http";
|
|
|
-import http, { IncomingMessage, ServerResponse } from 'http';
|
|
|
-
|
|
|
+import express, { Response } from 'express';
|
|
|
+import { Express } from 'express';
|
|
|
+import { postAxiosRequest } from '../utils/http.utils';
|
|
|
+import axios from 'axios';
|
|
|
let fromServer = new Subject<{ event: 'profile' | 'message', payload: any }>()
|
|
|
let toServer = new Subject<{ event: 'profile' | 'message', payload: any }>()
|
|
|
|
|
|
-startSocketServer(3001)
|
|
|
+// startSocketServer(3001)
|
|
|
// startSocketServer(3002)
|
|
|
-startClientSocketConnection('http://localhost:3000')
|
|
|
-// startHttpServer(3001, 'http://localhost:3000/response')
|
|
|
+// startClientSocketConnection('http://localhost:3000')
|
|
|
+startHttpServer(3001).then((app: Express) => {
|
|
|
+ operateHttpServer(app, 'http://localhost:3000/')
|
|
|
+})
|
|
|
consoleLog()
|
|
|
|
|
|
function consoleLog(): void {
|
|
@@ -55,6 +59,7 @@ function startSocketServer(port: number): void {
|
|
|
|
|
|
}
|
|
|
|
|
|
+
|
|
|
function startClientSocketConnection(serverUrl: string): void {
|
|
|
// let clientSocket = io(serverUrl)
|
|
|
let clientSocket: ClientSocket = io(serverUrl, {
|
|
@@ -84,187 +89,143 @@ function startClientSocketConnection(serverUrl: string): void {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+async function startHttpServer(port: number): Promise<Express> {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ let app: Express = express();
|
|
|
+ // Middleware to parse JSON requests
|
|
|
+ app.use(express.json());
|
|
|
+ app.listen(port, () => {
|
|
|
+ console.log({ message: `Server running at http://localhost:${port}` });
|
|
|
+ });
|
|
|
+ resolve(app)
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
-function startHttpServer(port: number, clientUrl: string): void {
|
|
|
- // Create an observable from the client connection
|
|
|
- const clientObservable = startHttpClientConnection(clientUrl);
|
|
|
-
|
|
|
- const server = http.createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
|
- const { method, url } = req;
|
|
|
-
|
|
|
- if (method === 'POST' && url === '/profile') {
|
|
|
- // Forward the /profile request to another server (e.g., localhost:3000)
|
|
|
- let body = '';
|
|
|
- req.on('data', chunk => (body += chunk));
|
|
|
- req.on('end', () => {
|
|
|
- const options = {
|
|
|
- hostname: 'localhost',
|
|
|
- port: 3000,
|
|
|
- path: '/profile',
|
|
|
- method: 'POST',
|
|
|
- headers: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- 'Content-Length': Buffer.byteLength(body),
|
|
|
- },
|
|
|
- };
|
|
|
-
|
|
|
- const forwardReq = http.request(options, forwardRes => {
|
|
|
- let forwardBody = '';
|
|
|
- forwardRes.on('data', chunk => (forwardBody += chunk));
|
|
|
- forwardRes.on('end', () => {
|
|
|
- res.writeHead(forwardRes.statusCode || 200, { 'Content-Type': 'application/json' });
|
|
|
- res.end(forwardBody); // Send back the forwarded response
|
|
|
- });
|
|
|
- });
|
|
|
-
|
|
|
- forwardReq.on('error', error => {
|
|
|
- console.error('Error forwarding /profile request:', error);
|
|
|
- res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
|
- res.end(JSON.stringify({ error: 'Failed to forward request' }));
|
|
|
- });
|
|
|
-
|
|
|
- forwardReq.write(body); // Send the original request body
|
|
|
- forwardReq.end();
|
|
|
- });
|
|
|
- } else if (method === 'POST' && url === '/response') {
|
|
|
- // Handle long-polling using the observable
|
|
|
- res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
|
-
|
|
|
- const subscription = clientObservable.subscribe({
|
|
|
- next: data => {
|
|
|
- res.write(JSON.stringify(data)); // Send data to the client
|
|
|
- res.end(); // Close the response for this poll
|
|
|
- subscription.unsubscribe(); // Unsubscribe after sending one response
|
|
|
- },
|
|
|
- error: error => {
|
|
|
- console.error('Error in observable:', error);
|
|
|
- res.write(JSON.stringify({ error: 'Error occurred' }));
|
|
|
- res.end();
|
|
|
- },
|
|
|
- });
|
|
|
-
|
|
|
- // Cleanup subscription if client disconnects
|
|
|
- req.on('close', () => {
|
|
|
- subscription.unsubscribe();
|
|
|
- });
|
|
|
- } else {
|
|
|
- // Default 404 handler
|
|
|
- res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
|
- res.end(JSON.stringify({ error: 'Not Found' }));
|
|
|
- }
|
|
|
- });
|
|
|
+function operateHttpServer(app: Express, url: string): void {
|
|
|
+ app.post('/profile', (req, res) => {
|
|
|
+ postAxiosRequest(url + `profile`, req.body).then((response) => {
|
|
|
+ res.json(response)
|
|
|
+ }).catch((error) => {
|
|
|
+ console.log(error)
|
|
|
+ res.json(error)
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
- server.listen(port, () => {
|
|
|
- console.log(`Server is running on http://localhost:${port}`);
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-
|
|
|
-/**
|
|
|
- * Starts a long-polling HTTP connection to the given URL and channels responses into an Observable.
|
|
|
- * @param url - The URL to send the long-polling requests to.
|
|
|
- * @returns Observable<any> - An observable emitting the response data for each poll.
|
|
|
- */
|
|
|
-function startHttpClientConnection(url: string): Observable<any> {
|
|
|
- return new Observable(subscriber => {
|
|
|
- const poll = () => {
|
|
|
- const options = new URL(url);
|
|
|
-
|
|
|
- const req = http.request(
|
|
|
- {
|
|
|
- hostname: options.hostname,
|
|
|
- port: options.port,
|
|
|
- path: options.pathname + options.search,
|
|
|
- method: 'GET',
|
|
|
- },
|
|
|
- res => {
|
|
|
- let body = '';
|
|
|
-
|
|
|
- // Accumulate data chunks
|
|
|
- res.on('data', chunk => {
|
|
|
- body += chunk;
|
|
|
- });
|
|
|
-
|
|
|
- // On response end, emit the data and start the next poll
|
|
|
- res.on('end', () => {
|
|
|
- try {
|
|
|
- const parsedData = JSON.parse(body);
|
|
|
- subscriber.next(parsedData);
|
|
|
- poll(); // Start the next poll
|
|
|
- } catch (error: any) {
|
|
|
- subscriber.error('Error parsing response: ' + error.message);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- );
|
|
|
+ app.post('/message', (req, res) => {
|
|
|
+ postAxiosRequest(url + `message`, req.body).then((response) => {
|
|
|
+ console.log(response)
|
|
|
+ res.json(response)
|
|
|
+ }).catch((error) => {
|
|
|
+ console.log(error)
|
|
|
+ res.json(error)
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
- // Handle request errors
|
|
|
- req.on('error', error => {
|
|
|
- subscriber.error('Request failed: ' + error.message);
|
|
|
- });
|
|
|
+ app.get('/poll', (req, res) => {
|
|
|
+ console.log('Client connected for long polling.');
|
|
|
+ // Flag to track if the response has been sent
|
|
|
+ let responseSent = false;
|
|
|
+ // Subscribe to the data stream
|
|
|
+ const subscription = handleClientHttpConnection(url).subscribe({
|
|
|
+ next: (message: any) => {
|
|
|
+ if (!responseSent) {
|
|
|
+ console.log(`Sending data to client: ${JSON.stringify(message)}`);
|
|
|
+ res.json(message); // Send the data to the client
|
|
|
+ responseSent = true; // Mark response as sent
|
|
|
+ subscription.unsubscribe(); // Unsubscribe to close this request
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error: (err: any) => {
|
|
|
+ if (!responseSent) {
|
|
|
+ console.error('Error in data stream:');
|
|
|
+ res.status(500).send('Internal Server Error');
|
|
|
+ responseSent = true; // Mark response as sent
|
|
|
+ }
|
|
|
+ subscription.unsubscribe(); // Ensure cleanup
|
|
|
+ },
|
|
|
+ complete: () => {
|
|
|
+ if (!responseSent) {
|
|
|
+ console.log('Data stream completed.');
|
|
|
+ res.status(204).send(); // No Content
|
|
|
+ responseSent = true; // Mark response as sent
|
|
|
+ }
|
|
|
+ subscription.unsubscribe(); // Ensure cleanup
|
|
|
+ },
|
|
|
+ });
|
|
|
|
|
|
- req.end(); // Send the request
|
|
|
- };
|
|
|
+ // Timeout if no data is emitted within a specified duration
|
|
|
+ const timeout = setTimeout(() => {
|
|
|
+ if (!responseSent) {
|
|
|
+ console.log({ message: 'No data emitted. Sending timeout response.' });
|
|
|
+ res.status(204).send(); // No Content
|
|
|
+ responseSent = true; // Mark response as sent
|
|
|
+ subscription.unsubscribe(); // Ensure cleanup
|
|
|
+ }
|
|
|
+ }, 15000); // 15 seconds timeout (adjust as needed)
|
|
|
|
|
|
- poll(); // Start the polling loop
|
|
|
+ // Handle client disconnection
|
|
|
+ res.on('close', () => {
|
|
|
+ if (!responseSent) {
|
|
|
+ console.error(`Http Client disconnected`);
|
|
|
|
|
|
- // Cleanup logic: teardown observable if unsubscribed
|
|
|
- return () => {
|
|
|
- console.log('Stopping long-polling connection.');
|
|
|
- };
|
|
|
+ subscription.unsubscribe(); // Ensure cleanup
|
|
|
+ }
|
|
|
+ clearTimeout(timeout); // Clear timeout to avoid unnecessary execution
|
|
|
+ });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
+// For client usage
|
|
|
+export function handleClientHttpConnection(url: string): Observable<any> {
|
|
|
+ return new Observable((eventNotification: Observer<any>) => {
|
|
|
+ let active: boolean = true; // Flag to control polling lifecycle
|
|
|
|
|
|
-// Utility function to parse JSON body
|
|
|
-const parseBody = (req: IncomingMessage): Promise<any> => {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- let body = '';
|
|
|
- req.on('data', chunk => {
|
|
|
- body += chunk;
|
|
|
- });
|
|
|
- req.on('end', () => {
|
|
|
- try {
|
|
|
- resolve(JSON.parse(body));
|
|
|
- } catch (error) {
|
|
|
- reject(error);
|
|
|
+ const longPoll = async () => {
|
|
|
+ while (active) {
|
|
|
+ try {
|
|
|
+ // Axios request with timeout
|
|
|
+ const response = await axios.get(`${url}poll`); // removing the timeout temporarily.
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = response.data;
|
|
|
+ eventNotification.next(data)
|
|
|
+ } else if (response.status === 204) {
|
|
|
+ console.log('No new messages from the server.');
|
|
|
+ } else {
|
|
|
+ throw new Error(`Unexpected response status: ${response.status}`);
|
|
|
+ }
|
|
|
+ } catch (error: unknown) {
|
|
|
+ console.error(`Unknown Error.`) // culprit is here
|
|
|
+ // Error handling with server disconnect notification
|
|
|
+ let errorMessage: string;
|
|
|
+
|
|
|
+ if (axios.isAxiosError(error)) {
|
|
|
+ if (error.response) {
|
|
|
+ errorMessage = `Server returned status ${error.response.status}: ${error.response.statusText}`;
|
|
|
+ } else if (error.code === 'ECONNABORTED') {
|
|
|
+ errorMessage = 'Request timed out.';
|
|
|
+ } else {
|
|
|
+ errorMessage = error.message || 'An Axios error occurred.';
|
|
|
+ }
|
|
|
+ } else if (error instanceof Error) {
|
|
|
+ errorMessage = error.message;
|
|
|
+ } else {
|
|
|
+ errorMessage = 'An unknown error occurred during polling.';
|
|
|
+ }
|
|
|
+
|
|
|
+ console.error(`Polling error: ${errorMessage}`);
|
|
|
+ // observer.error(new Error(errorMessage)); // Notify subscribers of the error
|
|
|
+ break; // Stop polling on error
|
|
|
+ }
|
|
|
}
|
|
|
- });
|
|
|
- req.on('error', reject);
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-// Function to forward request to another server and get the response
|
|
|
-const forwardRequest = (data: any): Promise<any> => {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const options = {
|
|
|
- hostname: 'localhost',
|
|
|
- port: 3000,
|
|
|
- path: '/profile',
|
|
|
- method: 'POST',
|
|
|
- headers: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- 'Content-Length': Buffer.byteLength(JSON.stringify(data)),
|
|
|
- },
|
|
|
};
|
|
|
|
|
|
- const req = httpRequest(options, res => {
|
|
|
- let responseBody = '';
|
|
|
- res.on('data', chunk => {
|
|
|
- responseBody += chunk;
|
|
|
- });
|
|
|
- res.on('end', () => {
|
|
|
- try {
|
|
|
- resolve(JSON.parse(responseBody));
|
|
|
- } catch (error) {
|
|
|
- reject(error);
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
+ longPoll();
|
|
|
|
|
|
- req.on('error', reject);
|
|
|
- req.write(JSON.stringify(data));
|
|
|
- req.end();
|
|
|
+ // Cleanup logic for unsubscribing
|
|
|
+ return () => {
|
|
|
+ console.log({ message: 'Unsubscribed from the long-polling channel.' });
|
|
|
+ eventNotification.complete(); // Notify completion
|
|
|
+ };
|
|
|
});
|
|
|
-};
|
|
|
+}
|