Files
sam-docs/dev/guides/r2-image-proxy-guide.md

8.5 KiB

R2 이미지 프록시 가이드

작성일: 2026-03-21 상태: 운영 중


1. 개요

SAM 프로젝트는 파일 저장소로 Cloudflare R2 (S3 호환)를 사용한다. MNG에서 R2 이미지를 표시할 때 환경(Docker/서버)과 용도(일반 표시/Canvas 편집/미리보기)에 따라 다른 접근 방식이 필요하다.

핵심 문제

브라우저 → R2 직접 접근: CORS 차단 (Canvas에서 사용 불가)
Docker 내부 → api.sam.kr: DNS 해석 불가 (500 에러)
브라우저 JS → https://nginx: Docker 내부 URL 접근 불가

2. 이미지 접근 경로 3가지

2.1 일반 <img> 표시 — redirect 방식

브라우저 → /files/{id}/view → MNG FileViewController → API presigned URL → 302 redirect → R2
  • 용도: 목록/상세 화면의 이미지 표시
  • 라우트: GET /files/{id}/viewFileViewController@show
  • 동작: API에서 presigned URL을 받아 브라우저를 R2로 redirect
  • 장점: 빠름 (서버에서 이미지 다운로드 안 함)
  • 제한: Canvas에서 사용 불가 (redirect 후 cross-origin → tainted canvas)

2.2 Canvas 편집기 — streaming 프록시

브라우저 → /files/{id}/proxy → MNG FileViewController → R2 다운로드 → 이미지 스트리밍
  • 용도: 절곡품 전개도 Canvas 편집기 (fabric.Image.fromURL)
  • 라우트: GET /files/{id}/proxyFileViewController@proxy
  • 동작: MNG 서버가 R2에서 이미지를 다운로드하여 같은 도메인으로 스트리밍
  • 장점: CORS 문제 없음, toDataURL() 정상 동작
  • 제한: file_id가 필요 (image_path만 있으면 사용 불가)

2.3 미리보기 모달 — MNG API 프록시

브라우저 JS → /api/admin/document-templates/presigned-url-by-path → MNG API → API 서버 → R2 presigned URL 반환
  • 용도: 문서양식 미리보기에서 섹션 이미지 (image_path만 있는 경우)
  • 라우트: POST /api/admin/document-templates/presigned-url-by-path
  • 동작: 브라우저 JS가 MNG API를 호출 → MNG가 API 서버에 presigned URL 요청 → URL 반환
  • 장점: file_id 없이 image_path로 접근 가능
  • 주의: 동기 XHR 사용 (미리보기 렌더링 시 순차 처리)

3. 환경별 설정

3.1 Docker (로컬)

# api/.env
R2_ACCESS_KEY_ID=cecd4d4c...
R2_SECRET_ACCESS_KEY=f20136ec...
R2_BUCKET=sam
R2_ENDPOINT=https://caf8dcb2c4ea443018ee5e7a7421db0e.r2.cloudflarestorage.com
R2_REGION=auto
# mng/.env (Docker 내부 통신)
API_BASE_URL=https://api.sam.kr
API_INTERNAL_URL=https://nginx

3.2 서버 (개발/운영)

# api/.env — R2 설정 동일
# mng/.env
API_BASE_URL=https://api.dev.codebridge-x.com
# API_INTERNAL_URL 미설정 (직접 접근)

4. MNG → API 호출 시 필수 패턴

MNG에서 API를 호출할 때 API_INTERNAL_URL 분기 처리가 필수이다.

$baseUrl = config('services.api.base_url', 'https://api.sam.kr');
$internalUrl = config('services.api.internal_url');

$headers = [
    'X-API-KEY' => config('services.api.key'),
    'X-TENANT-ID' => session('selected_tenant_id', 1),
];

// Docker: nginx 컨테이너 경유, Host 헤더로 서버 블록 라우팅
if ($internalUrl) {
    $headers['Host'] = parse_url($baseUrl, PHP_URL_HOST) ?: 'api.sam.kr';
    $baseUrl = $internalUrl;
}

$response = Http::baseUrl($baseUrl)
    ->withoutVerifying()
    ->withHeaders($headers)
    ->timeout(10)
    ->get('/api/v1/...');

참조 구현: FormulaApiService::resolveApiConnection()


5. API 화이트리스트

