A high-performance, WebSocket-based pub-sub system with REST API built using FastAPI. Arya provides real-time message broadcasting with thread-safe in-memory storage, backpressure handling, and comprehensive error management.
- WebSocket-based pub-sub with real-time message delivery
- REST API for topic management and system monitoring
- Thread-safe in-memory storage with asyncio locks
- Message history replay (configurable per subscription)
- Backpressure handling with bounded message queues
- Graceful error handling with proper error codes
- Health monitoring and system statistics
- Docker support for easy deployment
- Comprehensive test suite with 90%+ coverage
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ WebSocket │ │ PubSubManager │ │ REST API │
│ Handler │◄──►│ (Thread-Safe) │◄──►│ Endpoints │
│ │ │ │ │ │
│ • Connection │ │ • Topic Storage │ │ • Topic CRUD │
│ • Message Route │ │ • Subscriptions │ │ • Health Check │
│ • Broadcasting │ │ • Message Queue │ │ • Statistics │
└─────────────────┘ └──────────────────┘ └─────────────────┘
- PubSubManager: Thread-safe singleton managing topics, subscriptions, and message distribution
- WebSocketManager: Handles individual WebSocket connections with heartbeat and error handling
- Topic: Manages message history with configurable retention (default: 100 messages)
- Message Queue: Bounded queues (100 messages) for backpressure handling
arya/
├── src/arya/ # Main application code
│ ├── api/ # REST API endpoints
│ ├── core/ # Core business logic
│ │ ├── models.py # Pydantic data models
│ │ └── pubsub.py # PubSub manager
│ ├── websocket/ # WebSocket handlers
│ ├── config/ # Configuration management
│ ├── exceptions/ # Custom exceptions
│ └── utils/ # Utility functions
├── tests/ # Test suite
├── simple_test.py # Contract compliance test
├── test_client.py # Interactive demo client
├── docker-compose.yml # Docker setup
└── requirements.txt # Dependencies
# Build and run with Docker
docker build -t arya-pubsub .
docker run -p 8000:8000 arya-pubsub
# Or use docker-compose
docker-compose up --build# Install dependencies
pip install -r requirements.txt
# Run the server
PYTHONPATH=src python src/main.py
# Or with uvicorn directly (development with auto-reload)
PYTHONPATH=src uvicorn arya.api.main:create_app --factory --host 0.0.0.0 --port 8000 --reloadThe server will start on http://localhost:8000
curl -X POST "http://localhost:8000/topics" \
-H "Content-Type: application/json" \
-d '{"name": "orders"}'
# Response: {"status": "created", "topic": "orders"}curl -X DELETE "http://localhost:8000/topics/orders"
# Response: {"status": "deleted", "topic": "orders"}curl "http://localhost:8000/topics"
# Response: {
# "topics": [
# {"name": "orders", "subscribers": 3, "messages": 42}
# ]
# }curl "http://localhost:8000/health"
# Response: {
# "uptime_sec": 3600,
# "topics": 5,
# "subscribers": 15
# }curl "http://localhost:8000/stats"
# Response: {
# "topics": {
# "orders": {"messages": 42, "subscribers": 3},
# "notifications": {"messages": 15, "subscribers": 7}
# }
# }Connect to WebSocket endpoint: ws://localhost:8000/ws
All WebSocket messages use JSON format with the following structure:
{
"type": "message_type",
"request_id": "optional_correlation_id",
// ... additional fields based on message type
}{
"type": "subscribe",
"topic": "orders",
"client_id": "unique-client-123",
"last_n": 5,
"request_id": "sub-1"
}Response:
{
"type": "ack",
"message": "Subscribed to topic 'orders'",
"request_id": "sub-1",
"timestamp": "2024-01-15T10:30:00Z"
}{
"type": "unsubscribe",
"topic": "orders",
"client_id": "unique-client-123",
"request_id": "unsub-1"
}{
"type": "publish",
"topic": "orders",
"message": {
"order_id": 12345,
"customer_id": "cust-789",
"amount": 99.99,
"items": ["item1", "item2"]
},
"request_id": "pub-1"
}{
"type": "event",
"topic": "orders",
"message": {
"id": "msg-uuid-here",
"payload": {
"order_id": 12345,
"customer_id": "cust-789",
"amount": 99.99
},
"timestamp": "2024-01-15T10:30:00Z"
}
}// Send ping
{"type": "ping", "request_id": "ping-1"}
// Receive pong
{"type": "pong", "request_id": "ping-1", "timestamp": "2024-01-15T10:30:00Z"}{
"type": "error",
"error_code": "TOPIC_NOT_FOUND",
"error_message": "Topic 'nonexistent' not found",
"request_id": "sub-1",
"timestamp": "2024-01-15T10:30:00Z"
}Error Codes:
BAD_REQUEST: Invalid request format or parametersTOPIC_NOT_FOUND: Requested topic doesn't existSLOW_CONSUMER: Client is too slow, connection terminatedINTERNAL_ERROR: Server-side error
# Create and activate virtual environment (if not already done)
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Run all tests
PYTHONPATH=src python -m pytest tests/ -v
# Run with coverage (if pytest-cov is installed)
PYTHONPATH=src python -m pytest tests/ --cov=src --cov-report=html
# Run specific test categories
PYTHONPATH=src python -m pytest tests/test_models.py tests/test_pubsub.py -v # Unit tests
PYTHONPATH=src python -m pytest tests/test_api.py tests/test_websocket.py -v # Integration tests- Unit Tests:
test_models.py,test_pubsub.py - Integration Tests:
test_api.py,test_websocket.py - Coverage Target: 90%+
# Start the service first (in one terminal):
source venv/bin/activate
PYTHONPATH=src python src/main.py
# Then run the test (in another terminal):
source venv/bin/activate
python simple_test.py# Start the service first (in one terminal):
source venv/bin/activate
PYTHONPATH=src python src/main.py
# Then run the test client (in another terminal):
source venv/bin/activate
python test_client.pyLOG_LEVEL: Logging level (DEBUG, INFO, WARNING, ERROR)MAX_MESSAGE_HISTORY: Maximum messages per topic (default: 100)MAX_QUEUE_SIZE: Maximum message queue size per connection (default: 100)HEARTBEAT_INTERVAL: Heartbeat interval in seconds (default: 30)
- Message History: Adjust per-topic message retention
- Queue Size: Tune backpressure thresholds
- Connection Limits: Configure via uvicorn workers
- Memory Usage: Monitor with
/statsendpoint
# Use multi-stage build for smaller image
FROM python:3.11-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
RUN useradd --create-home --shell /bin/bash arya
USER arya
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]apiVersion: apps/v1
kind: Deployment
metadata:
name: arya-pubsub
spec:
replicas: 3
selector:
matchLabels:
app: arya-pubsub
template:
metadata:
labels:
app: arya-pubsub
spec:
containers:
- name: arya-pubsub
image: arya-pubsub:latest
ports:
- containerPort: 8000
env:
- name: LOG_LEVEL
value: "INFO"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"For horizontal scaling, use a load balancer with session affinity or implement client-side connection management for WebSocket persistence.
# Basic health check
curl http://localhost:8000/health
# Detailed statistics
curl http://localhost:8000/stats- Connection Count: Active WebSocket connections
- Topic Count: Number of active topics
- Message Throughput: Messages per second
- Memory Usage: Topic message history size
- Error Rates: Failed connections and operations
The system provides structured logging with different levels:
# Critical operations
logger.info("Client subscribed to topic")
# Debug information
logger.debug("Message added to topic history")
# Errors and warnings
logger.error("Failed to broadcast message")- Input Validation: Pydantic models validate all inputs
- Error Handling: Sanitized error messages
- Resource Limits: Bounded queues and message history
- Non-root User: Docker runs as non-privileged user
- Authentication: Add JWT or API key authentication
- Rate Limiting: Implement per-client rate limits
- TLS/SSL: Use HTTPS/WSS in production
- Network Security: Deploy behind reverse proxy
- Monitoring: Add security event logging
- In-Memory Only: No persistence across restarts
- Single Instance: No clustering support
- No Authentication: Open access (add auth layer)
- Message Size: No explicit size limits (JSON parsing limits apply)
- Backpressure Policy: Disconnect slow consumers (prevents memory exhaustion)
- Message History: 100 messages per topic (configurable)
- Thread Safety: AsyncIO locks (not thread-safe across OS threads)
- Error Handling: Fail fast with clear error messages
arya/
├── main.py # FastAPI application
├── models.py # Pydantic data models
├── pubsub.py # Core pub-sub manager
├── websocket_handler.py # WebSocket connection management
├── requirements.txt # Python dependencies
├── Dockerfile # Container configuration
├── docker-compose.yml # Local development setup
├── pytest.ini # Test configuration
├── tests/ # Test suite
│ ├── conftest.py # Test fixtures
│ ├── test_models.py # Model validation tests
│ ├── test_pubsub.py # Core logic tests
│ ├── test_api.py # REST API tests
│ └── test_websocket.py # WebSocket tests
└── README.md # This file
- New Message Types: Add to
models.pyand handle inwebsocket_handler.py - New Endpoints: Add to
main.pywith proper error handling - Storage Backends: Extend
PubSubManagerwith pluggable storage - Authentication: Add middleware to FastAPI application
- Fork the repository
- Create feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit pull request
import asyncio
import websockets
import json
async def client_example():
uri = "ws://localhost:8000/ws"
async with websockets.connect(uri) as websocket:
# Subscribe to topic
subscribe_msg = {
"type": "subscribe",
"topic": "orders",
"client_id": "python-client-1",
"last_n": 5
}
await websocket.send(json.dumps(subscribe_msg))
# Receive acknowledgment
response = await websocket.recv()
print(f"Subscribe response: {response}")
# Publish a message
publish_msg = {
"type": "publish",
"topic": "orders",
"message": {"order_id": 123, "amount": 99.99}
}
await websocket.send(json.dumps(publish_msg))
# Listen for events
while True:
message = await websocket.recv()
data = json.loads(message)
if data["type"] == "event":
print(f"Received: {data['message']['payload']}")
# Run the client
asyncio.run(client_example())const ws = new WebSocket('ws://localhost:8000/ws');
ws.onopen = function() {
// Subscribe to topic
const subscribeMsg = {
type: 'subscribe',
topic: 'orders',
client_id: 'js-client-1',
last_n: 10
};
ws.send(JSON.stringify(subscribeMsg));
};
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === 'event') {
console.log('Received message:', data.message.payload);
} else if (data.type === 'ack') {
console.log('Operation acknowledged:', data.message);
} else if (data.type === 'error') {
console.error('Error:', data.error_message);
}
};
// Publish message
function publishMessage(orderData) {
const publishMsg = {
type: 'publish',
topic: 'orders',
message: orderData
};
ws.send(JSON.stringify(publishMsg));
}
// Example usage
publishMessage({order_id: 456, customer: '[email protected]', amount: 149.99});This project is licensed under the MIT License - see the LICENSE file for details.
For questions, issues, or contributions:
- Create an issue on GitHub
- Check existing documentation
- Review test cases for usage examples
Built with ❤️ using FastAPI, WebSockets, and Python asyncio