ai-analysis.md, threat-intel-ai-service의 /chat 모두 오래전에 구현은 끝나 있었지만, static/index.html 대시보드에는 연결된 적이 없었다 — WebSocket으로 표적 위치 보여주는 것과 시뮬레이터 버튼, 딸 두 가지만 붙어 있었다. 이 문서는 그 연결을 붙이면서 실제로 드러난 것들을 정리한다.
① AI 위협분석 모달 — 표적 카드마다 "🔍 AI 위협분석" 버튼을 추가해서 이 서비스 자체의 /api/v1/threat-analysis/analyze를 호출하고 결과를 모달로 띄운다. 자기 자신의 API를 부르는 거라 별다른 라우팅 이슈가 없다.
② AI 챗봇 패널 — threat-intel-ai-service(별도 Python 리포)의 /chat을 SSE로 스트리밍해서 채팅 UI로 보여준다. 다른 서비스를 부르는 거라 문제가 하나 있었다.
threat-intel-ai-service는 별개 k8s Service(ClusterIP)라, 브라우저가 직접 절대 URL을 호출하게 하려면 두 벣에 부딛힌다:
두 문제를 한 번에 피하는 방법은 같은 오리진으로 만드는 것이었다. threat-intel-ai-service가 자기 라우터를 /ai prefix로 한 번 더 등록하고, 이 서비스가 이미 쓰고 있는 Traefik Ingress에 /ai 경로 규칙을 하나 추가해서, 브라우저 입장에서는 그냥 같은 사이트의 다른 경로(/ai/chat)를 부르는 것처럼 보이게 만들었다.
브라우저의 표준 SSE 클라이언트인 EventSource는 GET 요청만 지원한다. /chat은 질문을 body로 보내는 POST라 EventSource를 쓸 수 없다. 대신 fetch + ReadableStream으로 직접 파싱했다:
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const blocks = buffer.split('\n\n');
buffer = blocks.pop();
for (const block of blocks) handleSseBlock(block, assistantEl);
}
SSE 프레임은 빈 줄(nn)로 구분되므로, 청크가 올 때마다 버퍼에 쌓고 완성된 블록만 잘라서 처리한다.
위협분석 버튼을 실제로 눌러보니 500 에러가 났다:
com.google.genai.errors.ClientException: 404 . This model models/gemini-2.5-flash is no longer available to new users.
application.yaml이 여전히 gemini-2.5-flash를 가리키고 있었다 — threat-intel-ai-service(Python)에서 또같은 404를 격고 gemini-flash-lite-latest로 바꿔 게 며칠 전인데, 같은 계정의 Java 서비스에도 같은 문제가 있다는 걸 그때 확인 안 했다. curl로 /status만 확인하면 aiEnabled: true가 나오니(API 키 존재 여부만 확인) 정상처럼 보였고, /analyze를 실제로 호출해본 적이 최근 며칠간 없었다.
이게 이번 작업의 핵심 교훈: 기능이 코드로 구현돼 있고 상태 엔드포인트가 "정상"이라고 답해도, 그 기능을 실제로 실행하는 경로가 무언가에 연결되어 있지 않으면 회귀(regression)가 조용히 쌓인다.
<table header-row="true"><tr><td>붙인 것</td><td>부르는 대상</td><td>격은 문제</td><td>해결</td></tr><tr><td>AI 위협분석 모달</td><td>자신의 /analyze</td><td>gemini-2.5-flash 404</td><td>gemini-flash-lite-latest로 교체</td></tr><tr><td>AI 챗봇 패널</td><td>threat-intel-ai-service /chat</td><td>CORS + mixed content</td><td>Traefik /ai path same-origin화</td></tr><tr><td>챗봇 스트리밍</td><td>SSE(POST 필요)</td><td>EventSource POST 미지원</td><td>fetch+ReadableStream 수동 파싱</td></tr></table>