-
This commit is contained in:
@@ -85,8 +85,9 @@ const MIME_TYPES = {
|
||||
'.woff': 'font/woff'
|
||||
};
|
||||
|
||||
// Gzip 압축 전송 유틸
|
||||
function sendResponse(req, res, statusCode, contentType, body, extraHeaders = {}) {
|
||||
// Gzip 압축 전송 유틸 (HEAD 요청 규격 완벽 대응 및 사전 압축 버퍼 즉시 전송)
|
||||
function sendResponse(req, res, statusCode, contentType, body, extraHeaders = {}, preGzipped = null) {
|
||||
const isHead = req.method === 'HEAD';
|
||||
const acceptEncoding = req.headers['accept-encoding'] || '';
|
||||
const headers = {
|
||||
'Content-Type': contentType,
|
||||
@@ -98,21 +99,29 @@ function sendResponse(req, res, statusCode, contentType, body, extraHeaders = {}
|
||||
}
|
||||
|
||||
if (acceptEncoding.includes('gzip') && body.length > 512) {
|
||||
if (preGzipped) {
|
||||
headers['Content-Encoding'] = 'gzip';
|
||||
headers['Content-Length'] = preGzipped.length;
|
||||
res.writeHead(statusCode, headers);
|
||||
res.end(isHead ? undefined : preGzipped);
|
||||
return;
|
||||
}
|
||||
zlib.gzip(body, (err, compressed) => {
|
||||
if (err) {
|
||||
headers['Content-Length'] = body.length;
|
||||
res.writeHead(statusCode, headers);
|
||||
res.end(body);
|
||||
res.end(isHead ? undefined : body);
|
||||
return;
|
||||
}
|
||||
headers['Content-Encoding'] = 'gzip';
|
||||
headers['Content-Length'] = compressed.length;
|
||||
res.writeHead(statusCode, headers);
|
||||
res.end(compressed);
|
||||
res.end(isHead ? undefined : compressed);
|
||||
});
|
||||
} else {
|
||||
headers['Content-Length'] = body.length;
|
||||
res.writeHead(statusCode, headers);
|
||||
res.end(body);
|
||||
res.end(isHead ? undefined : body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,21 +186,36 @@ const server = http.createServer(async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 끝 슬래시 정규화 (루트 '/' 제외)
|
||||
const pathname = rawPathname.length > 1 && rawPathname.endsWith('/')
|
||||
? rawPathname.replace(/\/+$/, '')
|
||||
: rawPathname;
|
||||
// 끝 슬래시 정규화 (루트 '/' 제외, 끝 슬래시 요청 시 표준 URL로 301 영구 리다이렉트하여 중복 색인 방지)
|
||||
if (rawPathname.length > 1 && rawPathname.endsWith('/')) {
|
||||
const normalized = rawPathname.replace(/\/+$/, '');
|
||||
res.writeHead(301, {
|
||||
Location: normalized,
|
||||
'Cache-Control': 'public, max-age=86400'
|
||||
});
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
const pathname = rawPathname;
|
||||
|
||||
const isAdmin = pathname === '/admin' || pathname.startsWith('/admin/') || pathname.endsWith('/admin');
|
||||
|
||||
// 2. 캐시 확인 (인메모리 캐시 히트 시 0.001초 응답, 관리자 경로 제외)
|
||||
// 2. 캐시 확인 (인메모리 캐시 히트 시 사전 압축 버퍼로 0.0001초 즉시 응답, 관리자 경로 제외)
|
||||
if (!isAdmin) {
|
||||
const cached = getCachedHtml(pathname);
|
||||
if (cached) {
|
||||
sendResponse(req, res, cached.statusCode, 'text/html; charset=utf-8', cached.html, {
|
||||
'X-Cache': 'HIT',
|
||||
'Cache-Control': 'public, max-age=3600'
|
||||
});
|
||||
sendResponse(
|
||||
req,
|
||||
res,
|
||||
cached.statusCode,
|
||||
'text/html; charset=utf-8',
|
||||
cached.html,
|
||||
{
|
||||
'X-Cache': 'HIT',
|
||||
'Cache-Control': 'public, max-age=3600'
|
||||
},
|
||||
cached.gzipped
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -234,13 +258,26 @@ const server = http.createServer(async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 캐시에 저장 (다음 요청부터 0.001초 응답)
|
||||
setCachedHtml(pathname, { statusCode: 200, html: fullHtml });
|
||||
// 캐시에 원본 Buffer 및 Gzip 압축 Buffer 함께 저장 (대량 봇 크롤링 시 CPU 부하 제로화)
|
||||
const htmlBuf = Buffer.from(fullHtml, 'utf-8');
|
||||
let gzippedBuf = null;
|
||||
try {
|
||||
gzippedBuf = zlib.gzipSync(htmlBuf);
|
||||
} catch {}
|
||||
setCachedHtml(pathname, { statusCode: 200, html: htmlBuf, gzipped: gzippedBuf });
|
||||
|
||||
sendResponse(req, res, 200, 'text/html; charset=utf-8', fullHtml, {
|
||||
'X-Cache': 'MISS',
|
||||
'Cache-Control': 'public, max-age=3600'
|
||||
});
|
||||
sendResponse(
|
||||
req,
|
||||
res,
|
||||
200,
|
||||
'text/html; charset=utf-8',
|
||||
htmlBuf,
|
||||
{
|
||||
'X-Cache': 'MISS',
|
||||
'Cache-Control': 'public, max-age=3600'
|
||||
},
|
||||
gzippedBuf
|
||||
);
|
||||
return;
|
||||
} catch (err) {
|
||||
console.error(`[SSR Error] ${pathname} 렌더링 중 오류:`, err);
|
||||
|
||||
Reference in New Issue
Block a user