[Error] 이미지 서버전송 CORS 에러
커스텀 페이지에서 제작한 그림판 시안을 서버로 전송할때 생긴 CORS에러 해결
발생한 에러
에러 해석
서버로 이미지를 전송하는 과정에서 CORS에러 발생
GET 요청이 보내진 주소: https://mainproject022.s3.ap-northeast-2.amazonaws.com/ingredient/topping_strawberry side.png 요청을 보낸 도메인: http://localhost:5173
요청을 보낸 도메인인 http://localhost:5173
과 이미지를 호스팅하는 도메인인https://mainproject022.s3.ap-northeast-2.amazonaws.com
간에 CORS 헤더가 설정되지 않아서 발생한 것
CORS에 대해서
CORS
는 웹 페이지에서 다른 도메인의 리소스에 접근할 때 브라우저가 적용하는 보안 정책이다. 기본적으로동일 출처 정책(Same-Origin Policy)
에 의해 다른 도메인의 리소스에 접근하려는 시도는 차단된다. 이 때, CORS 정책을 적용하여 동일 출처 정책을 느슨하게 해서, 웹 페이지에서 특정 조건하에서 다른 도메인의 리소스에 접근할 수 있게 해준다.
해결방식
이미지를 호스팅하는 서버에서 CORS 헤더를 설정하여 특정 도메인에서의 요청을 허용하도록 구성
서버 측에서 Access-Control-Allow-Origin
헤더를 설정하여 허용할 도메인을 지정
처음에는 서버 측에서 헤더에 CORS 설정을 넣어서 해결하는 간단한 문제일줄 알았지만 백엔드 코드를 수정한 뒤에도 문제가 지속됐다.
문제 해결
전체 코드
import { RefObject } from 'react';
import { addCustom } from '../../../api/customApis';
import { LocalStorage } from '../../../utils/browserStorage';
import { LOCAL_STORAGE_KEY_LIST } from '../../../assets/constantValue/constantValue';
// 이미지 데이터 형식 정의
type ImageData = {
imageUrl: string;
x: number;
y: number;
width: number;
height: number;
};
// 이미지를 저장하는 함수
const saveAsImage = async (
images: ImageData[],
canvasRef: RefObject<HTMLCanvasElement>,
storeId: number,
productId: number,
navigate: (path: string) => void
) => {
// 캔버스와 컨텍스트 확인
const canvas = canvasRef.current;
if (!canvas) {
return;
}
const ctx = canvas.getContext('2d');
if (!ctx) {
console.error('Context 2D is not available');
return;
}
// 임시 캔버스 생성 및 이미지 그리기
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
if (!tempCtx) {
console.error('Failed to get 2D context for temporary canvas');
return;
}
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
tempCtx.drawImage(canvas, 0, 0);
// Data URL 검사 함수 정의
const isDataURL = (s: string) => {
return !!s.match(/^data:image\/([a-zA-Z]*);base64,([^"]*)/);
};
// 이미지 로드 및 그리기 함수 정의
const loadAndDrawImage = async (imageData: ImageData) => {
return new Promise<void>((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
if (isDataURL(imageData.imageUrl)) {
img.src = imageData.imageUrl;
} else {
img.src = `${imageData.imageUrl}?timestamp=${new Date().getTime()}`;
}
img.onload = () => {
tempCtx.drawImage(img, imageData.x, imageData.y, imageData.width, imageData.height);
resolve();
};
img.onerror = () => {
reject(new Error(`Failed to load image at ${imageData.imageUrl}`));
};
});
};
// 이미지 로드 및 그리기 함수들을 Promise.all로 실행
await Promise.all(images.map(loadAndDrawImage));
// 이미지 데이터의 불투명한 부분 처리
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
for (let i = 0; i < imageData.data.length; i += 4) {
if (imageData.data[i + 3] === 0) {
imageData.data[i] = 255;
imageData.data[i + 1] = 255;
imageData.data[i + 2] = 255;
imageData.data[i + 3] = 255;
}
}
tempCtx.putImageData(imageData, 0, 0);
// 이미지를 Blob 형식으로 변환
const dataUrl = tempCanvas.toDataURL('image/png');
const byteString = atob(dataUrl.split(',')[1]);
const arrayBuffer = new ArrayBuffer(byteString.length);
const int8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
int8Array[i] = byteString.charCodeAt(i);
}
const blob = new Blob([int8Array], { type: 'image/png' });
const file = new File([blob], 'canvas.png', { type: 'image/png' });
// 로그인 상태 확인 후 이미지 추가
const accessToken = LocalStorage.get(LOCAL_STORAGE_KEY_LIST.AccessToken);
if (accessToken) {
try {
await addCustom(storeId, productId, file);
return true;
} catch (error) {
alert('이미지 저장에 실패했습니다.');
return false;
}
} else {
alert('로그인 먼저 해주세요!');
navigate('/auth');
return false;
}
};
export default saveAsImage;
문제가 됐던 부분
// 이미지 로드 및 그리기 함수 정의
const loadAndDrawImage = async (imageData: ImageData) => {
return new Promise<void>((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
if (isDataURL(imageData.imageUrl)) {
img.src = imageData.imageUrl;
} else {
img.src = `${imageData.imageUrl}?timestamp=${new Date().getTime()}`;
}
img.onload = () => {
tempCtx.drawImage(img, imageData.x, imageData.y, imageData.width, imageData.height);
resolve();
};
img.onerror = () => {
reject(new Error(`Failed to load image at ${imageData.imageUrl}`));
};
});
};
img.crossOrigin = "anonymous";
img.crossOrigin = 'anonymous';
는 이미지 요청 시 CORS 정책을 적용하기 위한 설정, 해당 이미지의 원 서버에서 CORS 헤더를 올바르게 설정하고 응답할 경우, 브라우저는 이미지에 대해 CORS를 적용하고 사용자의 권한을 확인하게 된다.
이 설정을 사용하면 이미지를 다운로드할 때 원 서버로부터 허용된 경우에만 이미지를 다운로드 받을 수 있고, 다른 경우에는 차단된다.
댓글남기기