
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
/**
* @author : 하석형
* @since : 2023.08.24
* @dscription : Express 라이브러리 활용 HTTP Web Server 모듈입니다.
*/
const { BASE_DIR, PORT, API_SERVER_HOST } = require("../../../Global");
const Logger = require("../log/Logger"); //Logger(필수)
const express = require("express");
const webServer = express();
const expressProxy = require("express-http-proxy");
//파일 시스템 관련 라이브러리
const FS = require("fs");
//stream: 특정 자원을 Streaming 하기 위한 라이브러리 => Transform: Streaming 중인 자원의 Data에 Data 수정 및 추가를 지원해주는 객체
const Transform = require("stream").Transform;
//Streaming 중인 자원에 새로운 데이터를 stream 공간에 추가하기 위한 라이브러리
const newLineStream = require("new-line");
const axios = require('axios');
// 정적 파일 제공을 위한 설정
const path = require('path');
webServer.use(express.static('public'));
webServer.timeout = 200000;
// 정상적으로 동작하는 호스트를 찾아 요청을 보내는 미들웨어
webServer.use(async function (req, res, next) {
// 작업페이지
// res.status(503).sendFile(path.join(__dirname, 'public', 'error.html'));
// return;
const selectedServer = await findHealthyHost();
if (!selectedServer) {
res.status(503).send('All servers are down');
// res.status(503).sendFile(path.join(__dirname, 'public', 'error.html'));
return;
}
// 선택된 호스트로 요청을 보냄
req.selectedServer = selectedServer.inside;
req.selectedOutSideServer = selectedServer.outside;
next();
});
// Health Check 함수
async function checkHealth(serverUrl) {
try {
// Health Check API 호출
const response = await axios.get(`${serverUrl.inside}/cmmn/health-check.json`,{
timeout : 500,
});
// HTTP 상태 코드가 200인 경우에만 정상 동작으로 판단
if (response.status === 200) {
return true;
}
} catch (error) {
console.error(`Health check failed for server ${serverUrl.inside}: ${error.message}`);
}
return false;
}
// 배열을 무작위로 섞는 함수
function shuffle(array) {
const shuffledArray = array.slice(); // 원본 배열 복사
for (let i = shuffledArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]]; // 요소들을 무작위로 섞음
}
return shuffledArray;
}
webServer.use((req, res, next) => {
res.header("X-Frame-Options", "SAMEORIGIN"); // or 'SAMEORIGIN' or 'ALLOW-FROM uri'
next();
});
// 모든 호스트에 대한 헬스 체크를 수행하여 정상적으로 동작하는 호스트를 찾음
async function findHealthyHost() {
let healthyHost = null;
const shuffledHosts = shuffle(API_SERVER_HOST); // 호스트를 무작위로 섞음
// 각 호스트에 대한 헬스 체크를 수행
for (const serverUrl of shuffledHosts) {
if (await checkHealth(serverUrl)) {
// 현재 호스트가 정상적으로 동작하면 선택하고 루프를 종료
healthyHost = serverUrl;
break;
}
}
return healthyHost;
}
/**
* @author : 하석형
* @since : 2023.08.24
* @dscription : HTTP Server start
*/
webServer.listen(PORT, function () {
Logger.logging(`★★★ Node.js를 활용한 Web Server 구동(Port:${PORT}) ★★★`);
});
/**
* @author : 하석형
* @since : 2023.08.24
* @dscription : Intercepter 역할을 하는 미들웨어 기능
*/
webServer.use(function (request, response, next) {
let ip = request.headers["x-forwarded-for"] || request.socket.remoteAddress;
Logger.logging(
`[HTTP] ${request.url} (Method: ${request.method}, IP: ${ip})`
);
next();
});
/**
* @author : 김성원
* @since : 2024.05.30
* @dscription : robots.txt
*/
webServer.get("/robots.txt", function (request, response) {
//response.sendFile을 통한 HTTP html reponse (html내용 Streaming)
response.sendFile(`${BASE_DIR}/client/views/robots.txt`);
});
/**
* @author : 하석형
* @since : 2023.08.24
* @dscription : ROOT URL -> index.html
*/
webServer.get("/", function (request, response) {
//response.sendFile을 통한 HTTP html reponse (html내용 Streaming)
response.sendFile(`${BASE_DIR}/client/views/index.html`);
});
/**
* @author : 하석형
* @since : 2023.08.24
* @dscription : 화면요청 URL 처리
*/
webServer.get("*.page", function (request, response, next) {
//index.html 내용을 직접 Streaming하여 Response, Streaming 중간에 내용 수정
//수정 내용 : URL 요청이 아닌, 브라우저에 표시된 URL만 변경하여, 해당하는 URL PATH의 Vue Component를 routing하기 위함
const StreamTransform = new Transform();
StreamTransform._transform = function (data, encoding, done) {
let fileContent = data.toString();
let replaceBeforeContent = `<script id="app-start-vue-page">const APP_USER_HTTP_REQUEST_URL = '/';</script>`;
let replaceAfterContent = `<script id="app-start-vue-page">const APP_USER_HTTP_REQUEST_URL = '${request.params["0"]}.page';</script>`;
fileContent.replace(replaceBeforeContent, replaceAfterContent);
this.push(fileContent);
done();
};
//Streaming 진행
FS.createReadStream(`${BASE_DIR}/client/views/index.html`)
.pipe(newLineStream())
.pipe(StreamTransform)
.pipe(response);
});
/**
* @author : 김성훈
* @since : 2024.04.16
* @dscription : 이미지 불러오기
*/
webServer.use('/nas_data/files/*', expressProxy(function (req, res) {
return req.selectedServer;
}, {
proxyReqPathResolver: function (request) {
// Logger.logging('/AIDT/FILES/* : ' + `/AIDT/FILES/${request.params['0']}`);
return `/nas_data/files/${request.params['0']}`;
}
}));
/**
* @author : 하석형
* @since : 2023.08.24
* @dscription : REST API 서버에 데이터 요청 보내기(Proxy)
*/
webServer.use('*.json', expressProxy(function (req, res) {
// Logger.logging('json selectedServer : ' + req.selectedServer);
return req.selectedServer;
}, {
timeout: 120000,
proxyReqPathResolver: function (request) {
return `${request.params['0']}.json`;
},
proxyReqOptDecorator: function (proxyReqOpts, srcReq) {
proxyReqOpts.headers['X-Forwarded-For'] = srcReq.headers['x-forwarded-for'] || srcReq.connection.remoteAddress;
return proxyReqOpts;
}
}));
/**
* @author : 하석형
* @since : 2023.08.24
* @dscription : REST API 서버에 데이터 요청 보내기(Proxy)
*/
// webServer.use(
// "*.json",
// expressProxy(API_SERVER_HOST, {
// proxyReqPathResolver: function (request) {
// return `${request.params["0"]}.json`;
// },
// proxyReqOptDecorator: function (proxyReqOpts, srcReq) {
// proxyReqOpts.headers['X-Forwarded-For'] = srcReq.headers['x-forwarded-for'] || srcReq.connection.remoteAddress;
// return proxyReqOpts;
// }
// })
// );
/**
* @author : 하석형
* @since : 2023.08.24
* @dscription : REST API 서버에 데이터 요청 보내기(Proxy)
*/
webServer.use('*.file', expressProxy(function (req) {
// Logger.logging('file selectedServer : ' + req.selectedServer);
return req.selectedServer;
}, {
parseReqBody : false,
proxyReqPathResolver: function (request) {
return `${request.params['0']}.file`;
}
}));
// /**
// * @author : 하석형
// * @since : 2023.08.24
// * @dscription : REST API 서버에 데이터 요청 보내기(Proxy)
// */
// webServer.use(
// "*.file",
// expressProxy(API_SERVER_HOST, {
// parseReqBody: false,
// proxyReqPathResolver: function (request) {
// console.log("request : ", request.url, request.params[0]);
// return `${request.params["0"]}.file`;
// },
// })
// );
/**
* @author : 하석형
* @since : 2023.08.24
* @dscription : ROOT URL, Router's, 화면요청 URL 등.. 이 외 나머지 정적 자원에 대한 처리 기능
*/
webServer.get("*.*", function (request, response, next) {
response.sendFile(`${BASE_DIR}${request.params["0"]}.${request.params["1"]}`);
});
/**
* @author : 김성훈
* @since : 2024.04.19
* @dscription : 404 오류 처리
*/
webServer.use(function(req, res, next) {
res.status(404).redirect('/notFound.page');
});
/**
* @author : 하석형
* @since : 2023.08.24
* @dscription : Global Error Handler (*맨 마지막에 적용해야됨)
*/
webServer.use(function (error, request, response, next) {
const errorCode = !error.statusCode ? 500 : error.statusCode;
//response
// .status(errorCode)
// .send("에러가 발생하였습니다. 관리자에게 문의바랍니다.");
let message = `[Error:${errorCode}] ${request.url}/n ${error.stack}/n`;
Logger.logging(message);
response.status(errorCode).redirect('/notFound.page');
//next();
});