본문 바로가기
나의 IT 기억

🎯iframe 없이 서버 간 웹 페이지 통합하기: HTML Fetch 방식으로 마이크로서비스 UI 통합

by 浪畅 (Làng Chàng) 2025. 5. 31.

https://github.com/onesound71/WebContentIntegration

 

GitHub - onesound71/WebContentIntegration

Contribute to onesound71/WebContentIntegration development by creating an account on GitHub.

github.com

 

빨간 부분에 버튼을 누르면 WEB SERVER B 화면을 컨트롤

 

웹 개발을 하다 보면 여러 서버의 웹 페이지를 하나로 통합해야 하는 상황이 생깁니다. 보통은 iframe을 사용하는 것이 일반적이지만, iframe은 여러 제약사항이 있죠. 스타일링의 어려움, 보안 정책, 반응형 디자인의 한계 등등...

그래서 iframe 없이 서버 A에서 서버 B의 웹 페이지를 완전히 컨트롤할 수 있는 방법을 실험해봤습니다. 완벽한 해결책은 아니지만, 이런 식으로도 구현이 가능하구나 정도로 이해해주시면 됩니다. 더 좋은 방법이 생각나면 다시 공유하겠습니다!

핵심 아이디어: HTML Fetch 통합

기본 아이디어는 간단합니다:

  1. 서버 A서버 B의 완성된 HTML을 통째로 가져온다
  2. 가져온 HTML을 필요에 따라 수정한다
  3. 클라이언트에게 마치 서버 A의 페이지인 것처럼 제공한다
  4. JavaScript API 호출은 절대 URL로 서버 B를 직접 타겟팅한다
@app_a.get("/", response_class=HTMLResponse)
async def read_root():
    async with httpx.AsyncClient(timeout=10.0) as client:
        # 서버 B의 대시보드 HTML을 통째로 가져오기
        response = await client.get(f"{SERVER_B_URL}/dashboard")
        if response.status_code == 200:
            html_content = response.text
            # 필요시 HTML 수정 (스크립트 경로 등)
            modified_html = modify_html_for_integration(html_content)
            return HTMLResponse(content=modified_html)

프로젝트 구조와 실행

간단한 구조

WebContentIntegrationDemo/
├── server_a.py          # 메인 서버 (포트 8000)
├── server_b.py          # 데이터 서버 (포트 8001)
├── requirements.txt     # FastAPI, uvicorn, httpx 등
└── test_buttons.html    # 테스트 페이지

실행 방법

# 터미널 1: 데이터 서버 먼저 실행
python server_b.py

# 터미널 2: 메인 서버 실행
python server_a.py

그러면 http://localhost:8000에 접속했을 때, 서버 B의 대시보드가 서버 A를 통해 제공됩니다.

주요 구현 포인트

1. JavaScript API 라우팅

가장 중요한 부분입니다. 통합된 페이지에서 API 호출이 올바른 서버로 가도록 절대 URL을 사용합니다:

// ✅ 올바른 방법: 절대 URL로 서버 B 타겟팅
async function refreshWeather() {
    const response = await fetch('http://localhost:8001/api/weather', {
        method: 'GET',
        mode: 'cors',
        headers: {'Content-Type': 'application/json'}
    });

    if (response.ok) {
        const data = await response.json();
        updateWeatherDisplay(data);
    }
}

// ❌ 이렇게 하면 서버 A로 요청이 가버림
// const response = await fetch('/api/weather');

2. CORS 설정

서버 간 통신을 위해 CORS 설정이 필수입니다:

from fastapi.middleware.cors import CORSMiddleware

app_b.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:8000"],  # 서버 A 도메인
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

3. 실시간 데이터 통합

통합된 페이지에서 여러 API를 동시에 호출하여 실시간 데이터를 보여줍니다:

