mirror of
https://github.com/rikkaneko/paste.git
synced 2025-06-06 16:45:41 +00:00
Cache presigned URL for better caching
Signed-off-by: Joe Ma <rikkaneko23@gmail.com>
This commit is contained in:
parent
c5569ea30f
commit
a88c7321cf
4 changed files with 33 additions and 10 deletions
|
@ -1,3 +1,4 @@
|
||||||
export const SERVICE_URL = 'pb.nekoid.cc';
|
export const SERVICE_URL = 'pb.nekoid.cc';
|
||||||
export const PASTE_WEB_URL = 'https://raw.githubusercontent.com/rikkaneko/paste/main/frontend';
|
export const PASTE_WEB_URL = 'https://raw.githubusercontent.com/rikkaneko/paste/main/frontend';
|
||||||
export const UUID_LENGTH = 4;
|
export const UUID_LENGTH = 4;
|
||||||
|
export const CORS_DOMAIN = 'nekoid.cc';
|
||||||
|
|
16
src/index.ts
16
src/index.ts
|
@ -22,7 +22,7 @@ import { Router, error } from 'itty-router';
|
||||||
import { ERequest, Env, PasteIndexEntry, PASTE_TYPES } from './types';
|
import { ERequest, Env, PasteIndexEntry, PASTE_TYPES } from './types';
|
||||||
import { serve_static } from './proxy';
|
import { serve_static } from './proxy';
|
||||||
import { check_password_rules, get_paste_info, get_basic_auth, gen_id } from './utils';
|
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';
|
import { get_presign_url, router as large_upload } from './v2/large_upload';
|
||||||
|
|
||||||
const router = Router<ERequest, [Env, ExecutionContext]>();
|
const router = Router<ERequest, [Env, ExecutionContext]>();
|
||||||
|
@ -44,7 +44,7 @@ router.options('*', (request) => {
|
||||||
if (!request.origin) return new Response(null);
|
if (!request.origin) return new Response(null);
|
||||||
const url = new URL(request.origin);
|
const url = new URL(request.origin);
|
||||||
// Allow all subdomain of nekoid.cc
|
// Allow all subdomain of nekoid.cc
|
||||||
if (url.hostname.endsWith('nekoid.cc')) {
|
if (url.hostname.endsWith(CORS_DOMAIN)) {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 204,
|
status: 204,
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -217,6 +217,7 @@ router.post('/', async (request, env, ctx) => {
|
||||||
const s3 = new AwsClient({
|
const s3 = new AwsClient({
|
||||||
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
||||||
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
|
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
|
||||||
|
service: 's3', // required
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await s3.fetch(`${env.ENDPOINT}/${uuid}`, {
|
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);
|
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, {
|
return new Response(null, {
|
||||||
status: 301,
|
status: 301,
|
||||||
headers: {
|
headers: {
|
||||||
location: signed_url,
|
location: signed_url,
|
||||||
|
'cache-control': 'no-store',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -372,6 +381,7 @@ router.get('/:uuid/:option?', async (request, env, ctx) => {
|
||||||
const s3 = new AwsClient({
|
const s3 = new AwsClient({
|
||||||
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
||||||
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
|
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
|
||||||
|
service: 's3', // required
|
||||||
});
|
});
|
||||||
// Fetch form origin if not hit cache
|
// Fetch form origin if not hit cache
|
||||||
let origin = await s3.fetch(`${env.ENDPOINT}/${uuid}`, {
|
let origin = await s3.fetch(`${env.ENDPOINT}/${uuid}`, {
|
||||||
|
@ -559,7 +569,7 @@ export default {
|
||||||
if (!req.origin) return res;
|
if (!req.origin) return res;
|
||||||
const url = new URL(req.origin);
|
const url = new URL(req.origin);
|
||||||
// Allow all subdomain of nekoid.cc
|
// 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('Access-Control-Allow-Origin', url.origin);
|
||||||
res.headers.set('Vary', 'Origin');
|
res.headers.set('Vary', 'Origin');
|
||||||
}
|
}
|
||||||
|
|
2
src/types.d.ts
vendored
2
src/types.d.ts
vendored
|
@ -22,7 +22,7 @@ export interface PasteIndexEntry {
|
||||||
upload_completed?: boolean;
|
upload_completed?: boolean;
|
||||||
sha256_hash?: string;
|
sha256_hash?: string;
|
||||||
cached_presigned_url?: string;
|
cached_presigned_url?: string;
|
||||||
cached_presigned_url_expiration?: string;
|
cached_presigned_url_expiration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
|
|
|
@ -9,8 +9,17 @@ import { UUID_LENGTH } from '../constant';
|
||||||
export const router = Router<ERequest, [Env, ExecutionContext]>({ base: '/v2/large_upload' });
|
export const router = Router<ERequest, [Env, ExecutionContext]>({ base: '/v2/large_upload' });
|
||||||
|
|
||||||
export async function get_presign_url(uuid: string, descriptor: PasteIndexEntry, env: Env) {
|
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}`);
|
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(
|
endpoint_url.searchParams.set(
|
||||||
'response-content-disposition',
|
'response-content-disposition',
|
||||||
`inline; filename*=UTF-8''${encodeURIComponent(descriptor.title ?? uuid)}`
|
`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({
|
const s3 = new AwsClient({
|
||||||
accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!,
|
accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!,
|
||||||
secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!,
|
secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!,
|
||||||
service: 's3',
|
service: 's3', // required
|
||||||
});
|
});
|
||||||
|
|
||||||
const signed = await s3.sign(endpoint_url, {
|
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;
|
return signed.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,13 +123,13 @@ router.post('/create', async (request, env, ctx) => {
|
||||||
const s3 = new AwsClient({
|
const s3 = new AwsClient({
|
||||||
accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!,
|
accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!,
|
||||||
secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!,
|
secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!,
|
||||||
service: 's3',
|
service: 's3', // required
|
||||||
});
|
});
|
||||||
|
|
||||||
const current = Date.now();
|
const current = Date.now();
|
||||||
const expiration = new Date(current + 14400 * 1000).getTime();
|
const expiration = new Date(current + 14400 * 1000).getTime();
|
||||||
const endpoint_url = new URL(`${env.LARGE_ENDPOINT}/${uuid}`);
|
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 = {
|
const required_headers = {
|
||||||
'Content-Length': file_size.toString(),
|
'Content-Length': file_size.toString(),
|
||||||
'X-Amz-Content-Sha256': file_hash,
|
'X-Amz-Content-Sha256': file_hash,
|
||||||
|
@ -147,7 +159,7 @@ router.post('/create', async (request, env, ctx) => {
|
||||||
title: file_title || undefined,
|
title: file_title || undefined,
|
||||||
mime_type: file_mime || undefined,
|
mime_type: file_mime || undefined,
|
||||||
last_modified: current,
|
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,
|
password: password ? sha256(password).slice(0, 16) : undefined,
|
||||||
read_count_remain: read_limit ?? undefined,
|
read_count_remain: read_limit ?? undefined,
|
||||||
type: 'large_paste',
|
type: 'large_paste',
|
||||||
|
@ -192,7 +204,7 @@ router.post('/complete/:uuid', async (request, env, ctx) => {
|
||||||
const s3 = new AwsClient({
|
const s3 = new AwsClient({
|
||||||
accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!,
|
accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!,
|
||||||
secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!,
|
secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!,
|
||||||
service: 's3',
|
service: 's3', // required
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue