Workers cloudflare đọc thông tin json được lưu ở r2
Chào các bạn hôm nay mình xin phép giới thiệu 1 đoạn code đọc ghi xóa dữ liệu json với r2 :
Lý do mình code code này với mục đích đọc các thông tin json được lưu từ r2 do r2 miễn phí 10gb mà không tận dụng thì hơi phí
Bước 1 : mình connect đến r2 với đoạn code như sau
..........
"assets": {
"binding": "ASSETS",
"directory": "./public"
},
"observability": {
"enabled": true
},
"r2_buckets": [
{
"binding": "R2",
"bucket_name": "luubaiviet"
}
],
.............
cấu hình quá đơn giản để liên kết với r2
Với phương châm của workers đơn giản :
import { ipMiddleware } from './middlewares';
import { fileController } from './controlers/FileController';
// Map route với controller tương ứng
const controllers: Record<string, any> = {
post: fileController,
};
export default {
async fetch(request: Request, env: any, ctx: any): Promise<Response> {
const url = new URL(request.url); // Lấy đối tượng URL
const parts: string[] = url.pathname.split("/").filter(Boolean); // Tách phần đường dẫn thành mảng
// VD: /post/123 => ["post", "123"]
if (parts.length < 2) {
return new Response("Invalid request", { status: 400 });
}
const entity = parts[0]; // VD: "user", "post", "product"
const itemId = parts[1]; // ID của user, post, product
// Kiểm tra controller có tồn tại không
const controller = controllers[entity];
if (!controller) {
return new Response("Not Found", { status: 404 });
}
// Áp dụng middleware chỉ cho đường dẫn /post/*
const middlewareResponse = await ipMiddleware(request, env);
if (middlewareResponse) return middlewareResponse;
// Xử lý theo HTTP method
switch (request.method) {
case "GET":
return controller.read(env, itemId);
case "DELETE":
return controller.delete(env, itemId);
case "POST":
if (!request.headers.get("Content-Type")?.includes("application/json")) {
return new Response("Content-Type must be application/json", { status: 400 });
}
try {
const requestData = await request.json();
return controller.save(env, itemId, requestData);
} catch {
return new Response("Invalid JSON body", { status: 400 });
}
default:
return new Response("Method Not Allowed", { status: 405 });
}
},
} satisfies ExportedHandler<Env>;
Trong file : \src\controlers\FileController.ts
import { CacheService } from '../services/CacheService';
interface Env {
R2: {
put: (key: string, value: string, options?: { httpMetadata?: { contentType: string } }) => Promise<void>;
get: (key: string) => Promise<{ text: () => Promise<string> } | null>;
delete: (key: string) => Promise<void>;
};
}
const allowedOrigins = [
"https://truyenvideo.com",
"http://127.0.0.1",
];
// Hàm lấy header CORS động
function getCorsHeaders(request?: Request) {
const origin = request?.headers.get("Origin") || "";
const corsOrigin = allowedOrigins.includes(origin) ? origin : "";
console.log("🌐 Origin:", origin);
return {
"Content-Type": "application/json",
// "Access-Control-Allow-Origin": corsOrigin,
// dùng như này vì Access-Control-Allow-Origin chỉ chấp nhận một giá trị string hoặc * (tất cả các origin), nhưng không hỗ trợ mảng.
// "Access-Control-Allow-Origin": allowedOrigins.includes(origin ?? "") ? origin : "",
// "Access-Control-Allow-Origin": "*.truyenvideo.com",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
}
export const fileController = {
// Ghi dữ liệu vào R2 (Lưu bài viết)
async save(env: Env, fileName: string, data: unknown, request?: Request): Promise<Response> {
const fileKey = `articles/${fileName}.json`;
const jsonData = JSON.stringify(data);
await env.R2.put(fileKey, jsonData, {
httpMetadata: { contentType: "application/json" },
});
return new Response("File saved successfully!", {
status: 200,
headers: getCorsHeaders(request),
});
},
// Đọc dữ liệu từ R2
async read(env: Env, fileName: string, request?: Request): Promise<Response> {
const cacheKey = `file_${fileName}`;
// 🔍 Kiểm tra cache trước
const cachedResponse = await CacheService.get(cacheKey);
if (cachedResponse) {
console.log("✅ Cache hit:", cacheKey);
return new Response(JSON.stringify(cachedResponse), {
status: 200,
headers: getCorsHeaders(request),
});
}
console.log("⚡ Fetching from R2...");
const fileKey = `articles/${fileName}.json`;
const object = await env.R2.get(fileKey);
if (!object) return new Response("File not found", { status: 404 });
// Đọc nội dung file từ R2
const content = await object.text();
try {
const parsedContent = JSON.parse(content);
// 💾 Lưu vào cache dưới dạng object JSON
await CacheService.set(cacheKey, parsedContent);
return new Response(content, {
status: 200,
headers: getCorsHeaders(request),
});
} catch (error) {
return new Response("Invalid JSON format", { status: 500 });
}
},
// Xóa cache file
async deletecache(env: Env, fileName: string, request?: Request): Promise<Response> {
const cacheKey = `file_${fileName}`;
await CacheService.delete(cacheKey);
return new Response("Cache deleted successfully!", {
status: 200,
headers: getCorsHeaders(request),
});
},
// Xóa file trong R2
async delete(env: Env, fileName: string, request?: Request): Promise<Response> {
const fileKey = `articles/${fileName}.json`;
await env.R2.delete(fileKey);
return new Response("File deleted successfully!", {
status: 200,
headers: getCorsHeaders(request),
});
}
};
Ở đây mình dùng cache ở 1 file : src\services\CacheService.ts
export const CacheService = {
async get<T = unknown>(cacheKey: string): Promise<T | null> {
const cache = caches.default;
const request = new Request(`https://cache.local/${cacheKey}`); // Fake URL hợp lệ
const response = await cache.match(request);
if (!response) return null;
return await response.json() as T;
},
// Mặc định là 5 phút (300 giây)
async set<T>(cacheKey: string, data: T, maxAge: number = 300): Promise<Response> {
const request = new Request(`https://cache.local/${cacheKey}`); // Fake URL hợp lệ
const response = new Response(JSON.stringify(data), {
status: 200,
headers: {
"Content-Type": "application/json",
"Cache-Control": `s-maxage=${maxAge}`,
},
});
const cache = caches.default;
await cache.put(request, response.clone());
return response;
},
async delete(cacheKey: string): Promise<boolean> {
const cache = caches.default;
const request = new Request(`https://cache.local/${cacheKey}`); // Fake URL hợp lệ
return await cache.delete(request);
},
};
Trong file src\middlewares.ts
const ALLOWED_DOMAINS = [
"https://example.com",
"https://admin.example.com",
"http://localhost", // Cho phép gọi từ localhost
"http://127.0.0.1", // Một số trình duyệt sử dụng 127.0.0.1 thay vì localhost
"https://truyenvideo.com", // Cho phép từ truyenvideo.com
"https://www.truyenvideo.com",// Nếu có subdomain www
"*", // Cho phép từ mọi domain
"https://dash.cloudflare.com",// Cho phép từ Cloudflare Dashboard
];
const ALLOWED_IPS = ["*", "113.190.232.233", "203.0.113.42"]; // Danh sách IP cho phép
// Middleware kiểm tra domain (CORS)
export async function domainMiddleware(request: any, env: any) {
const origin = request.headers.get("Origin");
console.log("Origin:", origin);
// Nếu cho phép tất cả domain, bỏ qua kiểm tra
if (ALLOWED_DOMAINS.includes("*")) {
return null;
}
// Nếu Origin không hợp lệ, chặn request
if (!ALLOWED_DOMAINS.includes(origin)) {
return new Response("Forbidden: Invalid Domain", { status: 403 });
}
return null; // Tiếp tục xử lý
}
// Middleware kiểm tra IP
export async function ipMiddleware(request: any, env: any) {
const ip = request.headers.get("CF-Connecting-IP"); // Lấy IP từ Cloudflare
if (ALLOWED_IPS.includes("*")) {
return null;
}
// Nếu IP không hợp lệ, chặn request
if (!ip || !ALLOWED_IPS.includes(ip)) {
return new Response("Forbidden: Invalid IP", { status: 403 });
}
return null; // Tiếp tục xử lý
}