commit 0966bd9a614c802341efeca279365199e1750f3f Author: Godopu Date: Fri Feb 27 12:13:57 2026 +0900 init diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..2dfb8f1 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18da50b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +venv/ +.venv/ \ No newline at end of file diff --git a/01_fastapi_first_step/.DS_Store b/01_fastapi_first_step/.DS_Store new file mode 100644 index 0000000..8539c0e Binary files /dev/null and b/01_fastapi_first_step/.DS_Store differ diff --git a/01_fastapi_first_step/DESCRIPTION.md b/01_fastapi_first_step/DESCRIPTION.md new file mode 100644 index 0000000..5287c90 --- /dev/null +++ b/01_fastapi_first_step/DESCRIPTION.md @@ -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) 적용해보기 diff --git a/01_fastapi_first_step/README.md b/01_fastapi_first_step/README.md new file mode 100644 index 0000000..57378f4 --- /dev/null +++ b/01_fastapi_first_step/README.md @@ -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" + }, + ... + ] + ``` \ No newline at end of file diff --git a/01_fastapi_first_step/assets/architecture.png b/01_fastapi_first_step/assets/architecture.png new file mode 100644 index 0000000..d15ef4b Binary files /dev/null and b/01_fastapi_first_step/assets/architecture.png differ diff --git a/01_fastapi_first_step/assets/flow.png b/01_fastapi_first_step/assets/flow.png new file mode 100644 index 0000000..28d1095 Binary files /dev/null and b/01_fastapi_first_step/assets/flow.png differ diff --git a/01_fastapi_first_step/client.py b/01_fastapi_first_step/client.py new file mode 100644 index 0000000..91df8b9 --- /dev/null +++ b/01_fastapi_first_step/client.py @@ -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() diff --git a/01_fastapi_first_step/server.py b/01_fastapi_first_step/server.py new file mode 100644 index 0000000..61a39a3 --- /dev/null +++ b/01_fastapi_first_step/server.py @@ -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) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29