feat: 304 not modified
This commit is contained in:
parent
375d82c6f8
commit
702cd041a0
|
@ -12,6 +12,7 @@
|
|||
"publish": "wrangler publish"
|
||||
},
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "~1.0.2",
|
||||
"itty-router": "~2.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -2,6 +2,7 @@ lockfileVersion: 5.3
|
|||
|
||||
specifiers:
|
||||
'@cloudflare/wrangler': ~1.19.8
|
||||
base64-arraybuffer: ~1.0.2
|
||||
clean-webpack-plugin: ~4.0.0
|
||||
eslint: ~8.10.0
|
||||
itty-router: ~2.5.2
|
||||
|
@ -10,6 +11,7 @@ specifiers:
|
|||
webpack-cli: ~4.9.2
|
||||
|
||||
dependencies:
|
||||
base64-arraybuffer: 1.0.2
|
||||
itty-router: 2.5.2
|
||||
|
||||
devDependencies:
|
||||
|
@ -332,6 +334,11 @@ packages:
|
|||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: true
|
||||
|
||||
/base64-arraybuffer/1.0.2:
|
||||
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
dev: false
|
||||
|
||||
/brace-expansion/1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
dependencies:
|
||||
|
|
|
@ -20,7 +20,8 @@ router.all('*', async () => {
|
|||
});
|
||||
|
||||
addEventListener('fetch', (event) => {
|
||||
const req = event.request;
|
||||
event.respondWith(
|
||||
router.handle(event.request, event).catch((e) => genErrorResponse(e))
|
||||
router.handle(req, event).catch((e) => genErrorResponse(req, e))
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,3 +1,73 @@
|
|||
import { getETag } from './utils';
|
||||
|
||||
/**
|
||||
* @param {Request} req
|
||||
* @param {BodyInit|null|undefined} body
|
||||
* @param {ResponseInit|undefined} init
|
||||
*/
|
||||
export async function genResponse(req, body, init) {
|
||||
// 304
|
||||
const buffer = await new Response(body, init).arrayBuffer();
|
||||
const etag = await getETag(buffer);
|
||||
if (!etag) {
|
||||
return res;
|
||||
}
|
||||
if (req.headers.get('If-None-Match') === etag) {
|
||||
return new Response(null, {
|
||||
status: 304,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Cache-Control': init.headers['Cache-Control'] || 'no-cache',
|
||||
ETag: etag,
|
||||
},
|
||||
});
|
||||
}
|
||||
// 200
|
||||
const res = new Response(body, init);
|
||||
res.headers.set('Access-Control-Allow-Origin', '*');
|
||||
if (!init.headers['Cache-Control']) {
|
||||
res.headers.set('Cache-Control', 'no-cache');
|
||||
}
|
||||
res.headers.set('ETag', etag);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Request} req
|
||||
* @param {FetchEvent} event
|
||||
* @param {string} proxy
|
||||
*/
|
||||
export async function genProxyResponse(req, event, proxy) {
|
||||
// check cache
|
||||
const cacheKey = new Request(new URL(req.url).toString(), req);
|
||||
const cache = caches.default;
|
||||
let usingCache = true;
|
||||
let resCache = await cache.match(cacheKey);
|
||||
if (!resCache) {
|
||||
usingCache = false;
|
||||
resCache = await fetch(proxy);
|
||||
event.waitUntil(cache.put(cacheKey, resCache.clone()));
|
||||
}
|
||||
// 304
|
||||
const etag = resCache.headers.get('ETag');
|
||||
if (etag && req.headers.get('If-None-Match') === etag) {
|
||||
return new Response(null, {
|
||||
status: 304,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Cache-Control': resCache.headers.get('Cache-Control'),
|
||||
ETag: etag,
|
||||
'X-Proxy-Cache': 'HIT',
|
||||
},
|
||||
});
|
||||
}
|
||||
// normal response
|
||||
const res = new Response(resCache.body, resCache);
|
||||
res.headers.set('Access-Control-Allow-Origin', '*');
|
||||
res.headers.set('X-Proxy-Cache', usingCache ? 'HIT' : 'MISS');
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* custom api error
|
||||
*/
|
||||
|
@ -14,10 +84,12 @@ export class ResError extends Error {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Request} req
|
||||
* @param {any} e
|
||||
*/
|
||||
export function genErrorResponse(e) {
|
||||
const headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'text/plain',
|
||||
'Cache-Control': 'no-cache',
|
||||
};
|
||||
|
@ -28,15 +100,3 @@ export function genErrorResponse(e) {
|
|||
return new Response('Internal Server Error', { status: 500, headers });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
*/
|
||||
export function genHTMLResponse(html) {
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
'Cache-Control': 'max-age=0, private, must-revalidate',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,30 +1,28 @@
|
|||
/* global KV */
|
||||
import { genResponse } from '../response';
|
||||
import { validateID } from '../utils';
|
||||
|
||||
/**
|
||||
* @param {Request} req
|
||||
*/
|
||||
export async function get(req) {
|
||||
const id = validateID(req.params.id);
|
||||
|
||||
// get times from KV
|
||||
const count = Number.parseInt(await KV.get(id)) || 0;
|
||||
|
||||
return new Response(JSON.stringify({ count }), {
|
||||
return await genResponse(req, JSON.stringify({ count }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Request} req
|
||||
*/
|
||||
export async function del(req) {
|
||||
const id = validateID(req.params.id);
|
||||
await KV.delete(id);
|
||||
|
||||
return new Response(null, {
|
||||
return await genResponse(req, null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
import { genProxyResponse } from '../response';
|
||||
|
||||
/**
|
||||
* @param {Request} req
|
||||
* @param {FetchEvent} event
|
||||
*/
|
||||
export async function get(req, event) {
|
||||
const cacheKey = new Request(new URL(req.url).toString(), req);
|
||||
const cache = caches.default;
|
||||
|
||||
let usingCache = true;
|
||||
let resCache = await cache.match(cacheKey);
|
||||
if (!resCache) {
|
||||
usingCache = false;
|
||||
resCache = await fetch(
|
||||
'https://cdn.jsdelivr.net/gh/dsrkafuu/dsr-assets@3.0.0/favicon/favicon.ico'
|
||||
);
|
||||
event.waitUntil(cache.put(cacheKey, resCache.clone()));
|
||||
}
|
||||
|
||||
const res = new Response(resCache.body, resCache);
|
||||
res.headers.set('X-Custom-Cache', usingCache ? 'HIT' : 'MISS');
|
||||
return new Response(res.body, res);
|
||||
return await genProxyResponse(
|
||||
req,
|
||||
event,
|
||||
'https://cdn.jsdelivr.net/gh/dsrkafuu/dsr-assets@3.0.0/favicon/favicon.ico'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
/* global KV */
|
||||
import themes from '../../themes';
|
||||
import { genResponse } from '../response';
|
||||
import { validateID, minify } from '../utils';
|
||||
|
||||
/**
|
||||
* @param {number} count
|
||||
* @param {string} theme
|
||||
* @param {number|string} length
|
||||
* @returns
|
||||
*/
|
||||
function genImage(count, theme, length) {
|
||||
let nums;
|
||||
if (length === 'auto') {
|
||||
|
@ -29,6 +36,10 @@ function genImage(count, theme, length) {
|
|||
return minify(svg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Request} req
|
||||
* @param {FetchEvent} event
|
||||
*/
|
||||
export async function get(req, event) {
|
||||
const id = validateID(req.params.id);
|
||||
let { theme, length } = req.query;
|
||||
|
@ -48,10 +59,10 @@ export async function get(req, event) {
|
|||
// set time asynchronously (no await)
|
||||
event.waitUntil(KV.put(id, count.toString()));
|
||||
|
||||
return new Response(image, {
|
||||
return await genResponse(req, image, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'image/svg+xml; charset=utf-8',
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
export async function get() {
|
||||
return new Response(null, {
|
||||
import { genResponse } from '../response';
|
||||
|
||||
/**
|
||||
* @param {Request} req
|
||||
*/
|
||||
export async function get(req) {
|
||||
return await genResponse(req, null, {
|
||||
status: 301,
|
||||
headers: {
|
||||
Location: 'https://github.com/dsrkafuu/moe-counter#readme',
|
||||
|
|
33
src/utils.js
33
src/utils.js
|
@ -1,15 +1,42 @@
|
|||
import { encode } from 'base64-arraybuffer';
|
||||
import { ResError } from './response';
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
*/
|
||||
export function minify(str) {
|
||||
if (typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
return str.replace(/ *\n */g, '').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @returns
|
||||
*/
|
||||
export function validateID(id) {
|
||||
if (!/^[a-z][0-9a-z:!@#$%^&*_-]{0,255}$/i.test(id)) {
|
||||
throw new ResError(400, 'Invalid Counter ID');
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/jshttp/etag/blob/master/index.js#L39
|
||||
* @param {ArrayBuffer} buffer
|
||||
*/
|
||||
export async function getETag(buffer) {
|
||||
// fast empty etag
|
||||
if (buffer.byteLength === 0) {
|
||||
return 'W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
|
||||
}
|
||||
try {
|
||||
// body hash
|
||||
const digest = await crypto.subtle.digest('SHA-1', buffer);
|
||||
const base64 = encode(digest);
|
||||
const hash = base64.substring(0, 27); // remove trailing `=`s
|
||||
// body length
|
||||
const length = buffer.byteLength.toString(16);
|
||||
return `W/"${length}-${hash}"`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,5 +24,8 @@ module.exports = {
|
|||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
plugins: [new CleanWebpackPlugin()],
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue