Cache presigned URL for better caching

Signed-off-by: Joe Ma <rikkaneko23@gmail.com>
This commit is contained in:
Joe Ma 2024-02-05 01:17:47 +08:00
parent c5569ea30f
commit a88c7321cf
No known key found for this signature in database
GPG key ID: 7A0ECF5F5EDC587F
4 changed files with 33 additions and 10 deletions

View file

@ -1,3 +1,4 @@
export const SERVICE_URL = 'pb.nekoid.cc';
export const PASTE_WEB_URL = 'https://raw.githubusercontent.com/rikkaneko/paste/main/frontend';
export const UUID_LENGTH = 4;
export const CORS_DOMAIN = 'nekoid.cc';

View file

@ -22,7 +22,7 @@ import { Router, error } from 'itty-router';
import { ERequest, Env, PasteIndexEntry, PASTE_TYPES } from './types';
import { serve_static } from './proxy';
import { check_password_rules, get_paste_info, get_basic_auth, gen_id } from './utils';
import { UUID_LENGTH, PASTE_WEB_URL, SERVICE_URL } from './constant';
import { UUID_LENGTH, PASTE_WEB_URL, SERVICE_URL, CORS_DOMAIN } from './constant';
import { get_presign_url, router as large_upload } from './v2/large_upload';
const router = Router<ERequest, [Env, ExecutionContext]>();
@ -44,7 +44,7 @@ router.options('*', (request) => {
if (!request.origin) return new Response(null);
const url = new URL(request.origin);
// Allow all subdomain of nekoid.cc
if (url.hostname.endsWith('nekoid.cc')) {
if (url.hostname.endsWith(CORS_DOMAIN)) {
return new Response(null, {
status: 204,
headers: {
@ -217,6 +217,7 @@ router.post('/', async (request, env, ctx) => {
const s3 = new AwsClient({
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
service: 's3', // required
});
const res = await s3.fetch(`${env.ENDPOINT}/${uuid}`, {
@ -344,10 +345,18 @@ router.get('/:uuid/:option?', async (request, env, ctx) => {
}
const signed_url = await get_presign_url(uuid, descriptor, env);
ctx.waitUntil(
env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor), {
expiration: descriptor.expiration! / 1000,
})
);
return new Response(null, {
status: 301,
headers: {
location: signed_url,
'cache-control': 'no-store',
},
});
}
@ -372,6 +381,7 @@ router.get('/:uuid/:option?', async (request, env, ctx) => {
const s3 = new AwsClient({
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
service: 's3', // required
});
// Fetch form origin if not hit cache
let origin = await s3.fetch(`${env.ENDPOINT}/${uuid}`, {
@ -559,7 +569,7 @@ export default {
if (!req.origin) return res;
const url = new URL(req.origin);
// Allow all subdomain of nekoid.cc
if (url.hostname.endsWith('nekoid.cc')) {
if (url.hostname.endsWith(CORS_DOMAIN)) {
res.headers.set('Access-Control-Allow-Origin', url.origin);
res.headers.set('Vary', 'Origin');
}

2
src/types.d.ts vendored
View file

@ -22,7 +22,7 @@ export interface PasteIndexEntry {
upload_completed?: boolean;
sha256_hash?: string;
cached_presigned_url?: string;
cached_presigned_url_expiration?: string;
cached_presigned_url_expiration?: number;
}
export interface Env {

View file

@ -9,8 +9,17 @@ import { UUID_LENGTH } from '../constant';
export const router = Router<ERequest, [Env, ExecutionContext]>({ base: '/v2/large_upload' });
export async function get_presign_url(uuid: string, descriptor: PasteIndexEntry, env: Env) {
// Use cached presigned url if expiration is more than 10 mins
if (descriptor.cached_presigned_url) {
const expiration = new Date(descriptor.cached_presigned_url_expiration ?? 0);
const time_to_renew = new Date(Date.now() + 600 * 1000); // 10 mins after
if (expiration >= time_to_renew) {
return descriptor.cached_presigned_url;
}
}
const endpoint_url = new URL(`${env.LARGE_DOWNLOAD_ENDPOINT}/${uuid}`);
endpoint_url.searchParams.set('X-Amz-Expires', '3600');
endpoint_url.searchParams.set('X-Amz-Expires', '14400'); // Valid for 4 hours
endpoint_url.searchParams.set(
'response-content-disposition',
`inline; filename*=UTF-8''${encodeURIComponent(descriptor.title ?? uuid)}`
@ -21,7 +30,7 @@ export async function get_presign_url(uuid: string, descriptor: PasteIndexEntry,
const s3 = new AwsClient({
accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!,
secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!,
service: 's3',
service: 's3', // required
});
const signed = await s3.sign(endpoint_url, {
@ -32,6 +41,9 @@ export async function get_presign_url(uuid: string, descriptor: PasteIndexEntry,
},
});
descriptor.cached_presigned_url = signed.url;
descriptor.cached_presigned_url_expiration = new Date(Date.now() + 14400 * 1000).getTime();
return signed.url;
}
@ -111,13 +123,13 @@ router.post('/create', async (request, env, ctx) => {
const s3 = new AwsClient({
accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!,
secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!,
service: 's3',
service: 's3', // required
});
const current = Date.now();
const expiration = new Date(current + 14400 * 1000).getTime();
const endpoint_url = new URL(`${env.LARGE_ENDPOINT}/${uuid}`);
endpoint_url.searchParams.set('X-Amz-Expires', '14400');
endpoint_url.searchParams.set('X-Amz-Expires', '900'); // Valid for 15 mins
const required_headers = {
'Content-Length': file_size.toString(),
'X-Amz-Content-Sha256': file_hash,
@ -147,7 +159,7 @@ router.post('/create', async (request, env, ctx) => {
title: file_title || undefined,
mime_type: file_mime || undefined,
last_modified: current,
expiration: new Date(Date.now() + 3600 * 1000).getTime(),
expiration: new Date(Date.now() + 900 * 1000).getTime(),
password: password ? sha256(password).slice(0, 16) : undefined,
read_count_remain: read_limit ?? undefined,
type: 'large_paste',
@ -192,7 +204,7 @@ router.post('/complete/:uuid', async (request, env, ctx) => {
const s3 = new AwsClient({
accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!,
secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!,
service: 's3',
service: 's3', // required
});
try {