MNG에서 Bearer 토큰 없이 호출하는 API는 ApiKeyMiddlewareallowWithoutAuth에 등록 필요:

api/v1/bending-items             절곡 기초관리
api/v1/bending-items/*           절곡 기초관리 상세
api/v1/guiderail-models          가이드레일 모델
api/v1/guiderail-models/*        가이드레일 모델 상세
api/v1/items/*/files             품목 파일
api/v1/files/*/presigned-url     파일 presigned URL
api/v1/files/presigned-url-by-path  경로 기반 presigned URL

파일 위치: api/app/Http/Middleware/ApiKeyMiddleware.php


6. 트러블슈팅

이미지가 404로 나올 때

  1. R2 설정 확인: API .envR2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET, R2_ENDPOINT 존재 여부
  2. API 캐시 클리어: docker exec sam-api-1 php artisan config:clear
  3. R2 파일 존재 확인: Storage::disk('r2')->exists('경로')

이미지가 401로 나올 때

  1. 화이트리스트 확인: ApiKeyMiddlewareallowWithoutAuth에 해당 라우트 등록 여부
  2. X-API-KEY 확인: config('services.api.key') 값이 api_keys 테이블에 존재하는지
  3. X-TENANT-ID 확인: session('selected_tenant_id')

Canvas에서 tainted canvas 에러

  1. 프록시 사용 확인: /files/{id}/view(redirect) 대신 /files/{id}/proxy(streaming) 사용
  2. data-proxy-url 속성: <img> 태그에 data-proxy-url="{{ route('files.proxy', $fileId) }}" 추가
  3. JS에서 프록시 URL 우선: current.dataset.proxyUrl || current.src

Docker에서 api.sam.kr 연결 실패 (cURL error 7)

  1. API_INTERNAL_URL 설정: MNG .envAPI_INTERNAL_URL=https://nginx
  2. Host 헤더 추가: $headers['Host'] = parse_url($baseUrl, PHP_URL_HOST)
  3. 참조: BendingBaseController::api(), FileViewController, DocumentTemplateController

미리보기에서 섹션 이미지 안 나올 때

  1. MNG API 프록시 확인: /api/admin/document-templates/presigned-url-by-path 라우트 존재 여부
  2. image_url 캐시: _previewImageUrl 함수에서 한 번 조회 후 section.image_url에 캐시
  3. 브라우저 콘솔: XHR 요청 상태 확인 (200이면 정상, 401이면 화이트리스트, 500이면 R2 설정)

7. 관련 파일

파일 역할
mng/app/Http/Controllers/FileViewController.php show(redirect), proxy(streaming)
mng/routes/web.php /files/{id}/view, /files/{id}/proxy
mng/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php presignedUrlByPath (미리보기용)
mng/resources/views/document-templates/partials/preview-modal.blade.php _previewImageUrl 함수
mng/app/Http/Controllers/BendingBaseController.php api() 메서드 (internal_url 패턴)
mng/app/Http/Controllers/DocumentTemplateController.php getPresignedUrlFromApi, getPresignedUrlByPath
api/app/Http/Middleware/ApiKeyMiddleware.php allowWithoutAuth 화이트리스트
api/config/filesystems.php R2 디스크 설정 (disks.r2)

8. 요약 다이어그램

                        ┌──────────────────────────────────┐
                        │          Cloudflare R2           │
                        │      (S3 호환 파일 저장소)       │
                        └──────────┬───────────────────────┘
                                   │ presigned URL
                        ┌──────────┴───────────────────────┐
                        │         API 서버 (Laravel)        │
                        │  /api/v1/files/{id}/presigned-url │
                        │  /api/v1/files/presigned-url-by-path │
                        └──────────┬───────────────────────┘
                                   │
              ┌────────────────────┼────────────────────┐
              │                    │                    │
   ┌──────────┴──────────┐ ┌──────┴──────┐ ┌──────────┴──────────┐
   │  /files/{id}/view   │ │ /files/{id} │ │ /api/admin/doc-tmpl │
   │  (redirect → R2)   │ │  /proxy     │ │ /presigned-url-by-  │
   │                     │ │ (streaming) │ │ path (MNG API)      │
   │  일반 <img> 표시   │ │ Canvas 편집 │ │ 미리보기 모달       │
   └─────────────────────┘ └─────────────┘ └─────────────────────┘

최종 업데이트: 2026-03-21