생성된 파일 다운로드 링크 사용에 대한 중요 업데이트

Tripo 사용자 여러분께,

저희 플랫폼의 리소스에 액세스하고 사용하는 방식에 영향을 미칠 수 있는 예정된 변경 사항에 대해 알려드립니다.

무엇이 변경됩니까?

모든 생성된 아티팩트 URL에 CORS 정책을 적용할 예정입니다. 이는 저희 플랫폼에서 호스팅되는 모든 리소스가 교차 출처 요청에 대한 적절한 처리를 요구하게 되며, 다시 말해 Tripo API가 반환하는 링크를 사용하여 생성된 모델 파일을 직접 제공할 수 없게 됩니다.

왜 이렇게 변경됩니까?

이러한 변경은 보안을 강화하고 다른 출처 간의 리소스에 대한 무단 접근을 방지하려는 저희의 노력의 일환입니다. CORS(Cross-Origin Resource Sharing)는 서버가 어떤 외부 도메인이 리소스에 접근할 수 있는지 지정할 수 있도록 하는 메커니즘입니다.
CORS에 대한 자세한 정보는 MDN의 가이드를 참조하실 수 있습니다.

워크플로우에 미치는 영향

현재 저희는 아티팩트 URL에 대한 CORS 헤더를 제공하지 않습니다. 결과적으로, 브라우저에서 또는 외부 도메인에서 이러한 리소스를 직접 사용하거나 연결하는 경우 문제가 발생할 수 있습니다.
이러한 자산에 계속 액세스하려면 다음 조치 중 하나를 취해야 합니다.

  1. 아티팩트를 로컬로 다운로드 및 재저장: 아티팩트를 다운로드하여 백엔드에 저장할 수 있습니다. 로컬에 저장되면 CORS 제한 없이 서버에서 이러한 자산을 제공할 수 있습니다.
  2. 엣지 프록시 사용 (예: Next.js API Route, Cloudflare Workers): 엣지 프록시를 사용하여 아티팩트를 가져오고 필요한 CORS 헤더를 응답에 추가하여 제공할 수 있습니다. 예를 들어:
    • Next.js API Route: 저희 플랫폼에서 리소스를 가져와 적절한 CORS 헤더와 함께 전달하도록 API 라우트를 설정합니다.
    • Cloudflare Workers: Cloudflare Workers를 사용하여 저희 자산을 서버에서 제공할 때 CORS 헤더를 추가합니다.

준비 방법:

  • 통합 검토: 시스템이 아티팩트 URL에 대한 CORS를 처리할 수 있는지 확인합니다.
  • 아티팩트 다운로드 및 저장: 엣지 프록시를 사용하지 않는 경우, 아티팩트를 백엔드에 다운로드하여 저장하는 것을 고려하십시오.
  • 엣지 프록시 설정: Next.js 또는 Cloudflare Workers와 같은 솔루션을 사용하는 경우, 자산이 적절한 CORS 헤더와 함께 제공되도록 구성을 업데이트합니다.
  • 시스템 테스트: 저희 플랫폼에 대한 모든 교차 출처 요청이 예상대로 작동하는지 확인합니다.

실용적인 코드 예제가 필요하십니까? 시작하는 방법을 모르거나 구현이 어렵다고 느끼신다면, 시작하는 데 도움이 되는 실용적인 코드 스니펫을 제공해 드렸습니다. 자세한 내용은 부록을 참조하십시오.

FAQ

  • 생성된 파일이 손실됩니까?

    아닙니다. 언제든지 API에서 새로운 URL을 검색할 수 있습니다.

  • 귀사의 URL을 직접 반환하지는 않지만, 후처리 작업을 수행합니다. 우려해야 할까요?

    사용자에게 저희 URL을 직접 반환하지 않는 경우, 이 변경 사항을 건너뛸 수 있습니다. 그러나 특히 시스템에서 오류 급증 또는 비정상적인 동작이 감지되는 경우 사용량을 모니터링하는 것이 좋습니다.

도움이 필요하십니까?

이 변경으로 인해 워크플로우를 업데이트해야 할 수 있다는 점을 이해합니다. 엣지 프록시 설정에 도움이 필요하거나 이 전환에 대해 궁금한 점이 있으면 **support@tripo3d.ai**로 저희 지원팀에 문의하십시오.
저희 플랫폼의 보안을 강화하기 위한 여러분의 협조에 감사드립니다! 안전하고 생산적인 활동을 하시길 바랍니다!
감사합니다.
Tripo 팀 드림

부록 A: 백엔드 재저장 방식

FastAPI를 사용하고 있다면 (다른 프레임워크나 언어로 쉽게 포팅하거나 번역할 수 있습니다), CORS를 처리하고 아티팩트를 로컬에 저장하도록 기존 코드를 조정하는 방법은 다음과 같습니다.

현재 코드:

@app.post("/")
def generate(prompt: str) -> str:
    resp = ...  # API works
    return resp.json()['url']

다음과 같이 쉽게 수정할 수 있습니다.

import httpx
import uuid

from fastapi.responses import FileResponse
from fastapi import HTTPException

@app.post("/")
def generate(prompt: str) -> str:
    resp = ...  # same as above

    # Add the code below
    file_id = str(uuid.uuid4())
    with httpx.Client() as client:
        # Download the artifact
        response = client.get(url)

        # Check if the request was successful
        if response.status_code == 200:
            # Then resave it
            with open(f"./downloaded/{file_id}.glb", "wb") as file:
                file.write(response.content)

    return file_id

@app.get("/artifact/{file_id}")
def download(file_id: str) -> FileResponse:
    if os.path.exists(f"./downloaded/{file_id}.glb"):
      return FileResponse(f"./downloaded/{file_id}.glb")
    raise HTTPException(status_code=404, detail="Item not found")

