연동하면서 있었던 문제점
CORS를 양쪽에서 풀어주어야 함.
1. CORS 문제의 원인
CORS 문제는 기본적으로 서로 다른 도메인, 포트, 또는 프로토콜 간의 요청이 발생할 때 브라우저가 이를 보안 상 막으려 하기 때문에 발생합니다. 예를 들어:
- Spring Backend: http://localhost:8080
- FastAPI Backend: http://localhost:8000
두 서버가 서로 다른 도메인/포트를 사용하므로, 브라우저에서 FastAPI가 Spring 서버에 요청을 보낼 때 CORS 문제가 발생할 수 있습니다.
2. 해결 방법
(1) Spring에서 CORS 설정
Spring에서 CORS를 허용하려면 @CrossOrigin 애너테이션 또는 전역 설정을 추가해야 합니다.
a. 특정 컨트롤러/메서드에 CORS 허용
import org.springframework.web.bind.annotation.CrossOrigin;
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://localhost:8000") // FastAPI의 주소
public class MyController {
@GetMapping("/data")
public ResponseEntity<String> getData() {
return ResponseEntity.ok("Hello from Spring");
}
}
b. 전역적으로 CORS 허용
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8000") // FastAPI의 주소
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true);
}
}
(2) FastAPI에서 CORS 설정
FastAPI에서도 CORS 설정을 추가해야 합니다. 이를 위해 fastapi.middleware.cors.CORSMiddleware를 사용합니다.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# CORS 설정
origins = [
"http://localhost:8080", # Spring의 주소
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/data")
def read_data():
return {"message": "Hello from FastAPI"}
(3) 프록시 설정 / 이런 방법도 있다. 나중에 공부!
브라우저에서 CORS 문제를 피하려면 프록시를 사용할 수도 있습니다. 프론트엔드와 백엔드 간 요청을 동일한 도메인으로 보이게 처리하는 방식입니다. 이 방법은 CORS를 피하는 간단한 대안이 될 수 있습니다.
- Spring 서버에서 FastAPI 서버의 요청을 프록시 처리하거나,
- React/Vue 등의 프론트엔드에서 프록시 설정을 추가합니다.
컨트롤러
@Controller
@RequestMapping("/chatbot")
public class ChatController {
private final RestTemplate restTemplate = new RestTemplate();
@Value("${fastapi.url}")
private String fastApiUrl;
@PostMapping("/chat")
public ResponseEntity<String> askQuestion(@RequestBody Map<String, String> questionMap) {
System.out.println("FastAPI URL: " + fastApiUrl); // fastApiUrl 확인
try {
ResponseEntity<String> response = restTemplate.postForEntity(fastApiUrl, questionMap, String.class);
return ResponseEntity.ok(response.getBody());
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body("잘못된 요청 데이터: " + e.getMessage());
} catch (Exception e) {
return ResponseEntity.status(503).body("FastAPI 서버와 통신 중 오류 발생: " + e.getMessage());
}
}
@GetMapping("/chat")
public String getChat() {
return "chatForm"; // templates/chat.html 파일을 렌더링
}
}
폼파일(html)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat with AI</title>
</head>
<body>
<h1>AI와의 대화</h1>
<form id="chatForm">
<label for="question">질문을 입력하세요:</label>
<input type="text" id="question" name="question" required placeholder="예: 오늘 저녁 뭐야?">
<button type="submit">보내기</button>
</form>
<h2>응답:</h2>
<div id="response"></div>
<script>
document.getElementById('chatForm').addEventListener('submit', async function (event) {
event.preventDefault();
const question = document.getElementById('question').value;
const responseDiv = document.getElementById('response');
try {
const response = await fetch('http://localhost/chatbot/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query: question })
});
if (response.ok) {
const responseData = await response.json();
responseDiv.innerText = responseData.response || "응답 데이터가 없습니다.";
} else {
responseDiv.innerText = `Error: ${response.status}`;
}
} catch (error) {
responseDiv.innerText = `요청 중 에러가 발생했습니다: ${error.message}`;
}
// 입력 필드 초기화
document.getElementById('question').value = "";
});
</script>
</body>
</html>
py파일
from fastapi import FastAPI
from database import Base, engine
from items import router as item_router
from fastapi.middleware.cors import CORSMiddleware
from langchain_upstage import ChatUpstage
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from schemas import Query
from fastapi import Depends, HTTPException
from sqlalchemy.orm import Session
from database import get_db
app = FastAPI()
# CORS 설정 추가
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost"], # Spring Boot가 실행되는 주소
allow_credentials=True,
allow_methods=["*"], # 허용할 HTTP 메서드 (GET, POST 등)
allow_headers=["*"], # 허용할 HTTP 헤더
)
Base.metadata.create_all(bind=engine) # 스키마만 만들어놓으면 알아서 테이블을 만들어준다.
llm = ChatUpstage(model="solar-pro", api_key="up_YTwTGo0l41tcgLP7vXCMGG9hRL4gh")
prompt_template = PromptTemplate.from_template("""
당신은 세상에서 가장 친절한 CS상담원 입니다.
내 이름은 더브라위너야
고객의 요청에 적절한 반응을 해주세요.
---
질문: {question}
""")
chain = prompt_template | llm | StrOutputParser()
app.include_router(item_router)
@app.get("/")
def index():
return{"Hello": "World"}
@app.post("/chat/")
async def chat(query: Query, db: Session = Depends(get_db)):
try:
response = chain.invoke(query)
print(f"response : {response}")
return {"response": response}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
from fastapi.responses import HTMLResponse
@app.get("/chat-ui", response_class=HTMLResponse)
def get_chat_ui():
with open("index.html", "r", encoding="utf-8") as file:
html_content = file.read()
return html_content