← Назад к вопросам
Можно ли организовать REST поверх WebSocket?
2.7 Senior🔥 111 комментариев
#REST API и HTTP#Архитектура и паттерны#Асинхронность и многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
REST поверх WebSocket
Да, можно организовать REST-подобный API поверх WebSocket. Это называется RPC (Remote Procedure Call) или JSON-RPC протокол и часто используется для real-time приложений.
Зачем нужно
Обычный REST REST поверх WebSocket
────────────── ─────────────────────
HTTP connection Одно WebSocket соединение
Один запрос-ответ Множество операций
Новое соединение Постоянное соединение
Ovehead на каждый Низкий overhead
Энергозатратно Энергоэффективно
JSON-RPC протокол
# Клиент отправляет структурированный запрос
request = {
"jsonrpc": "2.0",
"method": "POST /api/v1/users", # Как REST endpoint
"params": {
"name": "John",
"email": "john@example.com"
},
"id": 1 # Для связи request-response
}
# Сервер отправляет ответ
response = {
"jsonrpc": "2.0",
"result": {"id": 1, "name": "John"},
"id": 1 # Совпадает с request id
}
Реализация в FastAPI + WebSocket
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import Dict, Any
import json
import uuid
app = FastAPI()
class WebSocketRPCServer:
def __init__(self):
self.handlers = {}
self._register_handlers()
def _register_handlers(self):
"""Регистрируем обработчики как REST endpoints"""
# POST /users
self.handlers["POST /users"] = self.create_user
# GET /users/{id}
self.handlers["GET /users/{id}"] = self.get_user
# PATCH /users/{id}
self.handlers["PATCH /users/{id}"] = self.update_user
# DELETE /users/{id}
self.handlers["DELETE /users/{id}"] = self.delete_user
async def create_user(self, params: Dict[str, Any]):
"""Обработчик создания пользователя"""
# Вместо return они возвращают JSON
user_id = str(uuid.uuid4())
return {"id": user_id, "name": params["name"], "email": params["email"]}
async def get_user(self, params: Dict[str, Any]):
user_id = params["id"]
# Получаем из БД
return {"id": user_id, "name": "John", "email": "john@example.com"}
async def update_user(self, params: Dict[str, Any]):
return {"id": params["id"], "updated": True}
async def delete_user(self, params: Dict[str, Any]):
return {"id": params["id"], "deleted": True}
async def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
"""Обрабатываем JSON-RPC запрос"""
method = request.get("method")
params = request.get("params", {})
request_id = request.get("id")
try:
if method not in self.handlers:
return {
"jsonrpc": "2.0",
"error": {"code": -32601, "message": f"Method {method} not found"},
"id": request_id
}
# Вызываем обработчик
result = await self.handlers[method](params)
return {
"jsonrpc": "2.0",
"result": result,
"id": request_id
}
except Exception as e:
return {
"jsonrpc": "2.0",
"error": {"code": -32603, "message": str(e)},
"id": request_id
}
rpc_server = WebSocketRPCServer()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
try:
while True:
# Получаем JSON-RPC запрос
data = await websocket.receive_text()
request = json.loads(data)
# Обрабатываем
response = await rpc_server.handle_request(request)
# Отправляем ответ
await websocket.send_text(json.dumps(response))
except WebSocketDisconnect:
print("Client disconnected")
Клиентская часть (JavaScript)
class WebSocketRPCClient {
constructor(url) {
this.ws = new WebSocket(url);
this.requestId = 0;
this.pendingRequests = new Map();
this.ws.onmessage = (event) => {
const response = JSON.parse(event.data);
const requestId = response.id;
if (this.pendingRequests.has(requestId)) {
const { resolve, reject } = this.pendingRequests.get(requestId);
if (response.error) {
reject(new Error(response.error.message));
} else {
resolve(response.result);
}
this.pendingRequests.delete(requestId);
}
};
}
async request(method, params = {}) {
const requestId = ++this.requestId;
const request = {
jsonrpc: "2.0",
method,
params,
id: requestId
};
return new Promise((resolve, reject) => {
this.pendingRequests.set(requestId, { resolve, reject });
this.ws.send(JSON.stringify(request));
});
}
// REST-like методы
async post(endpoint, data) {
return this.request(`POST ${endpoint}`, data);
}
async get(endpoint, id) {
return this.request(`GET ${endpoint}/${id}`, { id });
}
async patch(endpoint, id, data) {
return this.request(`PATCH ${endpoint}/${id}`, { id, ...data });
}
async delete(endpoint, id) {
return this.request(`DELETE ${endpoint}/${id}`, { id });
}
}
// Использование
const client = new WebSocketRPCClient('ws://localhost:8000/ws');
// REST-like API
const user = await client.post('/users', {
name: 'John',
email: 'john@example.com'
});
console.log(user); // { id: '...', name: 'John', ... }
const fetchedUser = await client.get('/users', user.id);
console.log(fetchedUser);
await client.patch('/users', user.id, { name: 'Jane' });
await client.delete('/users', user.id);
Пример более сложной реализации
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Callable, Dict, Any
class AdvancedRPCServer:
def __init__(self, db: AsyncSession):
self.db = db
self.methods: Dict[str, Callable] = {}
def register_method(self, name: str):
"""Декоратор для регистрации метода"""
def decorator(func):
self.methods[name] = func
return func
return decorator
async def handle(self, request: Dict) -> Dict:
method = request.get("method")
if method not in self.methods:
return self._error_response(
-32601,
f"Method '{method}' not found",
request.get("id")
)
try:
handler = self.methods[method]
result = await handler(request.get("params", {}))
return self._success_response(result, request.get("id"))
except Exception as e:
return self._error_response(-32603, str(e), request.get("id"))
def _success_response(self, result, request_id):
return {"jsonrpc": "2.0", "result": result, "id": request_id}
def _error_response(self, code, message, request_id):
return {
"jsonrpc": "2.0",
"error": {"code": code, "message": message},
"id": request_id
}
# Использование
rpc = AdvancedRPCServer(db=None)
@rpc.register_method("GET /users/{id}")
async def get_user(params):
user_id = params["id"]
# Получаем из БД
return {"id": user_id, "name": "John"}
@rpc.register_method("POST /users")
async def create_user(params):
# Создаём пользователя
return {"id": "123", "name": params["name"]}
Advantages и Disadvantages
Advantages:
- Одно соединение для множества операций
- Низкий overhead (нет HTTP headers на каждый запрос)
- Real-time уведомления
- Двусторонняя коммуникация
- Энергоэффективнее чем polling
Disadvantages:
- Сложнее для отладки (нельзя просто curl)
- Кэширование сложнее (HTTP кэши не работают)
- Масштабирование сложнее (нужно управлять соединениями)
- Требует WebSocket поддержки (не везде)
Когда использовать
✅ Используй REST + WebSocket поверх WebSocket:
- Real-time приложения (чаты, игры)
- High-frequency обновления (dashboards)
- Много операций на одном соединении
- Мобильные приложения (экономия батареи)
❌ Используй обычный REST:
- Stateless API
- Public API (CDN, кэширование)
- Простые операции
- Когда не нужна real-time
Итого
- Да, REST можно организовать поверх WebSocket
- JSON-RPC - стандартный протокол для этого
- Требует больше кода чем обычный REST
- Стоит использовать если нужна real-time и много операций
- Сложнее отлаживать - используй DevTools WebSocket inspector