Storage Service
The Storage Service (SFS - Simple File Share) provides file storage capabilities backed by the GFS distributed file system.
Features
- File Upload/Download: Stream large files with progress tracking
- Namespaces: Organize files into logical namespaces
- Progress Tracking: Real-time upload/download progress via SSE
- Authentication: JWT-based access control
API Endpoints
Files
| Method | Endpoint | Description |
|---|---|---|
| GET | /storage/files | List files in namespace |
| GET | /storage/:namespace/:filename | View/serve a file inline |
| GET | /storage/download/:namespace/:filename | Force-download a file |
| POST | /storage/:namespace/:filename | Upload a file |
| DELETE | /storage/:namespace/:filename | Delete a file |
All CRUD operations use the same path pattern (/storage/:namespace/:filename), which enables automatic gateway cache invalidation — uploads and deletes immediately evict cached GET responses for the same path.
Namespaces
| Method | Endpoint | Description |
|---|---|---|
| GET | /storage/namespaces | List namespaces |
| POST | /storage/namespaces | Create namespace |
| DELETE | /storage/namespaces/:name | Delete namespace |
| PUT | /storage/namespaces/:name | Update namespace |
Progress (SSE)
| Endpoint | Description |
|---|---|
/sse/progress?id=<transfer_id> | Stream transfer progress |
File Upload Flow
File Download Flow
Downloads use sequential streaming for memory efficiency:
- Chunks streamed directly to HTTP response
- No buffering of entire chunks in memory
- Constant memory usage regardless of file size
- Automatic failover to replicas on read errors
Frontend Download Mechanism
The frontend triggers downloads using a hidden iframe rather than an anchor element (<a> click) or fetch()+blob:
const iframe = document.createElement("iframe");
iframe.style.display = "none";
iframe.src = downloadUrl;
document.body.appendChild(iframe);
setTimeout(() => iframe.remove(), 60000);
This avoids a page reload that occurs with link.click() on cross-origin URLs (cloud.eddisonso.com → storage.cloud.eddisonso.com). The download attribute on <a> tags is ignored by browsers for cross-origin links, causing top-level navigation. The backend's Content-Disposition: attachment header tells the browser to save the file instead of rendering it, and the iframe keeps the navigation off the main page.
Progress Tracking
Progress is tracked via Server-Sent Events (SSE):
const eventSource = new EventSource('/sse/progress?id=<transfer_id>');
eventSource.onmessage = (event) => {
const { bytes, total, done, direction } = JSON.parse(event.data);
console.log(`${direction}: ${bytes}/${total} bytes`);
};
Namespaces
Namespaces provide logical separation of files:
default- Default namespace for unauthenticated access- Custom namespaces for user organization
- Hidden namespaces (not shown in UI)
Deployment
- Replicas: 4 backend pods spread across rp1/rp2/rp3/rp4
- Topology:
topologySpreadConstraintswithmaxSkew: 1andDoNotScheduleensures one pod per node - Node selector:
backend: "true"
Configuration
| Flag | Description | Default |
|---|---|---|
-addr | Listen address | :8080 |
-master | GFS master address | - |
-static | Static files directory | - |
Database Schema
-- Namespaces stored in PostgreSQL
CREATE TABLE namespaces (
name TEXT PRIMARY KEY,
hidden BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW()
);
Files and chunks are stored in GFS, with metadata managed by the GFS Master.