async function refreshAllData() {
    const startTime = performance.now();

    try {
        // 병렬로 여러 API 호출
        const [weatherResponse, analyticsResponse, usersResponse] = 
            await Promise.all([
                fetch('http://localhost:8001/api/weather'),
                fetch('http://localhost:8001/api/analytics'),
                fetch('http://localhost:8001/api/users')
            ]);

        // 응답 처리 및 UI 업데이트
        const weather = await weatherResponse.json();
        const analytics = await analyticsResponse.json();
        const users = await usersResponse.json();

        updateDashboard(weather, analytics, users);

    } catch (error) {
        console.error('데이터 로드 실패:', error);
        showErrorMessage('일부 데이터를 불러올 수 없습니다.');
    }

    const totalTime = Math.round(performance.now() - startTime);
    console.log(`전체 데이터 로드 시간: ${totalTime}ms`);
}

구현된 주요 기능들

실시간 모니터링 대시보드

  • 사용자 통계, 상품 정보, 방문자 현황
  • 날씨 정보 및 시스템 상태
  • 자동/수동 데이터 새로고침

API 테스트 시스템

  • 개별 API 성능 테스트
  • 전체 API 일괄 테스트
  • 응답 시간 및 데이터 크기 분석
  • 실시간 테스트 로그

에러 처리 및 헬스 체크

@app_a.get("/health")
async def health_check():
    try:
        # 서버 B 상태 확인
        async with httpx.AsyncClient(timeout=5.0) as client:
            response = await client.get(f"{SERVER_B_URL}/health")
            server_b_status = response.status_code == 200
    except:
        server_b_status = False

    return {
        "server_a": "healthy",
        "server_b": "healthy" if server_b_status else "unreachable",
        "integration": "active" if server_b_status else "degraded"
    }

장단점 분석

장점

  • iframe의 제약 없음: 스타일링 자유도, 반응형 디자인 가능
  • 단일 도메인 경험: 사용자 입장에서는 하나의 사이트처럼 느껴짐
  • 실시간 통합: 서버 간 데이터를 자유롭게 조합 가능
  • 성능 모니터링: 각 API 호출 시간을 정밀하게 측정 가능

단점

  • 서버 부하: 모든 요청이 서버 A를 거쳐야 함
  • 복잡성: 에러 처리, 라우팅 등이 복잡해짐
  • 보안 고려사항: CORS 설정, API 경로 노출 등
  • 실시간성 제한: 서버 B의 실시간 업데이트를 완전히 반영하기 어려움

실제 활용 사례

이런 방식은 다음과 같은 상황에서 유용할 수 있습니다:

마이크로서비스 UI 통합

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   사용자     │    │  주문 서비스   │    │  재고 서비스   │
│   대시보드    │◄──►│      UI     │    │      UI     │
│  (서버 A)    │    │  (서버 B)    │    │  (서버 C)    │
└─────────────┘    └─────────────┘    └─────────────┘

레거시 시스템 통합

기존 시스템의 UI를 큰 수정 없이 새로운 플랫폼에 통합할 때 유용합니다.

개선 가능한 부분들

현재 구현은 실험적 성격이 강합니다. 실제 운영에서는 다음 사항들을 고려해야 합니다:

캐싱 전략

# HTML 응답 캐싱으로 성능 향상
@lru_cache(maxsize=100)
async def get_cached_dashboard():
    # 캐시된 HTML 반환
    pass

더 나은 라우팅

  • API Gateway 패턴 적용
  • 로드 밸런싱 고려
  • 서비스 디스커버리 연동

보안 강화

  • JWT 토큰 기반 인증
  • API 키 관리
  • Rate Limiting

마치며

iframe 없이 서버 간 웹 페이지를 통합하는 실험을 해봤습니다. 완벽한 해결책은 아니지만, "이런 식으로도 구현이 가능하구나" 정도로 이해해주시면 됩니다.

실제로는 다음과 같은 대안들도 고려해볼 만합니다:

  • Module Federation (Webpack 5)
  • Single-SPA 같은 마이크로프론트엔드 프레임워크
  • Server-Side Includes (SSI)
  • Edge Side Includes (ESI)

더 좋은 방법이나 개선 아이디어가 생각나면 다시 공유하겠습니다!

혹시 비슷한 요구사항이 있으시거나 다른 접근 방법을 시도해보신 분이 계시다면 댓글로 공유해주세요. 함께 더 나은 방법을 찾아보면 좋겠습니다! 🚀