이전 내용
[파이썬] FastAPI - 스트리밍 응답
이전 내용 [파이썬] FastAPI - 쿼리 매개변수, 경로 매개변수, 백그라운드 태스크이전 내용 [파이썬] FastAPI - 정적 파일, API Router이전 내용 [파이썬] FastAPI - FastAPI와 Jinja2 고급 문법시작에 앞서해당
puppy-foot-it.tistory.com
웹소켓
웹소켓(WebSocket)은 웹 브라우저와 서버 간의 양방향 통신을 가능하게 하는 프로토콜이다. 이는 HTTP 프로토콜을 사용하는 기존의 요청-응답 모델의 한계를 극복하고, 실시간 데이터 전송이 필요한 애플리케이션에서 주로 사용된다.
[주요 특징]
- 양방향 통신: 클라이언트와 서버 간의 연결이 설정되면, 양쪽 모두 데이터를 자유롭게 주고받을 수 있다. 이는 서버가 클라이언트에게 데이터를 푸시할 수 있는 기능을 제공한다.
- 지속적인 연결:웹소켓은 오랜 시간 동안 연결을 유지할 수 있어, 반복적인 HTTP 요청을 보내는 것보다 더 효율적이고 낮은 지연 시간을 제공한다.
- 서버 푸시 기능(server push): 서버가 필요한 경우 클라이언트에게 자동으로 데이터를 전송할 수 있다.
- 저렴한 오버헤드:웹소켓은 헤더 크기가 작고, 연결이 한번 설정되면 그 이후의 메시지는 오버헤드가 적기 때문에 대량의 데이터를 전송하는 데 유리하다.
- 실시간 업데이트: 데이터를 실시간으로 전송하기 때문에, 소셜 미디어, 게임, 주식 거래 시스템 등 실시간 데이터 업데이트가 중요한 애플리케이션에서 널리 사용된다.
[웹소켓의 작업 흐름]
- 연결 설정: 클라이언트가 서버에 웹소켓 연결을 요청한다. 이 요청은 일반적인 HTTP 요청을 통해 이루어지며, Upgrade 헤더에 의해 웹소켓 프로토콜로 전환된다.
- 데이터 전송: 연결이 설정된 후, 클라이언트와 서버는 텍스트 또는 바이너리 데이터 형식으로 메시지를 지속적으로 주고받을 수 있다.
- 연결 종료: 통신이 종료되면, 클라이언트 또는 서버가 연결 종료 요청을 보내고, 상대방이 이를 확인한 후 연결이 종료된다.
웹소켓 기본 사용법
FastAPI 프레임워크에서는 웹소켓을 다룰 때 주로 WebSocket 타입을 매개변수로 지정해 주면 된다.
웹소켓은 연결이 지속되는 한 계속해서 메시지를 교환할 수 있으므로 서버 리소스를 효율적으로 사용하기 위해 비동기 처리가 필수적이다.
FastAPI에서 비동기 처리는 async와 await라는 두 가지 주요 문법을 통해 구현된다.
- async: 함수가 '비동기 함수' 임을 나타내는 키워드.
- 비동기 함수: 내부에서 awiat 표현식을 사용할 수 있으며, 호출될 때 즉시 실행되는 것이 아니라 이벤트 루프에 의해 스케줄링된다.
- await: 비동기 실행을 지원하는 함수(awaitable)가 완료될 때까지 기다린다. 그러나 이 대기 과정에서 프로그램의 다른 부분이 멈추지 않고, 이벤트 루트가 다른 비동기 작업을 계속 진행할 수 있도록 한다.
※ 비동기 처리
프로그램이 한 작업을 완료하기를 기다리는 동안 다른 작업을 동시에 수행할 수 있도록 하는 기술로, 특히 네트워크 요청이나 파일 I/O와 같이 대기 시간이 긴 작업에서 유용.
▶ 프로그램은 대기 없이 여러 작업을 병렬로 처리할 수 있다.
[FastAPI에서 웹소켓을 사용하여 실시간 양방향 통신 기능 구현 예제]
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
try:
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Returned Message: {data} from Server")
except WebSocketDisconnect:
print("Websocket Disconnected")
await websocket.close(code=1000) # 명시적으로 웹소켓 연결 종료
▶ 코드 설명
@app.websocket("/ws")
- @app.websocket("/ws") 데코레이터를 사용하여 웹소켓 라우트 생성.
- /ws 경로에서 웹소켓 연결을 처리하는 엔드포인트 정의.
- 클라이언트가 이 경로로 웹소켓 연결을 요청하면, 해당 함수 호출.
async def websocket_endpoint(websocket: WebSocket):
- websocket_endpoint 함수는 async def로 비동기로 정의되어 있으며, WebSocket 객체를 매개변수로 받음.
- websocket: Websocket 은 함수 매개변수로 웹소켓 연결 객체를 타입 힌트와 함께 선언한다.
- 클라이언트와의 연결을 나타내는 websocket 객체를 통해 데이터 송수신 처리.
await websocket.accept()
- 함수 내부의 비동기 작업들은 await 키워드를 사용하여 호출한다.
- websocket.accept(): 클라이언트에서의 웹소켓 연결 요청을 수락하는 부분.
- 클라이언트가 웹소켓에 연결하면 이 명령을 통해 연결 허용하고, 연결 수립 과정이 완료될 때까지 함수 실행을 일시 중지한다.
try:
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Returned Message: {data} from Server")
- try 블록 내의 코드는 무한 루프를 통해 클라이언트로부터 데이터를 지속적으로 수신하고 처리.
- await websocket.receive_text() 명령을 사용하여 클라이언트에서 전송한 텍스트 메시지 수신하며, 수신된 데이터는 data 변수에 저장.
- await websocket.send_text(...)를 통해 클라이언트에게 응답 메시지 전송. 여기서는 수신된 메시지를 포함하여 "Returned Message:..."라는 형식의 메시지를 보냄.
except WebSocketDisconnect:
print("Websocket Disconnected")
await websocket.close(code=1000) # 명시적으로 웹소켓 연결 종료
- except WebSocketDisconnect 블록은 클라이언트가 웹소켓 연결을 종료할 때 발생하는 예외 처리.
- 이 예외가 발생하면 "Websocket Disconnected"라는 메시지를 콘솔에 출력, await websocket.close(code=1000) 명령을 통해 명시적으로 웹소켓 연결 종료.
- 코드 1000은 정상적인 연결 종료를 나타내는 코드.
[웹소켓 테스트]
웹소켓 테스트를 위해 웹페이지 코드를 index.html에 작성한다.
웹페이지상에서 실시간 전송과 응답을 위해서는 프론트엔드 기술이 필요하여 자바스크립트(JavaScript)를 사용한다. 자바스크립트는 HTML 요소에 동적 기능을 추가하여 사용자와의 상호작용을 가능하게 한다.
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Test</title>
</head>
<body>
<h1>WebSocket Test Page!</h1>
<!-- 연결 및 해제 버튼-->
<button id="connect">Connect</button>
<button id="disconnect" disabled>Disconnect</button>
<!-- 사용자로부터 메시지를 입력받는 필드-->
<p>Enter your message: <input type="text" id="userMessage"></p>
<button id="sendMessage">Send Message</button>
<!-- 현재 연결 상태와 메시지 로그 표시-->
<p>Status: <span id="status">Disconnected</span></p>
<div id="messages"></div>
<script>
// 웹소켓 객체를 저장할 변수 선언
let websocket;
// DOM 엘리먼트에 대한 참조 가져옴
const statusElement = document.getElementById("status");
const messagesElement = document.getElementById("messages");
const userMessageElement = document.getElementById("userMessage");
// 'Connect' 버튼에 클릭 이벤트 리스너 추가
document.getElementById("connect").addEventListener("click", () => {
// 웹소켓 인스턴스 생성하고, 서버의 웹소켓 끝점에 연결
websocket = new WebSocket("ws://127.0.0.1:8000/ws");
// 웹소켓 연결이 성공적으로 열렸을 때 호출
websocket.onopen = (event) => {
statusElement.innerText = "Connected"; // 상태를 Connected 로 업데이트
document.getElementById("connect").disabled = true; // 연결 버튼 비활성화
document.getElementById("disconnect").disabled = false; // 해제 버튼 활성화
};
// 서버로부터 메시지를 받았을 때 호출
websocket.onmessage = (event) => {
// 메시지 로그에 새로운 메시지 추가
const newMessage = document.createElement("p");
newMessage.innerText = `Received: ${event.data}`;
messagesElement.appendChild(newMessage);
};
// 웹소켓 연결이 닫혔을 때 호출
websocket.onclose = (event) => {
statusElement.innerText = "Disconnected"; // 상태를 'Disconnected'로 업데이트
document.getElementById("connect").disabled = false; // 연결 버튼 활성화
document.getElementById("disconnect").disabled = true; // 해제 버튼 비활성화
};
});
// 'Disconnect' 버튼에 클릭 이벤트 리스너 추가
document.getElementById("disconnect").addEventListener("click", () => {
// 웹소켓 연결 닫음
websocket.close();
});
// 'Send Message' 버튼에 클릭 이벤트 리스너 추가
document.getElementById("sendMessage").addEventListener("click", () => {
// 입력 필드에서 메시지 가져옴
const message = userMessageElement.value;
// 웹소켓을 통해 메시지를 서버로 전송
websocket.send(message);
// 메시지 로그에 전송한 메시지 추가
const sentMessage = document.createElement("p");
sentMessage.innerText = `Sent: ${message}`;
messagesElement.appendChild(sentMessage);
});
</script>
</body>
</html>
해당 코드를 작성한 후, (FastAPI 실행말고) index.html을 켜서 테스트해 볼 수 있다.
웹소켓 주요 메소드
◆ 기본 주요 메소드
- await websocket.accept(): 웹소켓 연결 수락
서버와 클라이언트가 통신 방식을 명시하기 위해 사용되며, "wamp", "mqtt", "soap" 등 특정 subprotocol을 수락할 때 사용된다.
await websocket.accept(subprotocol="wamp")
- await websocket.receive_text(): 텍스트 메시지 수신
클라이언트로부터 텍스트 형식의 메시지를 수신. 이 메소드는 비동기적으로 실행되며, 메시지를 받을 때까지 대기한다. - await websocket.receive_bytes(): 바이트 메시지 수신
- await websocket.send_text(data): 텍스트 메시지 전송
서버에서 클라이언트로 텍스트 메시지를 전송. data에는 전송할 메시지 내용이 포함된다.
await websocket.send_text("안녕하세요!")
- await websocket.send_bytes(data): 바이트 메시지 전송
- await websocket.close(code, reason): 웹소켓 연결 종료
웹소켓 연결 종료. code: 종료 코드(예: 1000은 정상 종료)를 의미하며, reason: 종료 사유를 설명하는 선택적 문자열이다. - onopen: 연결 성공 이벤트
웹소켓 연결이 성공적으로 수립되었을 때 호출되는 이벤트 핸들러. 클라이언트와 서버 간의 연결 상태를 확인할 수 있다. - onmessage: 메시지 수신 이벤트
서버로부터 메시지를 수신했을 때 호출되는 이벤트 핸들러. 수신된 메시지에 대한 처리를 수행할 수 있다. - onclose: 연결 종료 이벤트
웹소켓 연결이 종료되었을 때 호출되는 이벤트 핸들러. 연결 상태의 변경에 대응하여 필요한 작업을 수행할 수 있다. - onerror: 오류 발생 이벤트
웹소켓 통신 중 오류가 발생했을 때 호출되는 이벤트 핸들러. 문제가 발생했을 때 적절한 처리를 할 수 있다. - WebsocketDisconnect: 연결 끊김 예외 처리
from fastapi import WebSocketDisconnect
try:
data = await websockt.receive_text()
except WebSocketDisconnect:
print("연결 종료")
◆ 매개변수 처리 간략화
웹소켓 경로에서도 쿼리와 경로 매개변수를 사용할 수 있다.
@app.websocket("/ws/{user_id}")
async def websocket_endpoint(websocket: WebSocket, user_id: str):
websocket.client_state와 websocket.application_state는 웹소켓의 현재 상태를 확인할 때 유용하며, 이를 통해 연결 상태에 따른 로직을 구현할 수 있다.
- websocket.client_state: 클라이언트의 웹소켓 연결 상태
현재 웹소켓 클라이언트의 상태를 나타낸다. 이 속성은 웹소켓 연결이 열려 있는지, 닫혀 있는지 등의 정보를 포함한다.
상태 값:
- CONNECTING: 연결이 시도 중인 상태.
- OPEN: 연결이 성공적으로 완료된 상태.
- CLOSING: 연결이 종료되고 있는 상태.
- CLOSED: 연결이 종료된 상태.
from fastapi import FastAPI, WebSocket, WebSocketDiscconect
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
if websocket.client_state == WebSocketState.CONNECTED:
await websocket.send_text("클라이언트 연결 중")
- websocket.application_state: 서버의 웹소켓 연결 상태
웹소켓 연결을 통해 애플리케이션의 상태를 나타낸다. 이 속성은 서버 측의 상태를 관리하는 데 사용된다.
상태 값: 이 값은 사용자 정의 애플리케이션 상태를 반영할 수 있다.
- IDLE: 애플리케이션이 현재 대기 중인 상태.
- ACTIVE: 애플리케이션이 현재 활성 상태인 경우.
- ERROR: 애플리케이션에서 오류가 발생한 상태.
from fastapi import FastAPI, WebSocket, WebSocketDiscconect
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
if websocket.application_state == WebSocketState.CONNECTED:
await websocket.send_text("애클리케이션 연결 중")
※ WebSocketState: websockets 라이브러리에 정의된 열거형 값으로, CONNECT, DISCONNECTED, CONNECTING 등이 있다. 이 값을 사용해서 특정 상태일 때의 처리를 한다. 이러한 상태 정보를 이용하면 FastAPI에서 웹소켓을 더 효율적으로 사용할 수 있다.
예. 연결이 끊긴 경우 재연결 시도 로직 구현 등
[참고]
가장 빠른 풀스택을 위한 플라스크 & FastAPI
다음 내용
[파이썬] FastAPI - ORM 연동하기(SQLAlchemy)
이전 내용 [파이썬] FastAPI - 웹소켓이전 내용 [파이썬] FastAPI - 스트리밍 응답이전 내용 [파이썬] FastAPI - 쿼리 매개변수, 경로 매개변수, 백그라운드 태스크이전 내용 [파이썬] FastAPI - 정적 파일, AP
puppy-foot-it.tistory.com
'[파이썬 Projects] > <파이썬 웹개발>' 카테고리의 다른 글
[파이썬] FastAPI - SQLAlchemy와 CRUD (Depends, db.query) (0) | 2025.05.01 |
---|---|
[파이썬] FastAPI - ORM 연동하기(SQLAlchemy) (0) | 2025.04.28 |
[파이썬] FastAPI - 스트리밍 응답 (0) | 2025.04.27 |
[파이썬] FastAPI - 쿼리 매개변수, 경로 매개변수, 백그라운드 태스크 (0) | 2025.04.27 |
[파이썬] FastAPI - 정적 파일, API Router (0) | 2025.04.27 |