Skip to content

Commit

Permalink
Enable metrics route in production
Browse files Browse the repository at this point in the history
  • Loading branch information
mario4tier committed Sep 1, 2024
1 parent cd8d16d commit 329e018
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 225 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { spawn } from 'child_process';
import * as fs from 'fs';
import path from 'path';
import { getIdPrefixes } from '../utils/strings.js';
import { isValidBlobId } from '../utils/validation.js';
// Test Blob Info
//
// MIME type: image/png
Expand All @@ -10,27 +12,27 @@ import path from 'path';
// Encoded size (including metadata): 62.0 MiB
// Sui object ID: 0x0ebad3b13ee9bc64f8d6370e71d3408a43febaa017a309d2367117afe144ae8c
// Cache-Control value initialized once
const blobCacheControl = process.env.BLOB_CACHE_CONTROL || 'public, max-age=30';
const blobCacheControl = process.env.BLOB_CACHE_CONTROL || 'public, max-age=10';
const SIZE_LIMIT = 209715200;
export const getBlob = async (req, res) => {
const { id } = req.params;
if (!id) {
return res.status(400).send('Blob ID is required');
const { id = '' } = req.params;
// Request validation
try {
await isValidBlobId(id);
}
// Verify that the blob ID is a URL-safe base64 string
const base64Pattern = /^[a-zA-Z0-9-_]+$/;
if (!base64Pattern.test(id)) {
return res.status(400).send('Blob ID invalid');
catch (error) {
if (error instanceof Error) {
res.status(400).send(error.message);
}
else {
res.status(400).send('Unknown error');
}
}
// Fast sanity check (enough characters for u256).
// TODO Calculate more precisely, this should be 44!?
if (id.length < 42) {
return res.status(400).send('Blob ID too short');
const { prefix_1, prefix_2 } = getIdPrefixes(id);
if (!prefix_1 || !prefix_2) {
// Should never happen because id was validated.
return res.status(500).send('Internal Server Error (prefix)');
}
// Extract two short prefix from the blob ID.
// Used for file system partitioning
const prefix_1 = id.slice(0, 2);
const prefix_2 = id.slice(2, 4);
const homePath = process.env.HOME || '';
// First attempt at reading meta information
// from ~/cache/prefix1/prefix2/<id>.json
Expand Down
70 changes: 70 additions & 0 deletions backend/cache/dist/controllers/metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as fs from 'fs';
import path from 'path';
import { getIdPrefixes } from '../utils/strings.js';
import { isValidBlobId } from '../utils/validation.js';
// Test Blob Info
//
// MIME type: image/png
//
// Blob ID: 0nzvRVLeF0I5kbWO3s_VDa-ixYZ_nhkp4J2EubJUtjo
// Unencoded size: 165 KiB
// Encoded size (including metadata): 62.0 MiB
// Sui object ID: 0x0ebad3b13ee9bc64f8d6370e71d3408a43febaa017a309d2367117afe144ae8c
// Cache-Control value initialized once
const metricsCacheControl = process.env.METRICS_CACHE_CONTROL || 'public, max-age=10';
export const getMetrics = async (req, res) => {
const { id = '' } = req.params;
// Request validation
try {
await isValidBlobId(id);
}
catch (error) {
if (error instanceof Error) {
res.status(400).send(error.message);
}
else {
res.status(400).send('Unknown error');
}
}
const { prefix_1, prefix_2 } = getIdPrefixes(id);
if (!prefix_1 || !prefix_2) {
// Should never happen because id was validated.
return res.status(500).send('Internal Server Error (prefix)');
}
// Attempt reading metrics information
// from ~/cache/prefix1/prefix2/<id>.metrics
//
// If it is not created yet, then there is no metrics.
// Return empty metrics JSON:
// {
// "status": "no_metrics",
// "message": "No metrics accumulated yet for this item. Metrics updated every ~24 hours.",
// }
//
// If the .metrics file exists, return it as JSON.
const homePath = process.env.HOME || '';
const jsonPath = path.resolve(homePath, 'cache', prefix_1, prefix_2, `${id}.metrics`);
const options = {
headers: {
'Cache-Control': metricsCacheControl,
'Content-Type': 'application/json',
},
};
try {
await fs.promises.access(jsonPath);
// Stream the .metrics as response.
res.sendFile(jsonPath, options, err => {
if (err) {
res.status(500).send('Error streaming metrics');
}
});
}
catch (err) {
// File does not exist, return "no_metrics" JSON response
res.json({
blobId: id,
status: 'no_metrics',
message: 'No metrics accumulated yet for this blob. Metrics updated every ~24 hours.',
});
}
};
8 changes: 0 additions & 8 deletions backend/cache/dist/router.js

This file was deleted.

8 changes: 8 additions & 0 deletions backend/cache/dist/routes/blobRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import express from 'express';
import { getBlob } from '../controllers/blob.js';
export const blobRoutes = express.Router();
blobRoutes.get('/', (req, res) => {
res.set('Cache-Control', 'public, max-age=86400');
res.send('Blob ID missing.<br><br> Useage: <b>https://cdn.suiftly.io/blob/<i>&lt;your_blob_id&gt;</i></b>');
});
blobRoutes.get('/:id', getBlob);
8 changes: 8 additions & 0 deletions backend/cache/dist/routes/metricsRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import express from 'express';
import { getMetrics } from '../controllers/metrics.js';
export const metricsRoutes = express.Router();
metricsRoutes.get('/', (req, res) => {
res.set('Cache-Control', 'public, max-age=86400');
res.send('Blob ID missing.<br><br> Useage: <b>https://cdn.suiftly.io/metrics/<i>&lt;your_blob_id&gt;</i></b>');
});
metricsRoutes.get('/:id', getMetrics);
6 changes: 4 additions & 2 deletions backend/cache/dist/server.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import 'dotenv/config';
import cors from 'cors';
import express from 'express';
import { router } from './router.js';
import { blobRoutes } from './routes/blobRoutes.js';
import { metricsRoutes } from './routes/metricsRoutes.js';
const port = process.env.PORT || 3000;
const app = express();
app.use(cors());
Expand All @@ -16,7 +17,8 @@ app.get('/', (req, res) => {
res.set('Cache-Control', 'public, max-age=86400'); // Cache 1 day
res.send('CDN is responding.<br><br> Access your own blob with <b>https://cdn.suiftly.io/blob/<i>&ltyour_blob_id&gt</i></b>');
});
app.use('/blob', router);
app.use('/blob', blobRoutes);
app.use('/metrics', metricsRoutes);
app.listen(port, () => {
console.log(`App listening on port: ${port}`);
});
11 changes: 11 additions & 0 deletions backend/cache/dist/utils/strings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Simple string utilities
export function getIdPrefixes(id) {
// Extract two short prefix from the blob ID.
// Used for file system partitioning
if (!id || id.length < 4) {
return { prefix_1: '', prefix_2: '' };
}
const prefix_1 = id.slice(0, 2);
const prefix_2 = id.slice(2, 4);
return { prefix_1, prefix_2 };
}
18 changes: 18 additions & 0 deletions backend/cache/dist/utils/validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function isValidBlobId(id) {
return new Promise((resolve, reject) => {
if (typeof id !== 'string' || id.trim().length === 0) {
return reject(new Error('Blob ID is required'));
}
// Verify that the blob ID is a URL-safe base64 string
const base64Pattern = /^[a-zA-Z0-9-_]+$/;
if (!base64Pattern.test(id)) {
reject(new Error('Blob ID invalid'));
}
// Fast sanity check (enough characters for u256).
// TODO Calculate more precisely, this should be 44!?
if (id.length < 42) {
reject(new Error('Blob ID too short'));
}
resolve();
});
}
183 changes: 0 additions & 183 deletions backend/cache/src/controller.ts

This file was deleted.

Loading

0 comments on commit 329e018

Please sign in to comment.