Skip to main content

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

MethodEndpointDescription
GET/storage/filesList files in namespace
GET/storage/:namespace/:filenameView/serve a file inline
GET/storage/download/:namespace/:filenameForce-download a file
POST/storage/:namespace/:filenameUpload a file
DELETE/storage/:namespace/:filenameDelete 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

MethodEndpointDescription
GET/storage/namespacesList namespaces
POST/storage/namespacesCreate namespace
DELETE/storage/namespaces/:nameDelete namespace
PUT/storage/namespaces/:nameUpdate namespace

Progress (SSE)

EndpointDescription
/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.comstorage.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: topologySpreadConstraints with maxSkew: 1 and DoNotSchedule ensures one pod per node
  • Node selector: backend: "true"

Configuration

FlagDescriptionDefault
-addrListen address:8080
-masterGFS master address-
-staticStatic 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.