
๐ ๋ฏธ๋ ํ๋ก์ ํธ๋ก ChatGPT API๋ฅผ ์ฌ์ฉํ "๋ชจ๋์ ๋ ์ํผ"๋ฅผ ๋ง๋ค๋ฉด์ ์ด๋ ค์ ๋ ์ ์ ์์ฝํด๋ณด์์ต๋๋ค.
| ๋ชจ๋ | ์ญํ | ์ค๋ฌด ํ์ฉ๋ |
|---|---|---|
FastAPI | ๋ฉ์ธ ์ ํ๋ฆฌ์ผ์ด์ ํด๋์ค | โญโญโญโญโญ |
Request | HTTP ์์ฒญ ๊ฐ์ฒด ์ฒ๋ฆฌ | โญโญโญโญ |
Form | HTML ํผ ๋ฐ์ดํฐ ์ฒ๋ฆฌ | โญโญโญ |
HTTPException | HTTP ์๋ฌ ์ฒ๋ฆฌ | โญโญโญโญโญ |
Cookie | ์ฟ ํค ๋ฐ์ดํฐ ์ฒ๋ฆฌ | โญโญโญ |
Depends | ์์กด์ฑ ์ฃผ์ | โญโญโญโญโญ |
# CORS ๋ฏธ๋ค์จ์ด ์ค์
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # ๋ชจ๋ ๋๋ฉ์ธ ํ์ฉ
allow_credentials=True, # ์ธ์ฆ ์ ๋ณด ํฌํจ ์์ฒญ ํ์ฉ
allow_methods=["*"], # ๋ชจ๋ HTTP ๋ฉ์๋ ํ์ฉ
allow_headers=["*"], # ๋ชจ๋ ํค๋ ํ์ฉ
)
Cross-Origin Resource Sharing์ ์ค์๋ง๋ก, ์น ๋ณด์ ์ ์ฑ ์ ๋๋ค.
| ์ค์ | ์ค๋ช | ๊ฐ๋ฐํ๊ฒฝ | ์ด์ํ๊ฒฝ |
|---|---|---|---|
allow_origins | ํ์ฉํ ๋๋ฉ์ธ | ["*"] | ["https://myapp.com"] |
allow_credentials | ์ธ์ฆ์ ๋ณด ํฌํจ ์์ฒญ | True | True |
allow_methods | ํ์ฉ HTTP ๋ฉ์๋ | ["*"] | ["GET", "POST"] |
allow_headers | ํ์ฉ ํค๋ | ["*"] | ํ์ํ ํค๋๋ง |
โ ๏ธ ๋ณด์ ์ฃผ์์ฌํญ: ์ค์ ๋ฐฐํฌ ์์๋
allow_origins๋ฅผ ํน์ ๋๋ฉ์ธ์ผ๋ก ์ ํํ์ธ์!
# ์ ์ ํ์ผ ์ฌ์ฉ ์ค์
app.mount("/static", StaticFiles(directory="static"), name="static")
# HTML ํ
ํ๋ฆฟ ๊ฒฝ๋ก ์ค์
templates = Jinja2Templates(directory="templates")
@app.get("/")
async def read_root(request: Request):
return templates.TemplateResponse("index.html", {
"request": request, # ํ์ ํ๋ผ๋ฏธํฐ
"title": "ํํ์ด์ง", # ํ
ํ๋ฆฟ ๋ณ์
"user": "๊น๊ฐ๋ฐ์" # ํ
ํ๋ฆฟ ๋ณ์
})
templates/index.html:
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>์๋
ํ์ธ์, {{ user }}๋!</h1>
</body>
</html>
| ํน์ง | requests | httpx | ์ค๋ฌด ์ถ์ฒ๋ |
|---|---|---|---|
| ๋๊ธฐ ์ง์ | โ ์๋ฒฝ ์ง์ | โ ์๋ฒฝ ์ง์ | โญโญโญโญโญ |
| ๋น๋๊ธฐ ์ง์ | โ ๋ฏธ์ง์ | โ ์๋ฒฝ ์ง์ | โญโญโญโญโญ |
| HTTP/2 ์ง์ | โ ๋ฏธ์ง์ | โ ์๋ฒฝ ์ง์ | โญโญโญโญ |
| ํ๋์ ์ธ API | โ ๏ธ ์ค๋๋ ์ค๊ณ | โ ์ต์ ์ค๊ณ | โญโญโญโญโญ |
| ์ฑ๋ฅ | ๋ณดํต | ์ฐ์ | โญโญโญโญ |
| FastAPI ํธํ์ฑ | ์ ํ์ | ์๋ฒฝ | โญโญโญโญโญ |
import requests
# ๊ธฐ๋ณธ GET ์์ฒญ
def get_user_info_sync(user_id: int):
"""๋๊ธฐ ๋ฐฉ์์ผ๋ก ์ฌ์ฉ์ ์ ๋ณด ์กฐํ"""
response = requests.get(f"https://api.example.com/users/{user_id}")
if response.status_code == 200:
return response.json()
else:
return {"error": f"์ฌ์ฉ์ ์กฐํ ์คํจ: {response.status_code}"}
# POST ์์ฒญ (JSON ๋ฐ์ดํฐ)
def create_user_sync(user_data: dict):
"""๋๊ธฐ ๋ฐฉ์์ผ๋ก ์ฌ์ฉ์ ์์ฑ"""
response = requests.post(
"https://api.example.com/users",
json=user_data,
headers={"Content-Type": "application/json"}
)
return response.json()
import httpx
# httpx๋ก ๋๊ธฐ ์์ฒญ (requests์ ๊ฑฐ์ ๋์ผํ API)
def get_user_info_sync_httpx(user_id: int):
"""httpx๋ก ๋๊ธฐ ๋ฐฉ์ ์ฌ์ฉ์ ์ ๋ณด ์กฐํ"""
with httpx.Client() as client:
response = client.get(f"https://api.example.com/users/{user_id}")
if response.status_code == 200:
return response.json()
else:
return {"error": f"์ฌ์ฉ์ ์กฐํ ์คํจ: {response.status_code}"}
import httpx
import asyncio
# ๋น๋๊ธฐ GET ์์ฒญ
async def get_user_info_async(user_id: int):
"""๋น๋๊ธฐ ๋ฐฉ์์ผ๋ก ์ฌ์ฉ์ ์ ๋ณด ์กฐํ"""
async with httpx.AsyncClient() as client:
response = await client.get(f"https://api.example.com/users/{user_id}")
if response.status_code == 200:
return response.json()
else:
return {"error": f"์ฌ์ฉ์ ์กฐํ ์คํจ: {response.status_code}"}
# ๋น๋๊ธฐ POST ์์ฒญ
async def create_user_async(user_data: dict):
"""๋น๋๊ธฐ ๋ฐฉ์์ผ๋ก ์ฌ์ฉ์ ์์ฑ"""
async with httpx.AsyncClient() as client:
response = await client.post(
"https://api.example.com/users",
json=user_data,
headers={"Content-Type": "application/json"}
)
return response.json()
# ์ฌ๋ฌ ์์ฒญ ๋์ ์ฒ๋ฆฌ
async def get_multiple_users(user_ids: list):
"""์ฌ๋ฌ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋์์ ์กฐํ"""
async with httpx.AsyncClient() as client:
tasks = [
client.get(f"https://api.example.com/users/{user_id}")
for user_id in user_ids
]
# ๋ชจ๋ ์์ฒญ์ ๋์์ ์คํ
responses = await asyncio.gather(*tasks)
return [
response.json() if response.status_code == 200 else None
for response in responses
]
from fastapi import FastAPI
import httpx
app = FastAPI()
# ๋๊ธฐ ๋ฐฉ์ (๊ฐ๋จํ ์์
์ฉ)
@app.get("/user/{user_id}")
def get_external_user(user_id: int):
"""์ธ๋ถ API์์ ์ฌ์ฉ์ ์ ๋ณด ์กฐํ (๋๊ธฐ)"""
with httpx.Client() as client:
response = client.get(f"https://jsonplaceholder.typicode.com/users/{user_id}")
if response.status_code == 200:
return {"success": True, "data": response.json()}
else:
return {"success": False, "error": "์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค"}
# ๋น๋๊ธฐ ๋ฐฉ์ (๊ถ์ฅ)
@app.get("/user-async/{user_id}")
async def get_external_user_async(user_id: int):
"""์ธ๋ถ API์์ ์ฌ์ฉ์ ์ ๋ณด ์กฐํ (๋น๋๊ธฐ)"""
async with httpx.AsyncClient() as client:
response = await client.get(f"https://jsonplaceholder.typicode.com/users/{user_id}")
if response.status_code == 200:
return {"success": True, "data": response.json()}
else:
return {"success": False, "error": "์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค"}
# ๋ณต์กํ ์ธ๋ถ API ์ฐ๋ ์์
@app.get("/weather/{city}")
async def get_weather(city: str):
"""๋ ์จ API ์ฐ๋ ์์"""
async with httpx.AsyncClient() as client:
# ์ค์ ๋ก๋ API ํค๊ฐ ํ์ํฉ๋๋ค
response = await client.get(
f"https://api.openweathermap.org/data/2.5/weather",
params={
"q": city,
"appid": "YOUR_API_KEY",
"units": "metric",
"lang": "kr"
}
)
if response.status_code == 200:
weather_data = response.json()
return {
"city": weather_data["name"],
"temperature": weather_data["main"]["temp"],
"description": weather_data["weather"][0]["description"]
}
else:
return {"error": "๋ ์จ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค"}
def clean_text(text: str) -> str:
"""ํ
์คํธ์์ HTML ํ๊ทธ ์ ๊ฑฐ ๋ฐ ํน์๋ฌธ์ ์ฒ๋ฆฌ"""
if not text:
return ""
# 1๏ธโฃ HTML ์ํฐํฐ ๋์ฝ๋ฉ
text = html.unescape(text)
# 2๏ธโฃ HTML ํ๊ทธ ์ ๊ฑฐ
text = re.sub(r'<[^>]+>', '', text)
# 3๏ธโฃ ์ฐ์๋ ๊ณต๋ฐฑ์ ํ๋๋ก ๋ณ๊ฒฝ
text = re.sub(r'\s+', ' ', text)
# 4๏ธโฃ ์๋ค ๊ณต๋ฐฑ ์ ๊ฑฐ
text = text.strip()
return text
| ๋จ๊ณ | ์ฒ๋ฆฌ ๋ด์ฉ | ์์ |
|---|---|---|
| HTML ์ํฐํฐ ๋์ฝ๋ฉ | < โ < | <script> โ <script> |
| HTML ํ๊ทธ ์ ๊ฑฐ | ๋ชจ๋ ํ๊ทธ ์ญ์ | <script>alert()</script> โ alert() |
| ๊ณต๋ฐฑ ์ ๊ทํ | ์ฐ์ ๊ณต๋ฐฑ ํตํฉ | ์๋
ํ์ธ์ โ ์๋
ํ์ธ์ |
| ๊ณต๋ฐฑ ์ ๊ฑฐ | ์๋ค ๋ถํ์ํ ๊ณต๋ฐฑ ์ ๊ฑฐ | ํ
์คํธ โ ํ
์คํธ |
def validate_recipe_input(title: str, content: str) -> None:
"""๋ ์ํผ ์
๋ ฅ ๋ฐ์ดํฐ ๊ฒ์ฆ"""
# ๐งน ํ
์คํธ ์ ๋ฆฌ
title = clean_text(title) if title else ""
content = clean_text(content) if content else ""
# ๐ ํ์๊ฐ ๊ฒ์ฆ
if not title:
raise HTTPException(
status_code=400,
detail="โ ๋ ์ํผ ์ ๋ชฉ์ ์
๋ ฅํด์ฃผ์ธ์"
)
if not content:
raise HTTPException(
status_code=400,
detail="โ ๋ ์ํผ ๋ด์ฉ์ ์
๋ ฅํด์ฃผ์ธ์"
)
# ๐ ๊ธธ์ด ์ ํ ๊ฒ์ฆ
if len(title) > 200:
raise HTTPException(
status_code=400,
detail="โ ์ ๋ชฉ์ 200์ ์ด๋ด๋ก ์
๋ ฅํด์ฃผ์ธ์"
)
if len(content) > 10000:
raise HTTPException(
status_code=400,
detail="โ ๋ด์ฉ์ 10,000์ ์ด๋ด๋ก ์
๋ ฅํด์ฃผ์ธ์"
)
| ํญ๋ชฉ | ๊ท์น | ์๋ฌ ์ฝ๋ | ๋ฉ์์ง |
|---|---|---|---|
| ์ ๋ชฉ ํ์ | ๋น ๊ฐ ๋ถํ | 400 | "๋ ์ํผ ์ ๋ชฉ์ ์ ๋ ฅํด์ฃผ์ธ์" |
| ๋ด์ฉ ํ์ | ๋น ๊ฐ ๋ถํ | 400 | "๋ ์ํผ ๋ด์ฉ์ ์ ๋ ฅํด์ฃผ์ธ์" |
| ์ ๋ชฉ ๊ธธ์ด | 200์ ์ด๋ด | 400 | "์ ๋ชฉ์ 200์ ์ด๋ด๋ก ์ ๋ ฅํด์ฃผ์ธ์" |
| ๋ด์ฉ ๊ธธ์ด | 10,000์ ์ด๋ด | 400 | "๋ด์ฉ์ 10,000์ ์ด๋ด๋ก ์ ๋ ฅํด์ฃผ์ธ์" |
| ๊ฐ๋ | ์ค๋ช | ์ค๋ฌด ์ค์๋ |
|---|---|---|
| ์ฒด๊ณ์ ์ธ Import | ๋ชจ๋์ ์ฉ๋๋ณ๋ก ์ ๋ฆฌํ์ฌ ๊ฐ๋ ์ฑ ํฅ์ | โญโญโญ |
| CORS ์ค์ | ํ๋ก ํธ์๋-๋ฐฑ์๋ ๊ฐ ์์ ํ ํต์ | โญโญโญโญโญ |
| ์ ์ ํ์ผ ์๋น | CSS, JS ๋ฑ ์ ์ ์์ ํจ์จ์ ์ ๊ณต | โญโญโญโญ |
| ํ ํ๋ฆฟ ์์ง | ๋์ HTML ์์ฑ์ผ๋ก ์ฌ์ฉ์ ๊ฒฝํ ํฅ์ | โญโญโญ |
| HTTP ํด๋ผ์ด์ธํธ | ์ธ๋ถ API ์ฐ๋ (httpx ๊ถ์ฅ) | โญโญโญโญโญ |
| ์์กด์ฑ ์ฃผ์ | ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ๊ณผ ํ ์คํธ ์ฉ์ด์ฑ ํ๋ณด | โญโญโญโญโญ |
| ๋ฐ์ดํฐ ๊ฒ์ฆ | XSS ๋ฐฉ์ง์ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๋ณด์ฅ | โญโญโญโญโญ |
๋ ์จ ๋์๋ณด๋
์์ ๋ฏธ๋์ด ์ง๊ณ๊ธฐ
๋ง์ดํฌ๋ก์๋น์ค ๊ฒ์ดํธ์จ์ด