mirror of
https://github.com/rikkaneko/paste.git
synced 2025-06-06 16:45:41 +00:00
Forward client HTPP headers to improve caching
Signed-off-by: Joe Ma <rikkaneko23@gmail.com>
This commit is contained in:
parent
601bd0b7dc
commit
e53deac322
3 changed files with 80 additions and 17 deletions
48
src/index.ts
48
src/index.ts
|
@ -20,7 +20,8 @@ import { AwsClient } from 'aws4fetch';
|
||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
import { sha256 } from 'js-sha256';
|
import { sha256 } from 'js-sha256';
|
||||||
import dedent from 'dedent-js';
|
import dedent from 'dedent-js';
|
||||||
import { PasteIndexEntry } from './types';
|
import { Env, PasteIndexEntry } from './types';
|
||||||
|
import { serve_static } from './proxy';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const SERVICE_URL = 'pb.nekoid.cc';
|
const SERVICE_URL = 'pb.nekoid.cc';
|
||||||
|
@ -28,14 +29,6 @@ const PASTE_WEB_URL_v1 = 'https://raw.githubusercontent.com/rikkaneko/paste/main
|
||||||
const PASTE_WEB_URL = 'https://raw.githubusercontent.com/rikkaneko/paste/main/static/v2';
|
const PASTE_WEB_URL = 'https://raw.githubusercontent.com/rikkaneko/paste/main/static/v2';
|
||||||
const UUID_LENGTH = 4;
|
const UUID_LENGTH = 4;
|
||||||
|
|
||||||
export interface Env {
|
|
||||||
PASTE_INDEX: KVNamespace;
|
|
||||||
QRCODE: ServiceWorkerGlobalScope;
|
|
||||||
AWS_ACCESS_KEY_ID: string;
|
|
||||||
AWS_SECRET_ACCESS_KEY: string;
|
|
||||||
ENDPOINT: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gen_id = customAlphabet('1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', UUID_LENGTH);
|
const gen_id = customAlphabet('1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', UUID_LENGTH);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -43,6 +36,7 @@ export default {
|
||||||
const { url, method, headers } = request;
|
const { url, method, headers } = request;
|
||||||
const { pathname, searchParams } = new URL(url);
|
const { pathname, searchParams } = new URL(url);
|
||||||
const path = pathname.replace(/\/+$/, '') || '/';
|
const path = pathname.replace(/\/+$/, '') || '/';
|
||||||
|
const match_etag = headers.get('If-None-Match') || undefined;
|
||||||
let cache = caches.default;
|
let cache = caches.default;
|
||||||
|
|
||||||
const agent = headers.get('user-agent') ?? '';
|
const agent = headers.get('user-agent') ?? '';
|
||||||
|
@ -67,18 +61,18 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path === '/v1' && method == 'GET') {
|
if (path === '/v1' && method == 'GET') {
|
||||||
return await proxy_uri(PASTE_WEB_URL_v1 + '/paste.html');
|
return await serve_static(PASTE_WEB_URL_v1 + '/paste.html', headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/\/(js|css)\/.*$/.test(path) && method == 'GET') {
|
if (/\/(js|css)\/.*$/.test(path) && method == 'GET') {
|
||||||
return await proxy_uri(PASTE_WEB_URL + path);
|
return await serve_static(PASTE_WEB_URL + path, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path === '/') {
|
if (path === '/') {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'GET': {
|
case 'GET': {
|
||||||
// Fetch the HTML for uploading text/file
|
// Fetch the HTML for uploading text/file
|
||||||
return await proxy_uri(PASTE_WEB_URL + '/paste.html');
|
return await serve_static(PASTE_WEB_URL + '/paste.html', headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new paste
|
// Create new paste
|
||||||
|
@ -343,14 +337,32 @@ export default {
|
||||||
|
|
||||||
// Enable CF cache for authorized request
|
// Enable CF cache for authorized request
|
||||||
// Match in existing cache
|
// Match in existing cache
|
||||||
let res = await cache.match(request.url);
|
let res = await cache.match(
|
||||||
|
new Request(`https://${SERVICE_URL}/${uuid}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: match_etag
|
||||||
|
? {
|
||||||
|
// ETag to cache file
|
||||||
|
'if-none-match': match_etag,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
if (res === undefined) {
|
if (res === undefined) {
|
||||||
// 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}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
headers: match_etag
|
||||||
|
? {
|
||||||
|
'if-none-match': match_etag,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
res = new Response(origin.body);
|
// Reserve ETag header
|
||||||
|
res = new Response(origin.body, { status: origin.status });
|
||||||
|
const etag = origin.headers.get('etag');
|
||||||
|
if (etag) res.headers.append('etag', etag);
|
||||||
|
|
||||||
if (res.status == 404) {
|
if (res.status == 404) {
|
||||||
// UUID exists in index but not found in remote object storage service, probably expired
|
// UUID exists in index but not found in remote object storage service, probably expired
|
||||||
|
@ -361,7 +373,7 @@ export default {
|
||||||
return new Response('Paste expired.\n', {
|
return new Response('Paste expired.\n', {
|
||||||
status: 410,
|
status: 410,
|
||||||
});
|
});
|
||||||
} else if (!res.ok) {
|
} else if (!res.ok && res.status !== 304) {
|
||||||
// Other error
|
// Other error
|
||||||
return new Response('Internal server error.\n', {
|
return new Response('Internal server error.\n', {
|
||||||
status: 500,
|
status: 500,
|
||||||
|
@ -410,13 +422,15 @@ export default {
|
||||||
|
|
||||||
// res.body cannot be read twice
|
// res.body cannot be read twice
|
||||||
// Do not block when writing to cache
|
// Do not block when writing to cache
|
||||||
ctx.waitUntil(cache.put(url, res.clone()));
|
if (res.ok) ctx.waitUntil(cache.put(url, res.clone()));
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache hit
|
// Cache hit
|
||||||
|
// Matched Etag, no body
|
||||||
|
if (res.status == 304) return res;
|
||||||
let { readable, writable } = new TransformStream();
|
let { readable, writable } = new TransformStream();
|
||||||
res.body!.pipeTo(writable);
|
res.body?.pipeTo(writable);
|
||||||
return new Response(readable, res);
|
return new Response(readable, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
41
src/proxy.ts
Normal file
41
src/proxy.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Proxy URI (Accept *.js, *.css, *.html, *.ico only)
|
||||||
|
// Use ETag and If-None-Match to cache file
|
||||||
|
export async function serve_static(path: string, req_headers?: Headers): Promise<Response> {
|
||||||
|
// Filter static file extension
|
||||||
|
let mime = 'text/plain; charset=UTF-8;';
|
||||||
|
if (path.endsWith('.js')) mime = 'application/javascript; charset=UTF-8;';
|
||||||
|
else if (path.endsWith('.css')) mime = 'text/css; charset=UTF-8;';
|
||||||
|
else if (path.endsWith('.html')) mime = 'text/html; charset=UTF-8;';
|
||||||
|
else if (path.endsWith('.ico')) mime = 'image/x-icon';
|
||||||
|
else
|
||||||
|
return new Response(null, {
|
||||||
|
headers: {
|
||||||
|
'cache-control': 'public, max-age=14400',
|
||||||
|
},
|
||||||
|
status: 404,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(path, {
|
||||||
|
headers: req_headers,
|
||||||
|
cf: {
|
||||||
|
cacheEverything: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Append ETag and Cache
|
||||||
|
const etag = res.headers.get('etag');
|
||||||
|
const nres = new Response(res.body, {
|
||||||
|
headers: {
|
||||||
|
'content-type': mime,
|
||||||
|
'cache-control': 'public, max-age=14400',
|
||||||
|
},
|
||||||
|
status: res.status,
|
||||||
|
});
|
||||||
|
if (etag) nres.headers.append('etag', etag);
|
||||||
|
return nres;
|
||||||
|
} catch (err) {
|
||||||
|
return new Response('Internal server error.\n', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
8
src/types.d.ts
vendored
8
src/types.d.ts
vendored
|
@ -8,3 +8,11 @@ export interface PasteIndexEntry {
|
||||||
read_count_remain?: number;
|
read_count_remain?: number;
|
||||||
type?: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Env {
|
||||||
|
PASTE_INDEX: KVNamespace;
|
||||||
|
QRCODE: ServiceWorkerGlobalScope;
|
||||||
|
AWS_ACCESS_KEY_ID: string;
|
||||||
|
AWS_SECRET_ACCESS_KEY: string;
|
||||||
|
ENDPOINT: string;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue