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. All namespaces are owned by a single user (the creator) and default to private on creation.

Visibility

ValueNameBehavior
0PrivateAccessible by the owner and service accounts scoped to that owner only
1PublicAnyone with the direct link can read; never listed or advertised to other users

The namespace listing endpoint (GET /storage/namespaces) returns only the authenticated caller's own namespaces — other users' namespaces are never surfaced regardless of visibility. Unauthenticated callers receive an empty list.

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.