[issue] Failed to Fetch

김연찬·2024년 10월 1일

목표

FastAPI와 JS에서 통신하기

배경

ComfyUI에서 서버를 실행 중 (로컬 환경)
ComfyUI와 통신하는 service.py 서버 실행중 (로컬 환경)
client에서 service.py와 fetch async를 통해 통신

그러나 통신 중 결과를 받지 못하고 Failed to Fetch 오류가 발생

main.py

from fastapi import FastAPI, HTTPException, File, UploadFile
from pydantic import BaseModel
import json
from urllib import request
import asyncio
from fastapi.middleware.cors import CORSMiddleware
import os
from fastapi import FastAPI, Request
import logging

app = FastAPI()

# CORS 설정
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 모든 출처 허용
    allow_credentials=True,
    allow_methods=["*"],  # 모든 HTTP 메소드 허용
    allow_headers=["*"],  # 모든 헤더 허용
)

COMFYUI_URL = "127.0.0.1:8188"  # ComfyUI 서버 IP 및 포트

# 요청 데이터 모델 정의
class WorkflowRequest(BaseModel):
    workflow: dict

# 응답 데이터 모델 정의
class WorkflowResponse(BaseModel):
    image: str  # base64 인코딩된 이미지 데이터

def queue_prompt(prompt_workflow, ip):
    p = {"prompt": prompt_workflow}
    data = json.dumps(p).encode('utf-8')
    req = request.Request(f"http://{ip}/prompt", data=data)
    try:
        res = request.urlopen(req)
        if res.code != 200:
            raise Exception(f"Error: {res.code} {res.reason}")
        return json.loads(res.read().decode('utf-8'))['prompt_id']
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

async def check_progress(prompt_id: str, ip: str):
    while True:
        try:
            req = request.Request(f"http://{ip}/history/{prompt_id}")
            res = request.urlopen(req)
            if res.code == 200:
                history = json.loads(res.read().decode('utf-8'))
                if prompt_id in history:
                    return history[prompt_id]
        except Exception as e:
            print(f"Error checking progress: {str(e)}")
        await asyncio.sleep(1)  # 1초 대기

@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI!"}
   
@app.post("/workflow/cloth")
async def cloth(img_model: UploadFile = File(...), img_product: UploadFile = File(...)):
    try:
        # 이미지를 저장할 경로 설정
        for img in [img_model, img_product]:
            image_path = f"./ComfyUI/input/{img.filename}"
            with open(image_path, "wb") as f:
                f.write(await img.read())

        # 저장된 이미지 경로를 워크플로우에 포함
        with (open("./workflow/cloth_api.json", "r", encoding="utf-8")) as f:
            workflow_request = WorkflowRequest(workflow=json.loads(f.read()))

        # 이미지 경로를 워크플로우에 추가
        workflow_request.workflow["3"]["inputs"]["image"] = img_model.filename
        workflow_request.workflow["4"]["inputs"]["image"] = img_product.filename

        # ComfyUI에 워크플로우 전송
        prompt_id = queue_prompt(workflow_request.workflow, COMFYUI_URL)
        result = await check_progress(prompt_id, COMFYUI_URL)

        # 결과에서 마지막 이미지 URL 추출
        final_image_url = None
        for node_id, node_output in result['outputs'].items():
            if 'images' in node_output:
                for image in node_output['images']:
                    final_image_url = f"http://{COMFYUI_URL}/view?filename={image['filename']}&type=temp"

        # 반환할 이미지 URL
        if final_image_url:
            return {"status": "completed", "images": final_image_url}
        else:
            return {"status": "completed", "images": None}
        
    except HTTPException as e:
        raise e
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

# Uvicorn 실행
import uvicorn

if __name__ == "__main__":
    uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)

clinet.html

async function generateImage() {
            const selectedImage1 = document.getElementById('selectedImage1');
            const selectedImage2 = document.getElementById('selectedImage2');
            const ballContainer = document.querySelector('.ball-container');
            const imagePreviewDiv = document.querySelector('.image-preview');
            const imagePlaceholder = document.getElementById('image-placeholder');
            const loadingText = document.createElement('p');
            loadingText.id = 'loading-text';
            loadingText.innerText = '이미지 생성중...';
            loadingText.style.marginTop = '-30px';

            if (!selectedFromTab1 || !selectedFromTab2) {
                alert('상품 이미지와 연예인 이미지를 모두 선택해주세요.');
                return;
            }

            try {
                imagePlaceholder.style.display = 'none';
                ballContainer.style.display = 'grid';
                ballContainer.insertAdjacentElement('afterend', loadingText);

                const formData = new FormData();
                const modelImage = await fetch(selectedImage2.querySelector('img').src).then(res => res.blob());
                const productImage = await fetch(selectedImage1.querySelector('img').src).then(res => res.blob());

                formData.append("img_model", modelImage, "baekjongwon.jpg"); // Blob 추가
                formData.append("img_product", productImage, "img-cloth.jpg"); // Blob 추가
                
                const response = await fetch(COMFYUI_SERVER_URL + "workflow/cloth", {
                    method: 'POST',
                    headers: {
                        'Accept' : '*/*'
                    },
                    body: formData
                });

                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }

                const data = await response.json();

                setTimeout(() => {
                    ballContainer.style.display = 'none';
                    loadingText.remove();

                    if (data.status === "completed" && data.images) {
                        const resultImg = document.createElement('img');
                        resultImg.src = data.images;
                        resultImg.alt = "Generated Image";
                        imagePreviewDiv.innerHTML = '';
                        imagePreviewDiv.appendChild(resultImg);
                    } else {
                        throw new Error('Image generation failed');
                    }
                }, 5000);

            } catch (error) {
                console.error('Error:', error);
                alert(`오류가 발생했습니다: ${error.message}`);
                ballContainer.style.display = 'none';
                loadingText.remove();
                imagePlaceholder.style.display = 'block';
            }
        }

시행착오

  • Failed to Fetch 오류 검색
  • CORS 문제 확인
  • API header 문제 확인

원인

  • 전부 로컬환경에서 작업하여 발생하는 문제
  • ComfyUI 서버환경을 실행하고 있으면 이유는 모르겠으나 로컬환경을 재시작하는 것같음
  • 재시작하면서 fetch 연결이 끊기 되며 발생하는 오류

해결

  • python -m http.server를 실행하여 환경을 분리하여 문제 해결

0개의 댓글