Download apidoc/fileworker.js from im1111/xpaintdev: direct link, hf CLI and curl.
- Browser
- Download file 7.2 kB
-
https://huggingface.co/spaces/im1111/xpaintdev/resolve/main/apidoc/fileworker.js
- Command line
-
hf download hf://spaces/im1111/xpaintdev/apidoc/fileworker.js
-
curl -L -o fileworker.js https://huggingface.co/spaces/im1111/xpaintdev/resolve/main/apidoc/fileworker.js
7.2 kB
| // Global rate limiting map and constants | |
| const requestCounter = new Map(); | |
| const RATE_LIMIT = 20; // requests per minute | |
| const WINDOW_SIZE = 60 * 1000; // 1 minute in milliseconds | |
| const MAX_FILE_SIZE = 50 * 1024 * 1024; // 20MB in bytes | |
| const MAX_STORAGE = 100 * 1024 * 1024 * 1024; // 100GB in bytes | |
| async function getStorageUsage(bucket) { | |
| let totalSize = 0; | |
| let cursor; | |
| do { | |
| const listing = await bucket.list({ | |
| cursor, | |
| include: ['customMetadata', 'httpMetadata'], | |
| }); | |
| // Sum up sizes of all objects in current page | |
| for (const object of listing.objects) { | |
| totalSize += object.size; | |
| } | |
| cursor = listing.cursor; | |
| } while (cursor); // Continue until no more pages | |
| return totalSize; | |
| } | |
| function objectNotFound(objectName) { | |
| return new Response(`<html><body>R2 object "<b>${objectName}</b>" not found</body></html>`, { | |
| status: 404, | |
| headers: { | |
| 'content-type': 'text/html; charset=UTF-8', | |
| 'Access-Control-Allow-Origin': '*', | |
| 'Access-Control-Allow-Methods': 'GET, HEAD, PUT, POST, OPTIONS', | |
| 'Access-Control-Allow-Headers': '*' | |
| } | |
| }); | |
| } | |
| function corsHeaders(headers = new Headers()) { | |
| headers.set('Access-Control-Allow-Origin', '*'); | |
| headers.set('Access-Control-Allow-Methods', 'GET, HEAD, PUT, POST, OPTIONS'); | |
| headers.set('Access-Control-Allow-Headers', '*'); | |
| return headers; | |
| } | |
| function checkRateLimit(ip) { | |
| const now = Date.now(); | |
| const userRequests = requestCounter.get(ip) || []; | |
| // Remove requests older than the window size | |
| const validRequests = userRequests.filter(timestamp => now - timestamp < WINDOW_SIZE); | |
| if (validRequests.length >= RATE_LIMIT) { | |
| return false; | |
| } | |
| // Add current request timestamp | |
| validRequests.push(now); | |
| requestCounter.set(ip, validRequests); | |
| // Cleanup old entries periodically | |
| if (Math.random() < 0.1) { | |
| for (const [key, timestamps] of requestCounter.entries()) { | |
| const validTimestamps = timestamps.filter(ts => now - ts < WINDOW_SIZE); | |
| if (validTimestamps.length === 0) { | |
| requestCounter.delete(key); | |
| } else { | |
| requestCounter.set(key, validTimestamps); | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| export default { | |
| async fetch(request, env) { | |
| // Get client IP | |
| const clientIP = request.headers.get('cf-connecting-ip') || 'unknown'; | |
| // Check rate limit | |
| if (!checkRateLimit(clientIP)) { | |
| return new Response('Rate limit exceeded. Please try again later.', { | |
| status: 429, | |
| headers: corsHeaders(new Headers({ | |
| 'Retry-After': '60' | |
| })) | |
| }); | |
| } | |
| const url = new URL(request.url); | |
| const objectName = url.pathname.slice(1); | |
| console.log(`${request.method} object ${objectName}: ${request.url}`); | |
| // Get current storage usage | |
| const currentUsage = await getStorageUsage(env.MY_BUCKET); | |
| console.log(`Current R2 storage usage: ${(currentUsage / (1024 * 1024 * 1024)).toFixed(2)} GB`); | |
| // Handle CORS preflight | |
| if (request.method === 'OPTIONS') { | |
| return new Response(null, { | |
| headers: corsHeaders() | |
| }); | |
| } | |
| if (request.method === 'GET' || request.method === 'HEAD') { | |
| if (objectName === '') { | |
| if (request.method === 'HEAD') { | |
| return new Response(undefined, { | |
| status: 400, | |
| headers: corsHeaders() | |
| }); | |
| } | |
| const options = { | |
| prefix: url.searchParams.get('prefix') ?? undefined, | |
| delimiter: url.searchParams.get('delimiter') ?? undefined, | |
| cursor: url.searchParams.get('cursor') ?? undefined, | |
| include: ['customMetadata', 'httpMetadata'], | |
| }; | |
| console.log(JSON.stringify(options)); | |
| const listing = await env.MY_BUCKET.list(options); | |
| return new Response('specify your path,current total storage used:' +currentUsage/1000/1000/1000+' GB', { | |
| headers: corsHeaders(new Headers({ | |
| 'content-type': 'application/json; charset=UTF-8' | |
| })) | |
| }); | |
| } | |
| if (request.method === 'GET') { | |
| const object = await env.MY_BUCKET.get(objectName, { | |
| range: request.headers, | |
| onlyIf: request.headers, | |
| }); | |
| if (object === null) { | |
| return objectNotFound(objectName); | |
| } | |
| const headers = new Headers(); | |
| object.writeHttpMetadata(headers); | |
| headers.set('etag', object.httpEtag); | |
| if (object.range) { | |
| headers.set("content-range", `bytes ${object.range.offset}-${object.range.end ?? object.size - 1}/${object.size}`); | |
| } | |
| const status = object.body ? (request.headers.get("range") !== null ? 206 : 200) : 304; | |
| return new Response(object.body, { | |
| headers: corsHeaders(headers), | |
| status | |
| }); | |
| } | |
| const object = await env.MY_BUCKET.head(objectName); | |
| if (object === null) { | |
| return objectNotFound(objectName); | |
| } | |
| const headers = new Headers(); | |
| object.writeHttpMetadata(headers); | |
| headers.set('etag', object.httpEtag); | |
| return new Response(null, { | |
| headers: corsHeaders(headers) | |
| }); | |
| } | |
| if (request.method === 'PUT' || request.method === 'POST') { | |
| // Check if storage usage exceeds limit | |
| if (currentUsage >= MAX_STORAGE) { | |
| return new Response('Storage limit exceeded (100GB). Delete some files before uploading.', { | |
| status: 507, // Insufficient Storage | |
| headers: corsHeaders() | |
| }); | |
| } | |
| // Check content length | |
| const contentLength = parseInt(request.headers.get('content-length') || '0'); | |
| if (contentLength > MAX_FILE_SIZE) { | |
| return new Response('File too large. Maximum size is 20MB.', { | |
| status: 413, | |
| headers: corsHeaders() | |
| }); | |
| } | |
| // Handle chunked uploads | |
| if (!contentLength) { | |
| const chunks = []; | |
| let totalSize = 0; | |
| for await (const chunk of request.body) { | |
| totalSize += chunk.length; | |
| if (totalSize > MAX_FILE_SIZE) { | |
| return new Response('File too large. Maximum size is 20MB.', { | |
| status: 413, | |
| headers: corsHeaders() | |
| }); | |
| } | |
| chunks.push(chunk); | |
| } | |
| request.body = new Blob(chunks); | |
| } | |
| // Check if this upload would exceed storage limit | |
| if (currentUsage + contentLength >= MAX_STORAGE) { | |
| return new Response('This upload would exceed the 100GB storage limit.', { | |
| status: 507, | |
| headers: corsHeaders() | |
| }); | |
| } | |
| const object = await env.MY_BUCKET.put(objectName, request.body, { | |
| httpMetadata: request.headers, | |
| }); | |
| return new Response(objectName, { | |
| headers: corsHeaders(new Headers({ | |
| 'etag': object.httpEtag, | |
| 'X-Storage-Usage': `${(currentUsage / (1024 * 1024 * 1024)).toFixed(2)}GB` | |
| })) | |
| }); | |
| } | |
| if (request.method === 'DELETE') { | |
| return new Response('delete unsupported', { | |
| headers: corsHeaders() | |
| }); | |
| } | |
| return new Response(`Unsupported method`, { | |
| status: 400, | |
| headers: corsHeaders() | |
| }); | |
| } | |
| } |