파일을 로컬에 다운로드하고 저장한 후에는 새 URL을 사용자에게 제공해야 합니다. 예를 들어, 애플리케이션이 https://app.example.com에 호스팅되어 있다면, 제공해야 할 URL은 다음과 같을 것입니다.

https://app.example.com/artifact/<file_id>

이 코드 스니펫은 통합을 조정하는 방법을 보여주는 시연일 뿐입니다. 그러나 몇 가지 중요한 사항을 고려해야 합니다.

  • 적절한 인증 추가: 무단 액세스를 방지하기 위해 다운로드 링크가 적절한 인증 방법으로 보호되는지 확인하십시오.
  • 신뢰할 수 있는 저장소 사용: 파일을 전용 서버 또는 S3 버킷과 같은 클라우드 저장소 옵션과 같이 더 신뢰할 수 있는 저장소 솔루션에 저장하는 것이 좋습니다.
  • 메타데이터 저장: 향후 사용(예: 추적 또는 감사)을 위해 아티팩트 메타데이터를 데이터베이스 또는 다른 저장 시스템에 기록해 두십시오.

부록 B: 엣지 프록시 방식

단일 페이지 애플리케이션(SPA) 또는 백엔드 포 프론트엔드(BFF) 서버를 개발 중이며 파일 다운로드 및 저장소를 서버에서 직접 처리하는 것을 선호하지 않는다면, 엣지 프록시 방식을 사용할 수 있습니다. 이를 통해 저희 서버에서 파일을 가져와 필요한 CORS 헤더를 적용한 다음 사용자에게 제공할 수 있습니다.

해결책 1: 엣지 함수를 사용하여 파일을 다운로드하고 다시 전송하기

다음은 Cloudflare Workers를 사용하여 이 방식을 구현하는 방법에 대한 예시입니다. 이 해결책은 파일을 다운로드하고 귀하의 도메인으로 다시 전송하여 CORS 문제를 처리할 것입니다.

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  // Parse the request URL
  const url = new URL(request.url);

  // Get the target URL from the 'url' query parameter
  const targetUrl = url.searchParams.get('url');

  // If no URL is provided, return an error
  if (!targetUrl) {
    return new Response('Please provide a URL parameter', {
      status: 400,
      headers: {
        'Content-Type': 'text/plain',
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type'
      }
    });
  }

  // Handle preflight OPTIONS request
  if (request.method === 'OPTIONS') {
    return handleCORS();
  }

  try {
    // Fetch the file from the target URL
    const response = await fetch(targetUrl);

    // If the fetch failed, return the error
    if (!response.ok) {
      return new Response(`Failed to fetch from target URL: ${response.statusText}`, {
        status: response.status,
        headers: corsHeaders()
      });
    }

    // Get the content type from the response or default to octet-stream
    const contentType = response.headers.get('Content-Type') || 'application/octet-stream';

    // Get the content disposition or create one from the URL
    let contentDisposition = response.headers.get('Content-Disposition');
    if (!contentDisposition) {
      // Extract filename from the URL
      const fileName = targetUrl.split('/').pop().split('?')[0] || 'file';
      contentDisposition = `attachment; filename="${fileName}"`;
    }

    // Create a new response with CORS headers
    const newResponse = new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: {
        'Content-Type': contentType,
        'Content-Disposition': contentDisposition,
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type',
        'Cache-Control': 'public, max-age=3600' // Cache for 1 hour
      }
    });

    return newResponse;
  } catch (error) {
    return new Response(`Error fetching the file: ${error.message}`, {
      status: 500,
      headers: corsHeaders()
    });
  }
}

// Handle CORS preflight requests
function handleCORS() {
  return new Response(null, {
    status: 204, // No content
    headers: corsHeaders()
  });
}

// Create CORS headers object
function corsHeaders() {
  return {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Max-Age': '86400' // 24 hours
  };
}

Cloudflare Worker가 설정되면 다음 JavaScript 코드를 사용하여 파일을 가져와 SPA에 통합할 수 있습니다.

fetch('https://your-worker-url.workers.dev/?url=<target_url>')
  .then(response => response.blob())
  .then(blob => {
    // Create a download link
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'downloaded-file.pdf';
    document.body.appendChild(a);
    a.click();
    a.remove();
  })
  .catch(error => console.error('Error:', error));

이 해결책은 최종 사용자에게 파일을 다운로드하고 제공하면서 CORS 제한을 우회해야 하는 SPA 또는 BFF 서버에 적합합니다. 파일은 대상 URL에서 가져와 엣지 프록시를 통해 전달되며 필요한 CORS 헤더와 함께 제공됩니다.

해결책 2: CORS 헤더 프록시 설정

간단한 CORS 프록시를 설정하려면 Cloudflare에서 제공하는 다음 예제를 참조하십시오: Cloudflare Workers - CORS Header Proxy
이를 통해 CORS 정책을 준수하면서 교차 출처 요청을 더 쉽게 처리할 수 있습니다.

중요 고려 사항:

  • 인증: 파일에 대한 무단 액세스를 방지하기 위해 인증 방법(예: API 키, OAuth 등)으로 엣지 함수를 적절히 보호해야 합니다.
  • 저장소: 이 방식이 효과적이지만, 대규모 다운로드를 관리하거나 리소스에 대한 안정적인 액세스를 보장해야 하는 경우, 향후 사용을 위해 아티팩트를 서버에 저장하는 것이 여전히 권장됩니다.

Advancing 3D generation to new heights

moving at the speed of creativity, achieving the depths of imagination.