반응형

FastAPI 실시간 대화방 예제

 

글. 수알치 오상문 

 

FastAPI를 이용하여 실시간 채팅 애플리케이션을 구현합니다. 사용자가 메시지를 입력하고 전송하면 서버로 전송되고, 서버는 모든 클라이언트에게 메시지를 전달(브로드캐스트)합니다.

[ FastAPI ]
/send 엔드포인트는 POST 요청으로 메시지를 받아 message_history에 저장하고, 간단한 응답을 반환합니다.
/ws 엔드포인트는 WebSocket 연결을 처리하고, 새로운 메시지가 message_history에 추가될 때마다 모든 클라이언트에게 전송합니다.

[ HTML ]
• HTMX의 hx-post, hx-target, hx-swap 속성을 사용하여 메시지 전송 폼을 처리합니다.
• JavaScript 코드는 WebSocket을 통해 서버에 연결하고, 서버에서 전송된 메시지를 chatbox에 추가합니다.


준비:
1. pip install fastapi uvicorn jinja2 htmx

2. main.py 작성 
3. templates 폴더를 만들고 그곳에 index.html 파일 저장

 

실행:
uvicorn main:app --reload 

 

웹브라우저에서 http://localhost:8000에 접속하여 대화를 시작합니다. 

별도 탭이나 다른 브라우저에서 접속하면 서로 대화를 나눌 수 있습니다.

 

[ main.py ]

import asyncio
from fastapi import FastAPI, Request, Response, WebSocket, WebSocketDisconnect
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse

app = FastAPI()
templates = Jinja2Templates(directory="templates")

# 채팅 메시지 저장
class Message:
    def __init__(self, username: str, message: str):
        self.username = username
        self.message = message

connected_clients = set()
message_queue = asyncio.Queue()  # 메시지 큐 사용


@app.get("/", response_class=HTMLResponse)
async def get(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})


@app.post("/send")
async def send_message(request: Request):
    form = await request.form()
    username = form.get("username")
    message = form.get("message")
    await message_queue.put(Message(username, message))  # 메시지를 큐에 추가
    return Response(status_code=204)  # 204 No Content 응답 반환


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    connected_clients.add(websocket)
    try:
        while True:
            message = await message_queue.get()  # 큐에서 메시지 가져오기
            # 모든 클라이언트에게 메시지 전송
            for client in connected_clients:
                await client.send_json({"username": message.username, "message": message.message})
    except WebSocketDisconnect:
        connected_clients.remove(websocket)

 

[ index.html ]

<!DOCTYPE html>
<html>
<head>
  <title>FastAPI 실시간 채팅</title>
  <script src="https://unpkg.com/htmx.org@1.9.2"></script>
  <style>
    /* 채팅 상자 스타일 */
    #chatbox {
      width: 400px;
      height: 300px;
      overflow-y: scroll; 
      border: 1px solid #ccc;
      padding: 10px;
    }

    input[name="username"] {
    width: 80px; /* username 입력 필드 너비 */
    }

    input[name="message"] {
    width: 260px; /* message 입력 필드 너비 */
    }    
  </style>
</head>
<body>
  <h1>아무나 대화방</h1>

  <div id="chatbox">
  </div> 

  <form hx-post="/send" hx-target="#chatbox" hx-swap="beforeend" hx-trigger="submit">
    <input type="text" name="username" placeholder="이름">
    <input type="text" name="message" placeholder="메시지">
    <button type="submit">전송</button>
  </form>

  <script>
    // 웹소켓 연결
    const websocket = new WebSocket("ws://localhost:8000/ws");

    websocket.onmessage = function(event) {
      const message = JSON.parse(event.data);
      const chatbox = document.getElementById("chatbox");
      const messageElement = document.createElement("p");
      messageElement.textContent = `${message.username}: ${message.message}`;
      chatbox.appendChild(messageElement);

      // 새로운 메시지가 추가될 때 스크롤을 맨 아래로 이동
      chatbox.scrollTop = chatbox.scrollHeight; 
      // 메시지 전송 후 입력 필드 초기화
      document.querySelector('input[name="message"]').value = '';       
    }
  </script>
</body>
</html>

 

[화면] 대화를 나누는 모습

 

 

반응형

+ Recent posts