# slskr HTTP API - Advanced Features Guide ## Overview This guide documents advanced HTTP API features including logging, batch operations, WebSocket support, and response caching. ## Request/Response Logging ### Overview slskr includes built-in structured logging for HTTP API requests and responses to help with debugging, monitoring, and performance analysis. ### Configuration Logging is controlled by `SLSKR_LOG_LEVEL`, the `[app].log_level` config value, or `RUST_LOG` as a compatibility alias. The System -> Logs Web UI tab can also change the runtime log level without restarting the daemon. ```bash # Info level (default) ./target/release/slskr # Debug level (log all requests) SLSKR_LOG_LEVEL=debug ./target/release/slskr # Trace level (maximum detail) SLSKR_LOG_LEVEL=trace ./target/release/slskr # Warn level (errors and warnings only) SLSKR_LOG_LEVEL=warn ./target/release/slskr ``` ### Log Output Format Logs are printed to stderr with the following format: ``` [TIMESTAMP] METHOD PATH STATUS bytes in XXms [error] ``` **Example:** ``` [INFO] GET /api/stats 200 256 bytes in 1ms [WARN] POST /api/searches 400 128 bytes in 5ms - Invalid query [ERROR] GET /api/transfers 500 64 bytes in 100ms - Database error ``` ### Log Levels | Level | Purpose | Example | |-------|---------|---------| | **TRACE** | Maximum detail, includes all requests | Development only | | **DEBUG** | Request/response details | Debugging issues | | **INFO** | Normal operations (default) | Production | | **WARN** | Client errors (4xx) | Monitoring | | **ERROR** | Server errors (5xx) | Alerting | ### Extracting Logs Redirect logs to file for analysis: ```bash ./target/release/slskr > api.log 2>&1 & # Monitor in real-time tail -f api.log # Filter specific operations grep "POST" api.log | tail -20 # Find slow requests grep -E "in [0-9]{3,}ms" api.log ``` ### Log Analysis Tips Find slow requests: ```bash grep -oE "in [0-9]+ms" api.log | sort -V | tail -10 ``` Count requests by method: ```bash grep -oE "^\[.*\] [A-Z]+" api.log | cut -d' ' -f2 | sort | uniq -c ``` ## Batch Operations ### Overview Batch endpoints allow you to execute multiple API operations in a single HTTP request, reducing round-trip time and overhead. ### Batch Request Format **Endpoint:** `POST /api/batch` **Request Body:** ```json { "operations": [ { "id": "op1", "method": "GET", "path": "/api/stats" }, { "id": "op2", "method": "GET", "path": "/api/transfers" }, { "id": "op3", "method": "POST", "path": "/api/searches", "body": {"query":"music"} } ] } ``` Operation bodies may be native JSON values or pre-serialized JSON strings. Both forms are normalized before dispatch, so the Go and Python SDK builders can send their normal object bodies. Each operation is authenticated and run through the same router as a standalone request; its result contains the actual HTTP status. `config.timeoutMs` bounds each operation, and `config.continueOnError` controls whether a non-2xx result stops the batch. ### Batch Response Format **Response:** ```json { "results": [ { "id": "op1", "status": 200, "body": "{\"total_size\":1000000,...}" }, { "id": "op2", "status": 200, "body": "{\"transfers\":[...]}" }, { "id": "op3", "status": 201, "body": "{\"search_id\":\"search-123\"}" } ], "total_time_ms": 45 } ``` ### Use Cases **1. Get All Stats at Once** Instead of: ```bash curl /api/stats curl /api/transfers curl /api/messages ``` Use: ```bash curl -X POST /api/batch -d '{ "operations": [ {"id":"s","method":"GET","path":"/api/stats"}, {"id":"t","method":"GET","path":"/api/transfers"}, {"id":"m","method":"GET","path":"/api/messages"} ] }' ``` **2. Bulk Message Send** ```json { "operations": [ {"id":"1","method":"POST","path":"/api/v0/messages","body":{"username":"alice","body":"Hi"}}, {"id":"2","method":"POST","path":"/api/v0/messages","body":{"username":"bob","body":"Hello"}}, {"id":"3","method":"POST","path":"/api/v0/messages","body":{"username":"charlie","body":"Hey"}} ] } ``` **3. Cancel Multiple Transfers** ```json { "operations": [ {"id":"1","method":"DELETE","path":"/api/transfers/123"}, {"id":"2","method":"DELETE","path":"/api/transfers/124"}, {"id":"3","method":"DELETE","path":"/api/transfers/125"} ] } ``` ### Performance Benefits - **Reduced Latency**: Single request/response cycle instead of multiple - **Lower Overhead**: Amortize HTTP header overhead - **Ordered Execution**: Operations run in request order, with per-operation status - **Timing**: See total execution time in response ### Error Handling Each operation result includes status code. Check individual results: ```javascript const response = await fetch('/api/batch', { method: 'POST', headers: {'Authorization': 'Bearer token'}, body: JSON.stringify({operations}) }); const batch = await response.json(); // Check individual results batch.results.forEach(result => { if (result.status >= 400) { console.error(`Operation ${result.id} failed: ${result.status}`); } }); ``` ## WebSocket Support ### Overview WebSocket connections enable real-time, bidirectional communication for event streaming, eliminating the need for polling. ### Connection Upgrade **Endpoint:** `GET /api/events/ws` or `WS /api/events/stream` **Connection Header:** ``` GET /api/events/ws HTTP/1.1 Host: localhost:8080 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== ``` ### Event Types WebSocket sends JSON-formatted events: ```json { "type": "search.started", "id": "search-123", "data": {"query": "music", "room": null}, "timestamp": "2025-05-04T12:00:00Z" } ``` **Supported Event Types:** | Type | Description | |------|-------------| | `search.started` | New search initiated | | `search.completed` | Search finished | | `search.result` | New result received | | `transfer.started` | Transfer started | | `transfer.completed` | Transfer finished | | `transfer.failed` | Transfer error | | `message.received` | Incoming message | | `connection.status` | Connection status changed | | `room.joined` | Joined room | | `room.user_joined` | User joined room | | `room.user_left` | User left room | ### Topic Subscriptions Subscribe to specific topics to reduce message volume: ```javascript const token = sessionStorage.getItem('slskr-token') ?? ''; const protocols = token ? [`slskr.api-token.${encodeURIComponent(token)}`] : []; const ws = new WebSocket('ws://localhost:8080/api/events/ws', protocols); ws.onopen = () => { // Subscribe to only search events ws.send(JSON.stringify({ type: 'subscribe', data: {topics: ['search.started', 'search.completed']} })); }; ws.onmessage = (event) => { const msg = JSON.parse(event.data); console.log(`Event: ${msg.type}`, msg); }; ``` ### Example: Node.js Client ```javascript const WebSocket = require('ws'); const token = process.env.SLSKR_API_TOKEN || ''; const protocols = token ? [`slskr.api-token.${encodeURIComponent(token)}`] : []; const ws = new WebSocket('ws://localhost:8080/api/events/ws', protocols); ws.on('open', () => { console.log('Connected to WebSocket'); // Subscribe to transfers ws.send(JSON.stringify({ type: 'subscribe', data: {topics: ['transfer.started', 'transfer.completed']} })); }); ws.on('message', (data) => { const event = JSON.parse(data); if (event.type === 'transfer.started') { console.log(`Transfer started: ${event.data.filename}`); } else if (event.type === 'transfer.completed') { console.log(`Transfer completed: ${event.data.filename}`); } }); ws.on('error', (error) => { console.error('WebSocket error:', error); }); ws.on('close', () => { console.log('WebSocket closed'); }); ``` ### Ping/Pong for Keep-Alive WebSocket server sends periodic pings to detect stale connections: ```javascript ws.on('ping', () => { ws.pong(); // Automatically handled by most libraries }); ``` ## MediaCore Retrieval Cache The daemon caches retrieved MediaCore descriptors. This is a descriptor cache, not a generic HTTP response cache: there are no `/api/cache/stats` or `/api/cache/invalidate` endpoints. ### Cache Statistics ```bash curl -H "Authorization: Bearer token" \ http://127.0.0.1:5030/api/v0/mediacore/retrieve/stats ``` The response includes `totalRetrievals`, `cacheHits`, `cacheMisses`, `cacheHitRatio`, `activeCacheEntries`, `cacheSizeBytes`, and `expiredEntriesCleaned`. ### Selective Invalidation Clear all cached descriptors, or provide descriptor keys to clear only selected entries: ```bash curl -X POST -H "Authorization: Bearer token" \ -H "Content-Type: application/json" \ http://127.0.0.1:5030/api/v0/mediacore/retrieve/cache/clear \ -d '{"keys": ["content-id-1", "content-id-2"]}' ``` The request is bounded and deduplicated by the daemon. An empty body preserves the clear-all behavior. ## Monitoring & Observability ### Health Check with Details ```bash # Basic health check curl http://127.0.0.1:5030/api/health # Get version curl http://127.0.0.1:5030/api/version # Get capabilities curl http://127.0.0.1:5030/api/capabilities # Get current stats curl -H "Authorization: Bearer token" \ http://127.0.0.1:5030/api/stats ``` ### Metrics Endpoint ```bash curl -H "Authorization: Bearer token" \ http://127.0.0.1:5030/api/metrics ``` **Response:** ```json { "uptime_seconds": 3600, "requests_total": 15847, "requests_per_second": 4.4, "average_latency_ms": 45, "p95_latency_ms": 120, "p99_latency_ms": 250, "active_connections": 23, "cache_hit_rate": 85.7 } ``` ### Logging for Observability Enable debug logging for detailed request/response tracking: ```bash RUST_LOG=debug ./target/release/slskr 2>&1 | tee api.log ``` Parse logs for monitoring: ```bash # Requests per second tail -100 api.log | grep -c "^\[.*\]" # Average response time tail -100 api.log | grep -oE "in [0-9]+ms" | \ awk '{sum+=$2; count++} END {print sum/count}' # Error rate tail -100 api.log | grep -E "[45][0-9]{2}" | wc -l ``` ## Performance Optimization ### Batch Operations vs Sequential **Sequential (6 requests, ~500ms):** ```bash time for i in {1..6}; do curl /api/endpoint$i; done # ~500ms total (80ms each) ``` **Batch (1 request, ~100ms):** ```bash time curl -X POST /api/batch -d '{"operations":[...]}' # ~100ms total ``` **Improvement: 5x faster** ### WebSocket vs Polling **Polling (every 5 seconds, continuous traffic):** ``` HTTP GET /api/events - 5 req/min * 60 min = 300 req/hour Bandwidth: 300 * 2KB = 600KB/hour ``` **WebSocket (event-driven):** ``` 1 connection = 0 KB when idle Bandwidth: ~100 bytes per event Average events: 10/hour = 1KB/hour ``` **Improvement: 600x less bandwidth** ### Caching Benefits **Without caching:** ``` /api/stats: 100ms * 1000 req/min = 100s CPU/min ``` **With 85% cache hit rate:** ``` /api/stats: (15 * 100ms + 85 * 5ms) = 2.425s CPU/min ``` **Improvement: 41x faster aggregated** ## Best Practices ### 1. Use Batch Operations for Bulk Tasks ```javascript // ❌ Bad: 10 sequential requests for (const transfer of transfers) { await fetch(`/api/transfers/${transfer.id}`, {method: 'DELETE'}); } // ✅ Good: Single batch request const operations = transfers.map(t => ({ id: t.id, method: 'DELETE', path: `/api/transfers/${t.id}` })); await fetch('/api/batch', { method: 'POST', body: JSON.stringify({operations}) }); ``` ### 2. Use WebSocket for Real-Time Updates ```javascript // ❌ Bad: Polling every 5 seconds setInterval(() => fetch('/api/transfers').then(...), 5000); // ✅ Good: WebSocket events const token = sessionStorage.getItem('slskr-token') ?? ''; const protocols = token ? [`slskr.api-token.${encodeURIComponent(token)}`] : []; const ws = new WebSocket('ws://localhost:8080/api/events/ws', protocols); ws.onmessage = (evt) => { if (JSON.parse(evt.data).type === 'transfer.completed') { updateUI(); } }; ``` ### 3. Monitor MediaCore Retrieval Cache ```bash # Check cache performance curl -H "Authorization: Bearer token" \ http://127.0.0.1:5030/api/v0/mediacore/retrieve/stats | jq '.cacheHitRatio' # If < 70%, consider increasing TTLs ``` ### 4. Enable Logging in Production ```bash # Low overhead: INFO level RUST_LOG=info ./target/release/slskr > /var/log/slskr.log 2>&1 & # Monitor warnings/errors tail -f /var/log/slskr.log | grep -E "WARN|ERROR" ``` ## Troubleshooting ### WebSocket Connection Fails **Error:** `WebSocket connection failed` **Solutions:** 1. Check if server supports WebSocket (may be behind reverse proxy) 2. Verify authentication header is present 3. Check firewall/proxy settings for WebSocket support ### Batch Operation Timeout **Error:** `Request timeout` **Solutions:** 1. Reduce batch size (max 100 operations recommended) 2. Increase server timeout in reverse proxy 3. Use sequential operations for very large batches ### Retrieval Cache Metrics Missing **Error:** `GET /api/cache/stats` returns `404 Not Found` **Solutions:** 1. Use `/api/v0/mediacore/retrieve/stats` 2. Use `/api/v0/mediacore/retrieve/cache/clear` for descriptor invalidation ## References - [HTTP API Documentation](http-api.md) - [Deployment Guide](http-api-deployment.md) - [Performance Analysis](performance-analysis.md) - [RFC 6455 - WebSocket Protocol](https://tools.ietf.org/html/rfc6455) - [JSON Batch Request/Response RFC](https://jsonapi.org/ext/jsonpatch/)