Flask 2.x의 비동기 뷰 및 WebSocket 통합
Olivia Novak
Dev Intern · Leapcell

소개
끊임없이 진화하는 백엔드 개발 환경에서 응답성과 실시간 기능은 바람직한 기능에서 필수 요구 사항으로 전환되었습니다. 전통적인 동기 Python 프레임워크는 강력하지만 데이터베이스 쿼리, 외부 API 호출 또는 장기 폴링 요청과 같은 I/O 바운드 작업을 처리할 때 종종 한계에 직면합니다. 이는 특히 부하가 많을 때 지연 시간을 증가시키고 처리량을 감소시킬 수 있습니다. Python의 async/await
와 Flask 2.x에의 원활한 통합은 이러한 문제를 해결하는 강력한 솔루션을 제공하여 개발자가 높은 동시성과 확장성을 갖춘 웹 애플리케이션을 구축할 수 있도록 합니다. 또한 클라이언트와 서버 간의 즉각적인 양방향 통신에 대한 수요 증가는 WebSocket을 중요한 기술로 만들었고, 대화형 기능 개발 방식을 변화시켰습니다. 이 문서는 Flask 2.x가 개발자가 비동기 뷰를 활용하여 성능을 향상시키고 WebSocket을 통합하여 풍부한 실시간 사용자 경험을 제공하는 방법을 탐구하며, 현대적이고 응답성이 뛰어난 웹 서비스를 구축하기 위한 기반을 제공합니다.
핵심 개념 및 메커니즘
구현 세부 사항을 살펴보기 전에 Flask에서 비동기 프로그래밍 및 WebSocket 통신을 이해하는 데 중요한 몇 가지 기본 개념을 명확히 해보겠습니다.
비동기 프로그래밍 (async/await): 이 패러다임은 프로그램이 I/O 작업을 시작하고 (예: 데이터베이스에서 읽기) 완료될 때까지 기다리는 대신 다른 작업을 실행하도록 전환할 수 있도록 합니다. I/O 작업이 완료되면 프로그램은 원래 작업을 재개할 수 있습니다. Python에서 async
는 "코루틴" (비동기 함수)을 정의하고, await
는 코루틴 또는 I/O 작업과 같은 awaitable이 완료될 때까지 코루틴의 실행을 일시 중지합니다. 이러한 논블로킹 특성은 컨텍스트 전환 및 리소스 경합으로 인한 오버헤드를 자주 발생하는 전통적인 멀티스레딩에 의존하지 않고도 동시성을 크게 향상시킵니다.
ASGI (Asynchronous Server Gateway Interface): WSGI (Web Server Gateway Interface)가 Python 웹 서버와 동기 웹 애플리케이션 간의 인터페이스를 표준화하는 것처럼, ASGI도 비동기 애플리케이션에 대해 동일한 역할을 합니다. Flask 2.x의 ASGI 지원은 Uvicorn 또는 Hypercorn와 같은 비동기 서버에서 실행할 수 있도록 해주며, 이들은 동시 비동기 요청을 효율적으로 처리하도록 설계되었습니다.
WebSockets: HTTP의 요청-응답 모델과 달리 WebSockets는 단일하고 오래 지속되는 TCP 연결을 통해 전이중 통신 채널을 제공합니다. 이는 클라이언트와 서버 모두 언제든지 서로 메시지를 보낼 수 있음을 의미하며, 채팅 애플리케이션, 라이브 대시보드 또는 공동 작업 도구와 같은 실시간 상호 작용을 촉진합니다. 초기 HTTP 핸드셰이크 후 연결은 WebSocket으로 "업그레이드"되어 지속적이고 낮은 지연 시간의 데이터 교환을 가능하게 합니다.
Flask 2.x의 비동기 뷰
Flask 2.x는 비동기 뷰 함수를 정의할 수 있도록 하여 async/await
를 채택합니다. 이는 특히 블로킹되는 I/O 바운드 작업에 유용합니다.
동기 Flask 뷰를 고려해 봅시다:
from flask import Flask, jsonify import time app = Flask(__name__) @app.route("/sync-data") def get_sync_data(): time.sleep(2) # Blocking I/O operation 시뮬레이션 return jsonify({"message": "Data fetched synchronously"}) if __name__ == "__main__": app.run(debug=True)
이 예에서 여러 클라이언트가 동시에 /sync-data
를 히트하면 각 요청은 2초간의 time.sleep()
이 순차적으로 완료될 때까지 기다려야 하므로 성능이 저하됩니다.
이제 비동기 뷰로 다시 작성해 보겠습니다:
from flask import Flask, jsonify import asyncio app = Flask(__name__) @app.route("/async-data") async def get_async_data(): await asyncio.sleep(2) # Non-blocking I/O operation 시뮬레이션 return jsonify({"message": "Data fetched asynchronously"}) if __name__ == "__main__": # async 뷰를 실행하려면 Uvicorn과 같은 ASGI 서버가 필요합니다 # 일반적으로 `uvicorn your_app_file:app --reload`로 실행합니다 pass
이것을 진정한 비동기식으로 실행하려면 ASGI 서버가 필요합니다. 예를 들어 파일 이름이 app.py
인 경우 uvicorn app:app --reload
를 사용하여 실행할 수 있습니다. 여러 비동기 요청이 /async-data
를 히트하면 asyncio.sleep(2)
는 서버 스레드를 블로킹하지 않고, 대신 sleep이 완료될 때까지 기다리는 동안 서버가 다른 요청이나 작업을 처리하도록 전환할 수 있습니다. 이는 애플리케이션의 동시 연결 처리 능력을 크게 향상시킵니다. aiohttp
를 사용하여 외부 API 호출, asyncpg
를 사용하여 PostgreSQL 또는 aiomysql
을 사용하여 MySQL에 유사한 논블로킹 방식으로 통합할 수 있습니다.
WebSocket 지원
Flask 자체에는 네이티브 빌트인 WebSocket 지원이 없습니다. 그러나 Flask-SocketIO
와 같은 Flask 확장 프로그램을 사용하거나 Flask와 함께 WebSocket을 지원하는 ASGI 툴킷을 직접 사용하여 원활하게 통합할 수 있습니다. 단순성과 광범위한 채택을 위해 Flask-SocketIO
에 중점을 둘 것입니다. 이는 더 높은 수준의 추상화를 제공하기 때문입니다.
먼저 필요한 라이브러리를 설치합니다:
pip install Flask Flask-SocketIO eventlet
eventlet
(또는 gevent
)은 Flask-SocketIO가 Python 표준 라이브러리를 논블로킹 처리하도록 패치하는 데 필요합니다. ASGI와 함께 진정한 async/await
WebSocket의 경우 python-socketio
를 ASGI 서버와 직접 사용할 수 있습니다.
Flask-SocketIO를 사용하는 간단한 채팅 애플리케이션 예제입니다:
from flask import Flask, render_template from flask_socketio import SocketIO, emit app = Flask(__name__) app.config['SECRET_KEY'] = 'your_secret_key' # 강력한 비밀 키로 교체 socketio = SocketIO(app, async_mode='eventlet') # 'async_mode'는 'gevent', 'threading' 또는 uvicorn이 있는 'asyncio'일 수 있습니다 @app.route('/') def index(): return render_template('index.html') @socketio.on('message') def handle_message(data): print(f"Received message: {data['data']}") emit('my response', {'data': data['data']}, broadcast=True) @socketio.on('connect') def test_connect(): print("Client connected") emit('my response', {'data': 'Connected!'}) @socketio.on('disconnect') def test_disconnect(): print("Client disconnected") if __name__ == '__main__': # Eventlet의 경우 `socketio.run()`으로 실행합니다 socketio.run(app, debug=True)
그리고 해당 templates/index.html
파일입니다:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Flask WebSocket Chat</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script> <script type="text/javascript" charset="utf-8"> var socket = io(); socket.on('connect', function() { socket.emit('message', {data: 'I\'m connected!'}); }); socket.on('my response', function(msg) { document.getElementById('messages').innerHTML += '<p>' + msg.data + '</p>'; }); function sendMessage() { var input = document.getElementById('chatInput'); socket.emit('message', {data: input.value}); input.value = ''; } </script> </head> <body> <h1>Flask WebSocket Chat</h1> <input type="text" id="chatInput" onkeydown="if (event.keyCode == 13) sendMessage()"> <button onclick="sendMessage()">Send</button> <div id="messages"></div> </body> </html>
이 예에서 @socketio.on('message')
는 클라이언트가 message
이벤트를 보낼 때마다 실행될 함수를 데코레이션합니다. emit('my response', ..., broadcast=True)
줄은 수신된 메시지를 연결된 모든 클라이언트에 다시 보냅니다. 효과적으로 간단한 채팅방을 만듭니다.
WebSockets와 비동기 뷰 결합 (ASGI 기반)
Flask-SocketIO
와 eventlet
은 WebSocket 기능을 제공하지만, ASGI 서버를 사용하여 Flask 2.x의 진정한 async/await
네이티브 경험을 위해서는 python-socketio
를 직접 통합할 수 있습니다. 이 접근 방식은 Flask 2.x 코어의 async/await
패러다임에 더 잘 맞습니다.
먼저 ASGI에 대해 특별히 python-socketio
를 설치합니다:
pip install Flask python-socketio aiohttp uvicorn
예시입니다:
from flask import Flask, render_template_string import socketio import asyncio # Socket.IO 서버 생성 sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*') # Flask 앱 생성 flask_app = Flask(__name__) # Flask 앱을 Socket.IO ASGI 앱으로 래핑 app = socketio.ASGIApp(sio, flask_app) @flask_app.route('/') async def index(): return render_template_string("" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Flask Async WebSocket Chat</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script> <script type="text/javascript" charset="utf-8"> var socket = io(); socket.on('connect', function() { console.log('Connected!'); socket.emit('message', {data: 'I\'m connected async!'}); }); socket.on('my response', function(msg) { document.getElementById('messages').innerHTML += '<p>' + msg.data + '</p>'; }); function sendMessage() { var input = document.getElementById('chatInput'); socket.emit('message', {data: input.value}); input.value = ''; } </script> </head> <body> <h1>Flask Async WebSocket Chat</h1> <input type="text" id="chatInput" onkeydown="if (event.keyCode == 13) sendMessage()"> <button onclick="sendMessage()">Send</button> <div id="messages"></div> </body> </html> ") @sio.on('connect') async def connect(sid, environ): print(f"Client connected: {sid}") await sio.emit('my response', {'data': 'Connected to async server!'}, room=sid) @sio.on('message') async def message(sid, data): print(f"Received message from {sid}: {data['data']}") await sio.emit('my response', data, broadcast=True, skip_sid=sid) # 발신자 제외 모두에게 에코 await sio.emit('my response', {'data': f"You said: {data['data']}"}, room=sid) # 발신자에게 확인 @sio.on('disconnect') async def disconnect(sid): print(f"Client disconnected: {sid}") if __name__ == '__main__': # 이것을 실행하려면 Uvicorn을 사용하세요: `uvicorn your_app_file:app --reload --port 5000` pass
이 설정에서 sio = socketio.AsyncServer(async_mode='asgi', ...)
는 ASGI 호환 비동기 Socket.IO 서버를 생성합니다. app = socketio.ASGIApp(sio, flask_app)
는 Flask 애플리케이션을 Socket.IO ASGI 애플리케이션으로 래핑하여 HTTP 라우트와 WebSocket 연결 모두 단일 ASGI 서버 (예: Uvicorn)로 제공할 수 있습니다. Socket.IO 이벤트 핸들러는 이제 async
함수가 되어 Flask 2.x의 async/await
패러다임과 완벽하게 일치합니다. 이 강력한 조합은 고도로 동시적인 웹 요청과 실시간 양방향 통신을 단일 비동기 프레임워크 내에서 가능하게 합니다.
결론
Flask 2.x의 async/await
채택은 개발자가 성능이 중요하고 I/O 바운드 작업에 접근하는 방식을 혁신하여 확장 가능한 웹 애플리케이션을 구축할 수 있는 강력한 기반을 제공합니다. Flask-SocketIO
와 같은 확장 프로그램을 통하거나 직접적인 ASGI 통합을 통해 WebSocket 지원과 결합될 때 Flask는 고도로 대화형이고 실시간 사용자 경험을 창출할 수 있도록 합니다. 비동기 뷰와 WebSockets를 활용함으로써 개발자는 동시성과 지속적인 통신을 효율적으로 처리하여 사용자 참여와 운영 효율성을 향상시키는 현대적이고 응답성이 뛰어난 애플리케이션을 구축할 수 있습니다. Flask의 미래는 의심할 여지 없이 비동기적이며 실시간입니다.