ETag للـ API: قلّل نقل JSON المتكرر من 8MB لـ 530KB
هتطلع من المقال ده بطريقة عملية تقلل نقل JSON المتكرر في API بدون تغيير شكل الواجهة أو نقل الداتا لقاعدة جديدة.
مستوى القارئ: متوسط
المشكلة باختصار
لو عندك صفحة منتجات بتطلب /api/products كل دقيقة، غالبًا بتبعت نفس الرد كاملًا حتى لو الداتا لم تتغير. الطريقة دي بتفشل مع تطبيقات dashboards والموبايل، لأن كل refresh بيكلف bandwidth وCPU ووقت انتظار.
الافتراض هنا إن الرد حجمه حوالي 82KB، وعندك 100 طلب متكرر من نفس العملاء خلال فترة قصيرة. بدون ETag هتنقل تقريبًا 8.2MB. مع ETag، أول طلب فقط يرجع JSON، وبعده الطلبات المطابقة ترجع 304 Not Modified بدون body تقريبًا. الرقم التقريبي ينزل إلى 530KB بسبب headers وبعض الطلبات غير المطابقة.
المثال قبل المفهوم
ركز في السيناريو ده: عندك لوحة تحكم تعرض آخر الطلبات. الموظف يفتحها طول اليوم. الواجهة تعمل polling كل 60 ثانية. في أغلب الدقائق لا يوجد طلب جديد، لكن السيرفر ما زال يبعت نفس JSON كاملًا.
ETag هنا يشتغل كرقم نسخة للرد. أول مرة السيرفر يقول للعميل: هذا الرد نسخته "abc123". في الطلب التالي، العميل يرسل If-None-Match: "abc123". لو النسخة لم تتغير، السيرفر يرد 304 بدل ما يرسل JSON مرة ثانية.
علميًا، ETag هو entity tag يمثل نسخة المورد. وIf-None-Match يجعل طلب GET مشروطًا. حسب MDN، لو الـ ETag عند السيرفر يطابق قيمة If-None-Match، فالرد المناسب في GET/HEAD هو 304 Not Modified مع headers التخزين المهمة.
تطبيق عملي في Express
أفضل طريقة في API صغير أو متوسط هي حساب hash من JSON النهائي. هذا ليس أسرع شيء في العالم، لكنه واضح وسهل القياس. الـ trade-off هنا: هتكسب تقليل نقل الشبكة، وتخسر وقت CPU بسيط لحساب hash.
import express from "express";
import { createHash } from "node:crypto";
const app = express();
function strongEtag(payload) {
return '"' + createHash("sha256")
.update(payload)
.digest("base64url") + '"';
}
app.get("/api/products", async (req, res) => {
const products = await loadProductsFromCacheOrDb();
const body = JSON.stringify({ products });
const etag = strongEtag(body);
res.set("Cache-Control", "private, max-age=0, must-revalidate");
res.set("ETag", etag);
if (req.headers["if-none-match"] === etag) {
return res.status(304).end();
}
res.type("application/json").send(body);
});
app.listen(3000);
في المثال ده استخدمنا من Node.js لحساب SHA-256. توثيق Node يوضح أن ينشئ كائن Hash ويستخدم و لإنتاج digest نهائي.