mirror of
https://github.com/direct-img/direct-img.link.git
synced 2026-10-17 18:27:52 +00:00
Perf: Send SurrealDB a token instead of the password (~47ms faster per query)
SurrealDB re-checks the password against its slow hash on every request. The token from /signin is kept in the isolate's memory and in KV until 10 min before it expires, replaced if refused, and the password is still used if no token can be had. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -272,6 +272,7 @@ Other keys share the namespace. They use uppercase prefixes, which can never col
|
||||
| `FREE:<src or auto>:<i>:<query>` (e.g. `FREE:wikimedia:2:orange cat`) | Same as above | 90 days |
|
||||
| `AI:<src>:<i>:<query>` (e.g. `AI:auroraimg-6m:2:frog wizard`) | Same as above | 90 days |
|
||||
| `OPENVERSE_TOKEN` | Openverse access token | Token lifetime minus 10 min |
|
||||
| `SURREAL_TOKEN` | SurrealDB access token, sent instead of the password, which SurrealDB takes ~50ms to check on every request | Token lifetime minus 10 min |
|
||||
|
||||
An AI image served because a search found nothing is stored under the search's own key, like any other image. A search where that also fails, or a `free.` search with a `src` (which gets no AI image), stores `{"t":…,"err":true}` for 24h.
|
||||
|
||||
|
||||
+2
-10
@@ -3,6 +3,7 @@ import { bingImageSearchFallback } from "./_utils/bing.js";
|
||||
import { freeImageUrls, parseFreeSource, FREE_UA } from "./_utils/free.js";
|
||||
import { isFlickrPlaceholder } from "./_utils/flickr.js";
|
||||
import { aiImage, parseAiSource, DEFAULT_AI_SOURCE } from "./_utils/ai.js";
|
||||
import { surreal } from "./_utils/surreal.js";
|
||||
import * as LIMITS from "../assets/rate-limit.js";
|
||||
|
||||
const TTL_SECONDS = 90 * 24 * 60 * 60;
|
||||
@@ -80,16 +81,7 @@ export async function onRequest(context) {
|
||||
const limit = free ? LIMITS.FREE_DAILY_LIMIT : ai ? LIMITS.AI_DAILY_LIMIT : LIMITS.DAILY_LIMIT;
|
||||
let count = 1, wait = false, locked = false, list = null;
|
||||
|
||||
const db = env.SURREAL_URL && env.SURREAL_USER && env.SURREAL_PASS && (body => fetch(`${env.SURREAL_URL}/sql`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Authorization": `Basic ${btoa(`${env.SURREAL_USER}:${env.SURREAL_PASS}`)}`,
|
||||
"surreal-ns": "direct_img",
|
||||
"surreal-db": "rate_limit",
|
||||
},
|
||||
body
|
||||
}));
|
||||
const db = surreal(env);
|
||||
|
||||
if (db) {
|
||||
// Counts the search and claims this query's lock in the same round trip, so concurrent misses (on any isolate or
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Uppercase so it can't collide with query cache keys, which are always lowercased
|
||||
const TOKEN_KEY = "SURREAL_TOKEN";
|
||||
// SurrealDB re-checks a password against its deliberately slow hash on every request (~47ms), but a token in ~1ms. A
|
||||
// token lasts 60 min, so it's kept in this isolate's memory and in KV until 10 min before it expires.
|
||||
let cached = null;
|
||||
|
||||
// A token from memory, then KV, then a new /signin (fresh skips memory and KV). null if signing in fails.
|
||||
async function getToken(env, fresh) {
|
||||
if (!fresh && cached?.until > Date.now()) return cached.token;
|
||||
let token = !fresh && await env.DIRECT_IMG_CACHE.get(TOKEN_KEY), signedIn = !token;
|
||||
if (signedIn) {
|
||||
const res = await fetch(`${env.SURREAL_URL}/signin`, {
|
||||
method: "POST",
|
||||
headers: { "Accept": "application/json" },
|
||||
body: JSON.stringify({ user: env.SURREAL_USER, pass: env.SURREAL_PASS }),
|
||||
});
|
||||
token = res.ok && (await res.json()).token;
|
||||
if (!token) return null;
|
||||
}
|
||||
const ttl = Math.floor(JSON.parse(atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"))).exp - Date.now() / 1000) - 600;
|
||||
if (signedIn && ttl >= 60) await env.DIRECT_IMG_CACHE.put(TOKEN_KEY, token, { expirationTtl: ttl }).catch(() => {});
|
||||
cached = ttl > 0 ? { token, until: Date.now() + ttl * 1000 } : null;
|
||||
return token;
|
||||
}
|
||||
|
||||
// Runs SurrealQL in direct_img/rate_limit and resolves to the Response, or null if SurrealDB isn't configured. Uses the
|
||||
// password only if no token can be had, so it still works, just slower.
|
||||
export function surreal(env) {
|
||||
if (!env.SURREAL_URL || !env.SURREAL_USER || !env.SURREAL_PASS) return null;
|
||||
const send = (token, body) => fetch(`${env.SURREAL_URL}/sql`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Authorization": token ? `Bearer ${token}` : `Basic ${btoa(`${env.SURREAL_USER}:${env.SURREAL_PASS}`)}`,
|
||||
"surreal-ns": "direct_img",
|
||||
"surreal-db": "rate_limit",
|
||||
},
|
||||
body
|
||||
});
|
||||
return async body => {
|
||||
const token = await getToken(env).catch(() => null), res = await send(token, body);
|
||||
// A refused token (expired early or revoked) means the query never ran, so it's safe to retry with a new one
|
||||
return res.status === 401 && token ? send(await getToken(env, true).catch(() => null), body) : res;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user