This commit is contained in:
2026-02-27 12:13:57 +09:00
commit 0966bd9a61
10 changed files with 319 additions and 0 deletions

BIN
01_fastapi_first_step/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,88 @@
# [수업 자료] FastAPI 첫 걸음: 간단한 채팅 프로그램 만들기
이 자료는 FastAPI를 사용하여 기본적인 클라이언트-서버 구조의 채팅 프로그램을 이해하기 위한 설명서입니다. PPT 제작 시 각 섹션을 슬라이드로 활용할 수 있도록 구성되었습니다.
---
## Slide 1: 프로젝트 개요
### "FastAPI로 만드는 나만의 채팅 서버"
- **목표**: Python의 현대적인 웹 프레임워크인 FastAPI를 이용해 메시지를 주고받는 서버와 클라이언트를 구현합니다.
- **핵심 기술**:
- **FastAPI**: 빠르고 생산적인 Python 웹 프레임워크
- **Uvicorn**: ASGI 서버로 FastAPI 앱을 실행
- **Pydantic**: 데이터 검증 및 설정을 위한 라이브러리
- **Requests**: Python에서 HTTP 요청을 보내는 라이브러리
---
## Slide 2: 전체 구조 (Architecture)
### "누가 무엇을 하나요?"
![채팅 프로그램 전체 구조](./assets/architecture.png)
- **서버 (Server - `server.py`)**:
- 메시지를 저장하는 장소 (메모리 내 리스트)
- 클라이언트의 요청을 기다리고 처리함 (API 제공)
- 메시지에 '언제' 보냈는지 시간을 기록함
- **클라이언트 (Client - `client.py`)**:
- 사용자와 직접 소통하는 화면 (터미널)
- 서버에 메시지를 보내거나(POST), 새로운 메시지를 가져옴(GET)
---
## Slide 3: 데이터의 약속 (JSON & Model)
### "서버와 클라이언트는 어떻게 대화하나요?"
- **데이터 형식 (JSON)**:
```json
{
"name": "고도푸",
"message": "안녕하세요!"
}
```
- **데이터 모델 (Pydantic)**:
- 서버는 어떤 데이터가 들어올지 미리 정의합니다.
- `name`과 `message`가 문자열(str)인지 자동으로 검사합니다.
---
## Slide 4: 메시지 전송 (POST Request)
### "서버로 메시지 보내기"
![메시지 전송 및 수신 흐름](./assets/flow.png)
1. 사용자가 내용을 입력합니다.
2. 클라이언트가 서버의 `/messages` 주소로 데이터를 실어 보냅니다. (POST)
3. 서버는 현재 시간을 계산하여 메시지와 함께 저장합니다.
- **서버 로직**: `datetime.now().strftime(...)`를 사용하여 친숙한 시간 형식으로 변환
---
## Slide 5: 메시지 확인 (GET Request & Polling)
### "새로운 메시지 가져오기"
- **효율적인 조회**: 모든 메시지를 매번 다 가져오는 것은 비효율적입니다.
- **인덱스 활용 (`start` 파라미터)**:
- 클라이언트는 "내가 5번까지 봤으니, 6번부터 줘!"라고 요청합니다.
- 예: `/messages?start=5`
- **결과**: 클라이언트는 아직 보지 못한 '새 소식'만 화면에 출력합니다.
---
## Slide 6: 실습 흐름 (Workflow)
### "직접 실행해 봅시다!"
1. **서버 실행**: `python server.py` (비서가 전화를 기다리는 상태)
2. **클라이언트 실행**: `python client.py` (사용자가 대화를 시작)
3. **상호작용**:
- 1번 선택: 메시지 작성 및 전송
- 2번 선택: 새 메시지 확인 (다른 사람이 보낸 것도 보임!)
- 3번 선택: 종료
---
## Slide 7: 요약 및 발전 방향
### "무엇을 배웠나요?"
- **HTTP Method**: 데이터를 보낼 때는 POST, 가져올 때는 GET을 사용합니다.
- **Stateless**: 서버는 클라이언트가 누구인지 기억하지 않지만, 요청에 따라 데이터를 관리합니다.
- **Next Step**:
- 데이터베이스(DB) 연결하여 재시작해도 대화 유지하기
- 웹 브라우저 화면(HTML/CSS)으로 예쁘게 만들기
- 실시간 통신(WebSocket) 적용해보기

View File

@@ -0,0 +1,105 @@
# 프로젝트 목표
- fastapi 학습을 위한 간단한 채팅 프로그램 구현
# 구성요소 및 요구사항
- 서버: server.py
- 클라이언트로 부터 메시지를 수신, 수신한 메시지를 리스트로 저장
- 수신한 메시지는 다음과 같은 형식으로 전달됨
```json
{
"name": "사용자 이름",
"message": "메시지 내용"
}
```
- 메시지에 'timestamp' 필드를 추가하여 현재 시간을 저장함. 다음과 같은 항목으로 리스트에 저장함.
```json
{
"name": "사용자 이름",
"message": "메시지 내용",
"timestamp": "2022-01-01 00:00:00"
}
```
- 클라이언트에 메시지 전송
- 클라이언트에 메시지를 전송함. 클라이언트로 부터 query message로 `start` 매개변수를 전달받으며, `start` 매개변수 이후 메시지를 모두 전송함.
예: `start=2` 일 경우 messages[2:]를 전송함.
- 클라이언트: client.py
- 초기화: 프로그램 시작 시, 사용자의 이름을 물어봄
```bash
이름을 입력해주세요 >
```
- 작업 수행을 위한 프롬프트 출력: 다음과 같은 프롬프트를 출력하고, 1~3사이 숫자를 입력받습니다. 만약 잘못된 값을 입력하면 다시 입력받습니다.
```bash
수행할 작업을 선택해주세요:
1. 메시지 전송
2. 수신한 메시지 보기
3. 종료
```
- 메시지 전송: 전송할 메시지를 입력받고, 서버로 전송합니다. 전송할 메시지 형식은 다음과 같습니다.
```json
{
"name": "사용자 이름",
"message": "메시지 내용"
}
```
- 서버로 부터 메시지를 수신:
- 수신한 메시지는 다음과 같은 형식을 가짐
```json
{
"name": "사용자 이름",
"message": "메시지 내용",
"timestamp": "2022-01-01 00:00:00"
}
```
- 수신한 메시지를 다음과 같은 형식으로 출력함
```bash
[2022-01-01 00:00:00] 사용자 이름: 메시지 내용
```
- `start` 변수를 통해 메시지 수신을 시작할 인덱스를 저장하고 있어야 함. 이 값을 메시지를 수신 요청시 서버로 `start` 매개변수를 전달하여야 하며, 메시지를 수신한 수 만큼 `start` 변수를 증가시켜야 함.
# Specification
## Server
- 다음과 같은 API를 제공함
### 1. 메시지 전송
- **URL**: `/messages`
- **Method**: `POST`
- **Request Body**:
```json
{
"name": "사용자 이름",
"message": "메시지 내용"
}
```
- **Response**:
```json
{
"status": "success",
"received": {
"name": "사용자 이름",
"message": "메시지 내용",
"timestamp": "2022-01-01 00:00:00"
}
}
```
### 2. 메시지 수신 (조회)
- **URL**: `/messages`
- **Method**: `GET`
- **Query Parameters**:
- `start` (int, default=0): 이 인덱스부터의 메시지를 가져옴
- **Response**:
```json
[
{
"name": "사용자 이름",
"message": "메시지 내용",
"timestamp": "2022-01-01 00:00:00"
},
...
]
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

View File

@@ -0,0 +1,83 @@
import requests
import sys
SERVER_URL = "http://localhost:8000"
def post_message(user_name, content):
"""서버로 메시지를 전송합니다."""
payload = {
"name": user_name,
"message": content
}
try:
response = requests.post(f"{SERVER_URL}/messages", json=payload)
if response.status_code == 200:
print("전송 성공!")
return True
else:
print(f"전송 실패: {response.text}")
return False
except Exception as e:
print(f"오류 발생: {e}")
return False
def get_messages(start_index):
"""서버로부터 새로운 메시지를 가져옵니다."""
try:
response = requests.get(f"{SERVER_URL}/messages", params={"start": start_index})
if response.status_code == 200:
new_messages = response.json()
if not new_messages:
print("새로운 메시지가 없습니다.")
else:
for msg in new_messages:
print(f"[{msg['timestamp']}] {msg['name']}: {msg['message']}")
return new_messages
else:
print(f"메시지 수신 실패: {response.text}")
return []
except Exception as e:
print(f"오류 발생: {e}")
return []
def main():
print("FastAPI 채팅 클라이언트에 오신 것을 환영합니다!")
user_name = input("이름을 입력해주세요 > ").strip()
if not user_name:
user_name = "Anonymous"
start_index = 0
while True:
print("\n수행할 작업을 선택해주세요:")
print("1. 메시지 전송")
print("2. 수신한 메시지 보기")
print("3. 종료")
choice = input("> ").strip()
if choice == "1":
message_content = input("메시지 내용: ").strip()
if message_content:
post_message(user_name, message_content)
else:
print("메시지를 입력해주세요.")
elif choice == "2":
new_msgs = get_messages(start_index)
start_index += len(new_msgs)
elif choice == "3":
print("프로그램을 종료합니다.")
sys.exit(0)
else:
print("잘못된 입력입니다. 1~3 사이의 숫자를 입력해주세요.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,41 @@
from fastapi import FastAPI, Query
from pydantic import BaseModel
from datetime import datetime
from typing import List
app = FastAPI()
# 메시지 저장을 위한 리스트
messages = []
class Message(BaseModel):
name: str
message: str
@app.post("/messages")
def post_message(msg: Message):
# 현재 시간을 YYYY-MM-DD HH:MM:SS 형식으로 저장
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
new_message = {
"name": msg.name,
"message": msg.message,
"timestamp": timestamp
}
messages.append(new_message)
return {
"status": "success",
"received": new_message,
"timestamp": timestamp
}
@app.get("/messages")
def get_messages(start: int = 0):
# start 인덱스부터 끝까지의 메시지를 반환
return messages[start:]
if __name__ == "__main__":
import uvicorn
# 터미널에서 직접 실행할 때를 위해 uvicorn 설정 추가
uvicorn.run(app, host="0.0.0.0", port=8000)