Use request forwarding for large paste

Signed-off-by: Joe Ma <rikkaneko23@gmail.com>
This commit is contained in:
Joe Ma 2024-02-14 03:17:04 +08:00
parent 63578bcf9d
commit 1fb3076115
No known key found for this signature in database
GPG key ID: 7A0ECF5F5EDC587F
4 changed files with 41 additions and 25 deletions

View file

@ -19,7 +19,7 @@
import { AwsClient } from 'aws4fetch'; import { AwsClient } from 'aws4fetch';
import { sha256 } from 'js-sha256'; import { sha256 } from 'js-sha256';
import { Router, error } from 'itty-router'; import { Router, error } from 'itty-router';
import { ERequest, Env, PasteIndexEntry, PASTE_TYPES } from './types'; import { ERequest, Env, PasteIndexEntry } 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, CORS_DOMAIN } from './constant'; import { UUID_LENGTH, PASTE_WEB_URL, SERVICE_URL, CORS_DOMAIN } from './constant';
@ -334,6 +334,7 @@ router.get('/:uuid/:option?', async (request, env, ctx) => {
// New added in 2.0 // New added in 2.0
// Handle large_paste // Handle large_paste
// Use presigned url generation only if the file size larger than 200MB, use request forwarding instead
if (descriptor.type === 'large_paste') { if (descriptor.type === 'large_paste') {
if (!descriptor.upload_completed) { if (!descriptor.upload_completed) {
return new Response('This paste is not yet finalized.\n', { return new Response('This paste is not yet finalized.\n', {
@ -341,21 +342,23 @@ router.get('/:uuid/:option?', async (request, env, ctx) => {
}); });
} }
const signed_url = await get_presign_url(uuid, descriptor, env); if (descriptor.size >= 209715200) {
const signed_url = await get_presign_url(uuid, descriptor, env);
ctx.waitUntil( ctx.waitUntil(
env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor), { env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor), {
expiration: descriptor.expiration! / 1000, 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', 'cache-control': 'no-store',
}, },
}); });
}
} }
// Enable CF cache for authorized request // Enable CF cache for authorized request
@ -375,13 +378,20 @@ router.get('/:uuid/:option?', async (request, env, ctx) => {
let res = await cache.match(req_key); let res = await cache.match(req_key);
if (res === undefined) { if (res === undefined) {
// Use althernative endpoint and credentials for large_type
const endpoint = descriptor.type === 'large_paste' ? env.LARGE_DOWNLOAD_ENDPOINT : env.ENDPOINT;
const access_key_id = descriptor.type === 'large_paste' ? env.LARGE_AWS_ACCESS_KEY_ID! : env.AWS_ACCESS_KEY_ID;
const secret_access_key =
descriptor.type === 'large_paste' ? env.LARGE_AWS_SECRET_ACCESS_KEY! : env.AWS_SECRET_ACCESS_KEY;
const s3 = new AwsClient({ const s3 = new AwsClient({
accessKeyId: env.AWS_ACCESS_KEY_ID, accessKeyId: access_key_id,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY, secretAccessKey: secret_access_key,
service: 's3', // required 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(`${endpoint}/${uuid}`, {
method: 'GET', method: 'GET',
headers: match_etag headers: match_etag
? { ? {
@ -527,12 +537,19 @@ router.delete('/:uuid', async (request, env, ctx) => {
}); });
} }
} }
// Use althernative endpoint and credentials for large_type
const endpoint = descriptor.type === 'large_paste' ? env.LARGE_DOWNLOAD_ENDPOINT : env.ENDPOINT; const endpoint = descriptor.type === 'large_paste' ? env.LARGE_DOWNLOAD_ENDPOINT : env.ENDPOINT;
const access_key_id = descriptor.type === 'large_paste' ? env.LARGE_AWS_ACCESS_KEY_ID! : env.AWS_ACCESS_KEY_ID;
const secret_access_key =
descriptor.type === 'large_paste' ? env.LARGE_AWS_SECRET_ACCESS_KEY! : env.AWS_SECRET_ACCESS_KEY;
const s3 = new AwsClient({ const s3 = new AwsClient({
accessKeyId: descriptor.type === 'large_paste' ? env.LARGE_AWS_ACCESS_KEY_ID! : env.AWS_ACCESS_KEY_ID, accessKeyId: access_key_id,
secretAccessKey: descriptor.type === 'large_paste' ? env.LARGE_AWS_SECRET_ACCESS_KEY! : env.AWS_SECRET_ACCESS_KEY, secretAccessKey: secret_access_key,
service: 's3', // required service: 's3', // required
}); });
let res = await s3.fetch(`${endpoint}/${uuid}`, { let res = await s3.fetch(`${endpoint}/${uuid}`, {
method: 'DELETE', method: 'DELETE',
}); });

3
src/types.d.ts vendored
View file

@ -6,7 +6,7 @@ export type ERequest = {
// match_etag?: string; // match_etag?: string;
} & IRequest; } & IRequest;
export type PASTE_TYPES = 'paste' | 'link' | 'large_paste'; export type PASTE_TYPES = 'paste' | 'text' | 'link' | 'large_paste';
export interface PasteIndexEntry { export interface PasteIndexEntry {
title?: string; title?: string;
@ -20,7 +20,6 @@ export interface PasteIndexEntry {
type: PASTE_TYPES; type: PASTE_TYPES;
// Only apply when large_paste // Only apply when large_paste
upload_completed?: boolean; upload_completed?: boolean;
sha256_hash?: string;
cached_presigned_url?: string; cached_presigned_url?: string;
cached_presigned_url_expiration?: number; cached_presigned_url_expiration?: number;
} }

View file

@ -23,7 +23,7 @@ import { PasteIndexEntry, Env } from './types';
export const gen_id = customAlphabet('1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', UUID_LENGTH); export const gen_id = customAlphabet('1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', UUID_LENGTH);
export function get_paste_info_obj(uuid: string, descriptor: PasteIndexEntry, env: Env) { export function get_paste_info_obj(uuid: string, descriptor: PasteIndexEntry) {
const created = new Date(descriptor.last_modified); const created = new Date(descriptor.last_modified);
const expired = new Date(descriptor.expiration ?? descriptor.last_modified + 2419200000); const expired = new Date(descriptor.expiration ?? descriptor.last_modified + 2419200000);
const link = `https://${SERVICE_URL}/${uuid}`; const link = `https://${SERVICE_URL}/${uuid}`;
@ -53,7 +53,7 @@ export async function get_paste_info(
need_qr: boolean = false, need_qr: boolean = false,
reply_json = false reply_json = false
): Promise<Response> { ): Promise<Response> {
const paste_info = get_paste_info_obj(uuid, descriptor, env); const paste_info = get_paste_info_obj(uuid, descriptor);
// Reply with JSON // Reply with JSON
if (reply_json) { if (reply_json) {
@ -167,6 +167,7 @@ export function get_basic_auth(headers: Headers): [string, string] | null {
return null; return null;
} }
} }
function to_human_readable_size(bytes: number): string { function to_human_readable_size(bytes: number): string {
let size = bytes + ' bytes'; let size = bytes + ' bytes';
const units = ['KiB', 'MiB', 'GiB', 'TiB']; const units = ['KiB', 'MiB', 'GiB', 'TiB'];

View file

@ -165,7 +165,6 @@ router.post('/create', async (request, env, ctx) => {
type: 'large_paste', type: 'large_paste',
size: file_size, size: file_size,
upload_completed: false, upload_completed: false,
sha256_hash: file_hash,
}; };
ctx.waitUntil( ctx.waitUntil(
@ -250,7 +249,7 @@ router.post('/complete/:uuid', async (request, env, ctx) => {
const paste_info = { const paste_info = {
upload_completed: true, upload_completed: true,
expired: new Date(expriation).toISOString(), expired: new Date(expriation).toISOString(),
paste_info: get_paste_info_obj(uuid, descriptor, env), paste_info: get_paste_info_obj(uuid, descriptor),
}; };
return new Response(JSON.stringify(paste_info)); return new Response(JSON.stringify(paste_info));