rs3gw 0.2.1

High-Performance AI/HPC Object Storage Gateway powered by scirs2-io
# WebSocket Event Streaming

rs3gw provides real-time event notifications via WebSocket connections, enabling clients to receive live updates about S3 operations.

## Endpoint

```
ws://localhost:9000/events/stream
```

Or with TLS:
```
wss://your-domain:9000/events/stream
```

## Query Parameters

- `bucket` (optional): Filter events by bucket name
- `prefix` (optional): Filter events by object key prefix
- `events` (optional): Comma-separated list of event types to subscribe to

## Event Types

- `object-created` - Object uploaded (PUT, POST, COPY)
- `object-removed` - Object deleted
- `object-metadata-changed` - Object metadata modified
- `object-accessed` - Object retrieved (GET)
- `multipart-upload-created` - Multipart upload initiated
- `multipart-upload-completed` - Multipart upload finalized
- `multipart-upload-aborted` - Multipart upload canceled
- `bucket-created` - Bucket created
- `bucket-removed` - Bucket deleted
- `bucket-policy-changed` - Bucket policy modified
- `bucket-tagging-changed` - Bucket tags modified

## Usage Examples

### JavaScript/Browser

```javascript
// Connect to event stream
const ws = new WebSocket('ws://localhost:9000/events/stream?bucket=my-bucket&events=object-created,object-removed');

// Handle connection open
ws.onopen = () => {
  console.log('Connected to rs3gw event stream');
};

// Handle incoming events
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === 'welcome') {
    console.log('Welcome:', data.message);
    console.log('Filters:', data.filters);
  } else {
    // S3 event
    console.log('Event:', data.eventType);
    console.log('Bucket:', data.bucket);
    console.log('Key:', data.key);
    console.log('Size:', data.size);
    console.log('ETag:', data.etag);
  }
};

// Handle errors
ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

// Handle connection close
ws.onclose = () => {
  console.log('Disconnected from event stream');
};
```

### Python

```python
import asyncio
import websockets
import json

async def stream_events():
    uri = "ws://localhost:9000/events/stream?bucket=my-bucket&prefix=data/"

    async with websockets.connect(uri) as websocket:
        while True:
            message = await websocket.recv()
            event = json.loads(message)

            if event.get('type') == 'welcome':
                print(f"Connected: {event['message']}")
                print(f"Filters: {event['filters']}")
            else:
                print(f"Event: {event['eventType']}")
                print(f"  Bucket: {event['bucket']}")
                if 'key' in event:
                    print(f"  Key: {event['key']}")
                if 'size' in event:
                    print(f"  Size: {event['size']} bytes")

asyncio.run(stream_events())
```

### Rust

```rust
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
use futures::{StreamExt, SinkExt};
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "ws://localhost:9000/events/stream?events=object-created";
    let (ws_stream, _) = connect_async(url).await?;
    let (mut write, mut read) = ws_stream.split();

    while let Some(msg) = read.next().await {
        let msg = msg?;

        if let Message::Text(text) = msg {
            let event: Value = serde_json::from_str(&text)?;

            if event["type"] == "welcome" {
                println!("Connected: {}", event["message"]);
            } else {
                println!("Event: {}", event["eventType"]);
                println!("  Bucket: {}", event["bucket"]);
                if let Some(key) = event.get("key") {
                    println!("  Key: {}", key);
                }
            }
        }
    }

    Ok(())
}
```

## Event Format

Each event is sent as a JSON object with the following structure:

```json
{
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "eventType": "object-created",
  "eventTime": "2024-01-15T10:30:45.123Z",
  "bucket": "my-bucket",
  "key": "path/to/object.txt",
  "size": 1024,
  "etag": "abc123def456",
  "metadata": {
    "custom-key": "custom-value"
  }
}
```

### Field Descriptions

- `eventId` - Unique identifier for this event (UUID v4)
- `eventType` - Type of event (see Event Types above)
- `eventTime` - ISO 8601 timestamp when event occurred
- `bucket` - Bucket name where event happened
- `key` - Object key (if applicable, omitted for bucket-level events)
- `size` - Object size in bytes (if applicable)
- `etag` - Object ETag (if applicable)
- `metadata` - Additional event-specific metadata (optional)

## Filtering

### Filter by Bucket

Only receive events for a specific bucket:

```
ws://localhost:9000/events/stream?bucket=my-bucket
```

### Filter by Prefix

Only receive events for objects matching a prefix:

```
ws://localhost:9000/events/stream?prefix=logs/2024/
```

### Filter by Event Types

Subscribe to specific event types only:

```
ws://localhost:9000/events/stream?events=object-created,object-removed
```

### Combine Filters

All filters can be combined:

```
ws://localhost:9000/events/stream?bucket=my-bucket&prefix=data/&events=object-created
```

## Performance Considerations

- The event broadcaster uses Tokio broadcast channels with a buffer of 1000 events
- If a client cannot keep up with event rate, older events may be dropped
- Multiple concurrent WebSocket connections are supported
- Events are sent in real-time as they occur
- Connection health is maintained via ping/pong frames

## Implementation Notes

- Events are broadcast to all connected clients simultaneously
- No events are stored or replayed - clients only receive events that occur after connection
- WebSocket connections automatically handle ping/pong for keepalive
- Clients should implement reconnection logic with exponential backoff for production use
- Welcome message is sent immediately upon connection to confirm filters