এই পর্বে যা শিখবেন
- একটি product list অনুরোধ কোন কোন স্তর পার হয়, আর প্রতিটি স্তর কী যোগ করে
- Method, path, header, body, status আর cookie নিজের চোখে পড়া
- ধরে নেওয়া সংখ্যা দিয়ে latency budget হিসাব করা
- DevTools-এর Network, Waterfall আর Timing পড়া, আর
curl -vচালানো - 502 দেখে ধাপে ধাপে ভাঙা স্তরটি খুঁজে বের করা
১ · একটি ক্লিকের পেছনে পুরো পথের মানচিত্র
“বাজারঘর” আমাদের কাল্পনিক অনলাইন দোকান। রুনা ফোনে সাইট খুলে “চাল” বিভাগে চাপলেন। এক সেকেন্ডের কম সময়ে পণ্যের তালিকা চলে এল। এই ছোট্ট মুহূর্তে একটি request (অনুরোধ) বেশ কয়েকটি মেশিন ঘুরে একটি response (উত্তর) হয়ে ফিরেছে।
ফুল-স্ট্যাক ডেভেলপারের কাছে এই পথের মানচিত্র সবচেয়ে কাজের জিনিস। কারণ পরের ৯০টি পর্বে আপনি যা-ই লিখবেন, সেটি এই পথের কোনো একটি জায়গায় বসবে। আর কিছু ভাঙলে প্রথম প্রশ্ন হবে, “পথের কোন জায়গায় ভাঙল?”
একটি বড় রেস্তোরাঁ ভাবুন। ঠিকানা-বই দেখে আপনি রেস্তোরাঁ খুঁজে পান (DNS). গলির মোড়ে তাদের ছোট কাউন্টারে আগে থেকে বানানো খাবার থাকে (CDN). ভেতরে রিসেপশন খালি টেবিলে পাঠায় (load balancer). ওয়েটার ঠিক করেন অর্ডারটি কোন বিভাগের (router). দরজায় কেউ পরিচয় দেখেন (middleware). ম্যানেজার অর্ডার বুঝে নেন (controller). রাঁধুনি রান্নার নিয়ম মানেন (service). উপকরণ আসে গুদাম থেকে (database). খাবার ফেরে উল্টো পথে।
“বাজারঘর”-এর একটি অনুরোধের মানচিত্র
Request goes down, response comes back up the same path
সব অ্যাপে সব স্তর থাকে না। একটি ছোট প্রজেক্টে CDN না-ও থাকতে পারে, আর এক server-এই proxy, app আর database চলতে পারে। আবার বড় ব্যবস্থায় API gateway, cache বা একাধিক service যোগ হয়। Managed hosting-এ কিছু স্তর আপনার চোখের আড়ালে থাকে, কিন্তু থাকে ঠিকই। তাই মানচিত্রটি মুখস্থ করার জিনিস নয়, নিজের অ্যাপে মিলিয়ে দেখার জিনিস। আপনার সাইটে এই ছবির কোন বাক্সগুলো আছে, সেটি জানা প্রথম কাজ।
এই মানচিত্রের নিচের স্তরগুলোর প্রোটোকল অন্য কোর্সে গভীরভাবে পড়ানো হয়েছে। এখানে শুধু এটুকু মনে রাখুন:
- DNS:
bazarghar.example-এর মতো নামকে IP ঠিকানায় বদলায়। বিস্তারিত: DNS. - TCP ও TLS: নির্ভরযোগ্য, এনক্রিপ্ট করা সংযোগ বানায়। TCP-তে এক RTT লাগে, TLS 1.3-এ আরও এক RTT. বিস্তারিত: TCP সংযোগ, TLS ও HTTPS.
- HTTP: এই সংযোগের ওপর দিয়ে অনুরোধ-উত্তরের ভাষা। প্রোটোকলের গভীরে: HTTP, HTTP/2 ও HTTP/3.
- শুরুর চিত্র: একদম নতুন হলে আগে পড়ুন ওয়েব কীভাবে কাজ করে।
১৯৯০-এর দশকের শুরুতে CERN-এ Tim Berners-Lee-র প্রথম HTTP ছিল খুবই সরল: একটি GET, কোনো header নেই, status নেই। ১৯৯৬ সালে RFC 1945 লিখে রাখে HTTP/1.0-এর প্রচলিত রূপ। আজকের অর্থ-নিয়ম আছে RFC 9110-এ (২০২২), যা HTTP/1.1, HTTP/2 আর HTTP/3 তিনটির জন্যই এক।
২ · এক অনুরোধ, হপ ধরে ধরে
এক মেশিন বা স্তর থেকে পরেরটিতে যাওয়াকে বলে hop (হপ)। নিচের ডায়াগ্রামে রুনার অনুরোধটি ধরে এগোন। ডানের ঘড়িতে সময় জমতে থাকবে। সংখ্যাগুলো হিসাবের সুবিধার জন্য ধরে নেওয়া, মাপা নয়: browser থেকে CDN পর্যন্ত RTT 20 ms, CDN থেকে মূল server পর্যন্ত RTT 40 ms, আর CDN-মূল server-এর সংযোগ আগে থেকেই খোলা।
- Browserরুনার ফোন
- DNSনাম → IP
- CDNকাছের edge
- Proxyapp বাছাই
- Appআপনার কোড
- Databaseআসল তথ্য
| অনুরোধ এখন | |
| মোট সময় |
fetch("https://bazarghar.example/api/products?category=rice")
bazarghar.example → 203.0.113.10
203.0.113.x ডকুমেন্টেশনের জন্য সংরক্ষিত ঠিকানা
TCP handshake 1 RTT
TLS handshake 1 RTT
এখনো একটি বাইট HTTP যায়নি
GET /api/products?category=rice
cache: MISS (ব্যক্তিগত বা ঘন ঘন বদলানো তথ্য, তাই মূল server-এ যায়)
upstream: app-2 (health check ঠিক আছে) X-Forwarded-For: 198.51.100.7 X-Request-Id: req-1001
router GET /api/products → listProducts middleware log, cookie পড়া
SELECT id, name, price FROM products
WHERE category = $1 LIMIT 20;
-- $1 = 'rice' (parameter, জোড়া লাগানো লেখা নয়)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"items":[{"id":1,"name":"Miniket Rice 5kg","price":420}, …]}
app → proxy → CDN → browser
প্রথম বাইট পৌঁছাল: 185 ms
const { items } = await res.json();
শেষ বাইট: 225 ms, এবার পর্দায় আঁকা
- শুরু, 0 ms. রুনা “চাল” চাপলেন। পাতার JavaScript একটি fetch ডাকল। অনুরোধ এখনো ফোন ছাড়েনি।
- DNS, 25 ms. Browser নামটির IP জানে না। ধরে নেওয়া হলো, resolver থেকে উত্তর আনতে ২৫ ms লাগল। পরের অনুরোধে এই সময় লাগবে না, কারণ উত্তরটি cache-এ থাকবে।
- সংযোগ, 65 ms. CDN-এর কাছের edge server-এর সাথে TCP আর TLS মিলে দুই RTT, অর্থাৎ আরও ৪০ ms. ৬৫ ms পেরিয়ে গেছে, অথচ একটি বাইট HTTP-ও যায়নি।
- CDN, 75 ms. অনুরোধ RTT-র অর্ধেক, ১০ ms-এ CDN-এ পৌঁছাল। ছবি বা CSS হলে এখানেই উত্তর মিলত। এই তালিকা cache-এ নেই (MISS), তাই মূল server-এর দিকে গেল।
- Proxy, 97 ms. ২০ ms পথ আর ২ ms কাজ। Load balancer বা reverse proxy একটি সুস্থ app বেছে নিল। আসল client-এর IP আর একটি request id header-এ জুড়ে দিল।
- App, 105 ms. এবার আপনার কোড। Router path দেখে handler বাছল। Middleware log লিখল আর cookie পড়ল। দুটো মিলে ৮ ms.
- Database, 150 ms. Controller আর service ৫ ms নিল, database query নিল ৪০ ms. এটিই এই যাত্রার সবচেয়ে বড় একক সার্ভার-খরচ।
- JSON, 155 ms. Database-এর সারিগুলো থেকে object, তারপর JSON লেখা। ৫ ms. Status আর header ঠিক করে উত্তর রওনা হলো।
- ফেরা, 185 ms. মূল server থেকে CDN ২০ ms, CDN থেকে ফোন ১০ ms. প্রথম বাইট পৌঁছাল। অনুরোধ পাঠানো শেষ হয়েছিল ৬৫ ms-এ, তাই TTFB এখানে ১৮৫ − ৬৫ = ১২০ ms.
- শেষ বাইট, 225 ms. ২০ KB × ৮ = ১৬০ kilobit, ৪ Mbps-এ ৪০ ms. মোট ২২৫ ms. এর পরে browser JSON পড়ে পর্দা বদলায়, সেই সময় এই হিসাবের বাইরে।
এই ঘড়ির হিসাব থেকে তিনটি কথা বেরিয়ে আসে। প্রথমত, আপনার লেখা কোড চলেছে মাত্র ৫৮ ms, মোট ২২৫ ms-এর চার ভাগের এক ভাগের একটু বেশি। দ্বিতীয়ত, প্রতিটি স্তর দুবার সময় নেয়: যাওয়ার পথে আর ফেরার পথে। তৃতীয়ত, প্রতিটি স্তর অনুরোধে কিছু যোগ করে, আবার ব্যর্থও হতে পারে। তাই নতুন স্তর মানে নতুন ক্ষমতা, আবার নতুন একটি খোঁজার জায়গা।
৩ · HTTP বার্তা — ডেভেলপার যেভাবে দেখেন
পোস্ট অফিসের খাম ভাবুন। ওপরে ঠিকানা, পাশে নির্দেশনা (“ভঙ্গুর”, “জরুরি”), ভেতরে চিঠি। HTTP বার্তাও তাই। HTTP/1.1-এ এটি সত্যিই সাধারণ লেখা। HTTP/2 আর HTTP/3-এ তারে যায় binary ফ্রেম হয়ে, কিন্তু ডেভেলপারের কাছে অর্থ একই থাকে।
| অংশ | উদাহরণ | ডেভেলপারের কাছে অর্থ |
|---|---|---|
| Method | GET, POST, PUT, PATCH, DELETE | কী করতে চাই। GET পড়ে, তথ্য বদলায় না। |
| Path ও query | /api/products?category=rice | কোন জিনিস, কোন শর্তে। Router এটিই দেখে। |
| Header | Accept, Authorization, Cookie | বার্তা সম্পর্কে তথ্য, নাম-মানের জোড়া। |
| Body | {"productId":1,"qty":2} | আসল তথ্য। সাধারণত POST, PUT, PATCH-এ পাঠানো হয়। |
| Status | 200, 201, 404, 500 | ফলাফলের সংক্ষেপ। ২xx সফল, ৩xx অন্যত্র, ৪xx client-এর ভুল, ৫xx server-এর ভুল। |
| Cookie | Set-Cookie → Cookie | Server একটি ছোট লেখা রাখতে বলে, browser পরের অনুরোধে ফেরত পাঠায়। |
কার্টে পণ্য যোগ করলে অনুরোধটি অন্যরকম দেখায়। এখানে body আছে, আর Content-Type জানায় body-টি কী রূপে আছে। Server এই header দেখেই ঠিক করে কীভাবে পড়বে। JSON পাঠিয়ে এই header ভুলে গেলে অনেক framework-এ req.body ফাঁকা আসে, যা নতুনদের একটি পরিচিত ঝামেলা।
POST /api/cart/items HTTP/1.1
Host: bazarghar.example
Content-Type: application/json
Cookie: sid=example-session-id
{"productId":1,"qty":2}
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{"cartItems":3}
এটি বার্তার গঠন দেখানোর উদাহরণ, কোনো run-এর আউটপুট নয়। URL আর method-এর নকশা আসবে পর্ব ৩২-এ, status code-এর বিস্তার পর্ব ৩৩-এ।
JSON হলো আজকের API-র সাধারণ ভাষা: object আর array লেখার একটি নিয়ম। মনে রাখুন, তারে যায় লেখা। res.json() বা JSON.parse সেই লেখাকে object বানায়। তাই JSON-এ তারিখ বা Map নিজের আসল রূপে থাকে না, আর দামের মতো সংখ্যা ভুলে string হয়ে গেলে যোগের বদলে জোড়া লাগে।
এবার সত্যিকারের একটি node:http server চালান। একই ব্লকে fetch দিয়ে তাকে ডাকা হচ্ছে, আর দুই দিক থেকে বার্তাটি ছাপা হচ্ছে। Language-এর খুঁটিনাটি আছে JavaScript-এর fetch ও HTTP পর্বে।
const http = require("node:http");
const server = http.createServer((req, res) => {
const url = new URL(req.url, "http://localhost");
console.log("SERVER saw:", req.method, url.pathname, url.search);
console.log(" accept =", req.headers.accept);
console.log(" x-request-id =", req.headers["x-request-id"]);
const body = JSON.stringify({
category: url.searchParams.get("category"),
items: [{ id: 1, name: "Miniket Rice 5kg", price: 420 }],
});
res.writeHead(200, {
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": "no-store",
Connection: "close",
});
res.end(body);
});
server.listen(0, "127.0.0.1", async () => {
const { port } = server.address();
const r = await fetch(`http://127.0.0.1:${port}/api/products?category=rice`, {
headers: { Accept: "application/json", "X-Request-Id": "req-1001" },
});
console.log("CLIENT got:", r.status, r.statusText);
console.log(" content-type =", r.headers.get("content-type"));
console.log(" cache-control =", r.headers.get("cache-control"));
const text = await r.text(); // body: শুধু লেখা
console.log(" raw body =", text);
const data = JSON.parse(text); // লেখা থেকে object
console.log(" price + 1 =", data.items[0].price + 1);
server.close();
});Server দেখল SERVER saw: GET /api/products ?category=rice, অর্থাৎ path আর query আলাদা জিনিস। Header-এর নাম Node ছোট হাতের অক্ষরে দেয়, তাই x-request-id = req-1001. Client পেল CLIENT got: 200 OK, আর raw body ছিল শুধু একটি লম্বা লেখা। JSON.parse-এর পরেই price + 1 = 421 এল। port 0 মানে OS নিজে একটি খালি port দিল।
GET অনুরোধের URL-এ কখনো password, token বা OTP রাখবেন না। URL জমা হয় browser-এর history, proxy-র log আর analytics-এ। গোপন তথ্য যায় header বা POST body-তে, HTTPS-এর ওপর দিয়ে।
৪ · App server-এর ভেতরে — router, middleware, controller, service
Express, Next.js, Django বা Laravel যেটিই হোক, ভেতরের ছাঁচ প্রায় একই। স্তরগুলো আলাদা রাখা হয় যাতে প্রতিটি অংশের একটিই কাজ থাকে।
Middleware
সব বা অনেক route-এর সাধারণ কাজ: log, cookie পড়া, body parse, login যাচাই।
বিস্তারিত: পর্ব ২৬Service
ব্যবসার নিয়ম: স্টকে আছে কি না, কোন ছাড় খাটে। HTTP জানে না, তাই test সহজ।
বিস্তারিত: পর্ব ২৯নিচের ব্লকে হাতে লেখা ছোট একটি router আর middleware chain আছে। কোনো সত্যিকারের network নেই, তাই ঘড়িটিও সিমুলেটেড। সংখ্যাগুলো স্টেপারের সাথে মেলানো: router আর middleware ৮, controller আর service ৫, database ৪০, JSON ৫.
// সিমুলেটেড ঘড়ি: সময় মাপা নয়, ধরে নেওয়া (ms)
let clock = 0;
const hop = (layer, note, ms) => {
clock += ms;
console.log(`${String(clock).padStart(3)} ms ${layer.padEnd(10)} ${note}`);
};
const rows = [{ id: 1, name: "Miniket Rice 5kg", cat: "rice" },
{ id: 2, name: "Masoor Dal 1kg", cat: "dal" }];
const service = (cat) => {
hop("service", `rule: only category=${cat}`, 3);
const found = rows.filter((r) => r.cat === cat);
hop("database", `SELECT ... -> ${found.length} row`, 40);
return found;
};
const listProducts = (req) => { // controller
hop("controller", "read query, call service", 2);
const items = service(req.query.category);
hop("controller", "build JSON", 5);
return { status: 200, body: { items } };
};
const routes = { "GET /api/products": listProducts };
const logger = (req, next) => { hop("middleware", "log request", 2); return next(); };
const auth = (req, next) => { hop("middleware", "cookie: guest", 3); return next(); };
const chain = [logger, auth];
function handle(req) {
clock = 0;
hop("router", `match ${req.method} ${req.path}`, 3);
const handler = routes[`${req.method} ${req.path}`];
if (!handler) { hop("router", "no route", 0); return { status: 404 }; }
const run = (i) => (i < chain.length ? chain[i](req, () => run(i + 1)) : handler(req));
return run(0);
}
console.log(JSON.stringify(handle({ method: "GET", path: "/api/products", query: { category: "rice" } })));
console.log(JSON.stringify(handle({ method: "GET", path: "/api/product", query: {} })));প্রথম অনুরোধ 58 ms-এ শেষ, আর log পড়লেই বোঝা যায় 53 ms লাইনের ৪০ ms গেছে database-এ। দ্বিতীয় অনুরোধে /api/product-এর শেষের s নেই। Router-এ মিল নেই, তাই {"status":404} ফিরল 3 ms-এ। Middleware, controller, database কিছুই চলেনি। next() ডাকলে পরের ধাপ চলে, এই সরল ধারণার ওপরই Express-এর পাইপলাইন দাঁড়িয়ে।
৫ · সময় কোথায় যায় — latency budget
যে সময়টা একটি অনুরোধে লাগে, তাকে বলে latency. মাসের বাজেটে যেমন বাসাভাড়া, বাজার, যাতায়াত আলাদা করে লেখা হয়, তেমনি latency budget-এ মোট সময়কে স্তরে ভাগ করা হয়। ধরা যাক, “বাজারঘর”-এর দল ঠিক করল, product API-র মোট সময় ৩০০ ms-এর মধ্যে থাকবে (লক্ষ্যটি দলের নিজের সিদ্ধান্ত, কোনো মান নয়)।
| ধাপ | প্রথম অনুরোধ (ms) | দ্বিতীয় অনুরোধ (ms) | কেন |
|---|---|---|---|
| DNS | 25 | 0 | উত্তর cache-এ আছে |
| TCP + TLS 1.3 | 20 + 20 | 0 | সংযোগ খোলাই আছে (keep-alive) |
| Browser ↔ CDN | 20 | 20 | এক RTT |
| CDN ↔ মূল server | 40 | 40 | এক RTT |
| Proxy + app + database + JSON | 2 + 13 + 40 + 5 | 60 | server-এর কাজ |
| Body download | 40 | 40 | 20 KB, 4 Mbps |
| মোট | 225 | 160 | ৬৫ ms বেঁচেছে |
ক্যালকুলেটরটি চালিয়ে দেখুন। চারটি প্রশ্নের উত্তর এখানে: সংযোগ আবার ব্যবহার করলে কত বাঁচে, ধীর query কত ক্ষতি করে, বড় উত্তর কত ক্ষতি করে, আর CDN-এ মিললে কী হয়।
// সব সংখ্যা ধরে নেওয়া (ms); আপনার অ্যাপের মাপা সংখ্যা বসিয়ে দেখুন
const download = (kb, mbps) => (kb * 8) / mbps; // KB -> kilobit, ÷ Mbps = ms
const base = {
dns: 25, tcp: 20, tls: 20, browserToEdge: 20, edgeToOrigin: 40,
lb: 2, app: 13, db: 40, json: 5, body: download(20, 4),
};
const BUDGET = 300;
const report = (name, changes) => {
const p = { ...base, ...changes };
const total = Object.values(p).reduce((a, b) => a + b, 0);
const server = p.lb + p.app + p.db + p.json;
const network = total - server;
const mark = total <= BUDGET ? "OK " : "OVER";
console.log(`${mark} ${String(total).padStart(4)} ms server ${String(server).padStart(3)} network ${String(network).padStart(3)} ${name}`);
};
const warm = { dns: 0, tcp: 0, tls: 0 };
const edgeHit = { edgeToOrigin: 0, lb: 0, app: 0, db: 0, json: 0 };
console.log(`budget = ${BUDGET} ms, body = ${base.body} ms`);
report("first request (cold)", {});
report("second request (warm)", warm);
report("warm, slow query", { ...warm, db: 400 });
report("warm, 200 KB body", { ...warm, body: download(200, 4) });
report("warm, CDN hit", { ...warm, ...edgeHit });
// cache hit ratio: গড় = h × hit + (1 - h) × miss
const avg = (h, hit, miss) => h * hit + (1 - h) * miss;
for (const h of [0.5, 0.9, 0.99]) console.log(`hit ratio ${h}: average ${avg(h, 60, 160).toFixed(0)} ms`);first request ২২৫ ms আর second request ১৬০ ms, টেবিলের সাথে হুবহু মিলেছে। warm, slow query লাইনে index ছাড়া query ৪০০ ms ধরলে মোট 520 ms, বাজেট ফেল। warm, 200 KB body-তেও 520 ms: ২০০ × ৮ ÷ ৪ = ৪০০ ms. দুই সমস্যার ফল এক, কিন্তু একটি server-এ, অন্যটি network-এ। ঠিক এই কারণেই ভাগ করে দেখতে হয়। warm, CDN hit মাত্র 60 ms, কারণ অনুরোধ মূল server পর্যন্ত যায়ইনি। শেষের তিন লাইন cache-এর গড়: ০.৯ × ৬০ + ০.১ × ১৬০ = ৭০ ms.
দুটি পরিচিত হিসাব হাতে করে দেখুন। প্রথমটি বড় JavaScript ফাইল: ১.৫ MB ফাইল ৪ Mbps-এ নামতে ১.৫ × ৮ ÷ ৪ = ৩ সেকেন্ড লাগে, parse আর চালানোর সময় বাদেই। দ্বিতীয়টি বারবার database ডাকা: ৫০টি পণ্যের জন্য একটি তালিকা query আর প্রতিটির জন্য আলাদা একটি query মানে ৫১টি query. প্রতিটি মাত্র ২ ms হলেও মোট ১০২ ms, অথচ একটি join-এ কাজটি হতে পারত। একে বলে N+1 সমস্যা, যা পর্ব ৪২-এ মাপা হবে।
গড় সময় সবটা বলে না। ধরুন ১০০টি অনুরোধের ৯৯টি ১৬০ ms, একটি ৫,০০০ ms. গড় দাঁড়ায় (৯৯ × ১৬০ + ৫০০০) ÷ ১০০ = ২০৮.৪ ms, যা দেখতে নিরীহ। অথচ প্রতি ১০০ জনে একজন পাঁচ সেকেন্ড অপেক্ষা করছেন। তাই দলগুলো percentile দেখে: p50 মানে অর্ধেক অনুরোধ এর চেয়ে দ্রুত, p99 মানে ৯৯% অনুরোধ এর চেয়ে দ্রুত। আরও গভীর হিসাব পাবেন Latency ও Throughput পর্বে।
একটি RTT যত ছোটই হোক, তাকে কোড দিয়ে কমানো যায় না, কারণ সেটি দূরত্ব আর পথের ব্যাপার। কমানো যায় RTT-র সংখ্যা (সংযোগ পুনর্ব্যবহার), পথের দৈর্ঘ্য (CDN), server-এর কাজ (index, cache) আর পাঠানো বাইট (compression, pagination).
৬ · HTTP-র স্মৃতি নেই — state তাহলে থাকে কোথায়
RFC 9110 বলে, HTTP একটি stateless প্রোটোকল: প্রতিটি অনুরোধের অর্থ একা পড়েই বোঝা যায়। Server আগের অনুরোধ মনে রাখতে বাধ্য নয়। অথচ রুনার কার্ট তো মনে থাকে! কীভাবে?
পাড়ার চায়ের দোকানে দোকানি সব মুখ মনে রাখেন না। আপনি বলেন, “আমার খাতা নম্বর ৪২।” তিনি খাতা খুলে হিসাব দেখেন। এখানে নম্বরটি হলো cookie, আর খাতাটি session store. দোকানির মগজ ফাঁকা থাকলেও হিসাব হারায় না, আবার অন্য কর্মচারী বসলেও একই খাতা খুলতে পারেন।
const http = require("node:http");
const { randomBytes } = require("node:crypto");
const sessions = new Map(); // চাবি: session id, মান: কার্ট
const server = http.createServer((req, res) => {
const sid = /sid=(\w+)/.exec(req.headers.cookie ?? "")?.[1];
const known = sessions.has(sid);
const id = known ? sid : randomBytes(16).toString("hex"); // অনুমান করা যায় না
const cart = known ? sessions.get(id) : { items: 0 };
cart.items += 1;
sessions.set(id, cart);
const headers = { "Content-Type": "application/json", Connection: "close" };
// production-এ HTTPS, তাই সেখানে Secure-ও যোগ হবে
if (!known) headers["Set-Cookie"] = `sid=${id}; HttpOnly; SameSite=Lax; Path=/`;
res.writeHead(200, headers).end(JSON.stringify(cart));
});
server.listen(0, "127.0.0.1", async () => {
const url = `http://127.0.0.1:${server.address().port}/cart/add`;
const add = async (cookie) => {
const r = await fetch(url, { method: "POST", headers: cookie ? { Cookie: cookie } : {} });
return { cart: await r.json(), setCookie: r.headers.get("set-cookie") };
};
const first = await add();
console.log("1 no cookie ->", first.cart, "| set-cookie:", first.setCookie.replace(/=\w+/, "=(32 hex)"));
const cookie = first.setCookie.split(";")[0]; // browser এই কাজ নিজে করে
console.log("2 with cookie->", (await add(cookie)).cart);
console.log("3 with cookie->", (await add(cookie)).cart);
console.log("4 no cookie ->", (await add()).cart);
console.log("sessions on server:", sessions.size);
server.close();
});অনুরোধ ২ আর ৩ cookie নিয়ে এসেছে, তাই কার্ট বেড়ে { items: 3 } হলো। অনুরোধ ৪ cookie আনেনি, তাই server তাকে চিনল না: { items: 1 }, আর শেষে sessions on server: 2. Connection বা ফোন এক থাকলেও কিছু আসে যায় না, পরিচয় আসে শুধু header থেকে। HttpOnly JavaScript-কে cookie পড়তে দেয় না, SameSite=Lax অন্য সাইটের বেশিরভাগ অনুরোধে cookie পাঠানো আটকায়।
এই কোডের Map app-এর মেমরিতে থাকে, আর সেখানেই ফাঁদ। app-1-এর Map-এ রুনার কার্ট, কিন্তু পরের অনুরোধ load balancer পাঠাল app-2-তে। সেখানে কার্ট নেই! Server restart হলেও সব হারাবে। তাই বাস্তবে state রাখা হয় app-এর বাইরে:
| কোথায় | কী রাখা হয় | মনে রাখার কথা |
|---|---|---|
| URL | বিভাগ, পাতা, sort | শেয়ার করা যায়; গোপন কিছু নয় |
| Cookie | session id | প্রতিটি অনুরোধে যায়, তাই ছোট রাখুন |
| Browser storage | theme, খসড়া | JavaScript পড়তে পারে; token-এর ঝুঁকি (পর্ব ৪৬) |
| Redis-জাতীয় store | session, cache | সব app ভাগ করে নেয় (পর্ব ৪৪, ৪৫) |
| Database | order, দাম, স্টক | আসল, স্থায়ী তথ্য |
বড় ব্যবস্থায় এই নকশার আলোচনা পাবেন Session ও State পর্বে, আর নিরাপত্তার দিক Session ও Token পর্বে।
৭ · উত্তর পাওয়ার পর browser কী করে
পর্দায় পণ্য দেখানোর কাজ তখনো বাকি। পুরো পাতা খোলার সময় HTML এলে browser এই কাজগুলো করে:
- HTML parse: লেখা পড়ে গাছের মতো DOM বানায়। সাধারণ
<script>পেলে থেমে সেটি চালায়, তাইdeferবাtype="module"কাজের। - CSS: stylesheet থেকে CSSOM বানায়। CSS না এলে সাধারণত প্রথম আঁকা আটকে থাকে, যাতে স্টাইলহীন পাতা না দেখা যায়।
- Layout ও paint: কোন জিনিস কত বড়, কোথায় বসবে তা হিসাব করে পিক্সেল আঁকে।
- JavaScript: React-এর মতো কোড চলে, event ধরে।
- Client থেকে fetch: JS নিজে আবার API ডাকে, JSON পায়, DOM বদলায়। এটিই আধুনিক অ্যাপের দ্বিতীয় যাত্রা।
Client থেকে fetch করার সময় একটি ফাঁদ আছে। fetch শুধু network-এ ব্যর্থ হলে বা CORS-এ আটকালে reject করে। 404 বা 500 এলেও Promise সফল হয়, কারণ উত্তর তো এসেছে! তাই res.ok নিজে দেখতে হয়।
async function loadProducts(category) {
const res = await fetch(`/api/products?category=${encodeURIComponent(category)}`);
if (!res.ok) throw new Error(`API failed: ${res.status}`); // 404, 500 এখানে ধরা পড়ে
return res.json();
}ব্যবহারকারীর ইনপুট URL-এ বসানোর আগে encodeURIComponent দিয়ে নিরাপদ করা হয়েছে। React-এ এই ডাক, loading আর error অবস্থা কীভাবে সামলাতে হয়, তা পর্ব ১৩ আর ১৯-এ আসবে।
অর্থাৎ একটি পাতা মানে একটি অনুরোধ নয়, বহু অনুরোধ। এই ক্রম কোথায় বদলানো যায়, অর্থাৎ HTML server-এ তৈরি হবে না browser-এ, সেটিই পরের পর্বের বিষয়। Parse আর render-এর খুঁটিনাটি আছে ওয়েব কীভাবে কাজ করে পর্বে।
৮ · DevTools আর curl — যাত্রাটি নিজের চোখে দেখা
F12 চেপে DevTools খুলুন, Network ট্যাবে যান, পাতা reload করুন। প্রতিটি সারি একটি অনুরোধ। Name, Status, Type, Size, Time কলাম দেখুন। Size-এ (memory cache) বা (disk cache) থাকলে অনুরোধটি network-এই যায়নি। শেষের Waterfall কলাম সময়রেখা, আর একটি সারিতে ক্লিক করে Timing ট্যাবে সেই অনুরোধের ভাগগুলো পাবেন। Chrome-এর ডকুমেন্টেশন অনুযায়ী প্রধান ভাগগুলো হলো Queueing, Stalled, DNS Lookup, Initial connection (ভেতরে SSL), Request sent, Waiting (TTFB) আর Content Download.
নিচে “বাজারঘর”-এর পাতা প্রথমবার খোলার একটি নমুনা waterfall, সংখ্যাগুলো ধরে নেওয়া। ফোনের গতি ৪ Mbps.
| সারি | |
| DNS + সংযোগ | |
| Waiting (TTFB) | |
| Content Download |
- প্রথম সারি: HTML document. ০ থেকে ২৬০ ms. কমলা অংশে DNS আর সংযোগ, ৬৫ ms. সবুজ Waiting ১৮০ ms, এই সময়টা server ভাবছে আর উত্তর পথে আছে। Waiting বড় হলে সন্দেহ আগে server-এর দিকে যায়।
- দুটি সারি একসাথে শুরু। HTML-এর শুরুর অংশ পড়েই browser ২৫০ ms-এ CSS আর JS চাইল। সংযোগ আগেই খোলা, তাই কমলা নেই।
app.js-এর Download ১২০ ms: ৬০ KB × ৮ ÷ ৪ Mbps. এই সারিতে দীর্ঘ নীল মানে ফাইল বড় বা network ধীর। - ৪১০ থেকে ৫০০ ms: ফাঁকা জায়গা। Waterfall-এ ফাঁকা মানে সবসময় অলস নয়। এখানে browser JS parse করে চালাচ্ছে। Network ট্যাব এটি দেখায় না, দেখতে হয় Performance ট্যাবে।
- API শুরু ৫০০ ms-এ। JS চলার পরেই
fetchডাকা হলো, তাই এত দেরিতে শুরু। Waiting ১২০ + Download ৪০ = ১৬০ ms, ঠিক বিভাগ ৫-এর দ্বিতীয় অনুরোধের হিসাব। শেষ ৬৬০ ms-এ। এর আগে রুনা পণ্য দেখেননি। - ছবি সবার শেষে। JSON না এলে browser জানতই না কোন ছবি লাগবে। Waiting মাত্র ৩০ ms, কারণ CDN-এ মিলেছে। Download ১৫০ ms: ৭৫ KB × ৮ ÷ ৪. পাতা শেষ ৮৫০ ms-এ।
- রোগ নির্ণয়। আসল প্রশ্ন: রুনা পণ্য দেখতে ৬৬০ ms অপেক্ষা করলেন কেন? API নিজে ১৬০ ms, বাকিটা তার আগের ক্রম। API দ্রুত করার বদলে HTML-এর সাথেই তথ্য পাঠানো বা JS ছোট করাই বেশি লাভের হতে পারে। Waterfall-এর মূল শিক্ষা: সবচেয়ে লম্বা বার নয়, কোনটি কার জন্য অপেক্ষা করছে সেটি দেখুন।
Network ট্যাবের কয়েকটি ছোট অভ্যাস অনেক সময় বাঁচায়। Disable cache টিক দিলে প্রথমবার আসা ব্যবহারকারীর অবস্থা দেখা যায়। Throttling থেকে ধীর network বেছে নিলে আপনার দ্রুত Wi-Fi-এর আড়ালে থাকা সমস্যা বেরিয়ে আসে। Fetch/XHR ফিল্টার শুধু API অনুরোধগুলো দেখায়। একটি সারিতে ক্লিক করলে Headers, Payload আর Response ট্যাবে পুরো বার্তা পড়া যায়। আর সারিতে right-click করে Copy as cURL নিলে একই অনুরোধ terminal-এ চালানো যায়। সাবধান: এতে cookie-ও কপি হয়, তাই কোথাও পেস্ট করার আগে মুছে ফেলুন।
Browser ছাড়াও অনুরোধ পাঠানো যায়। curl command line থেকে HTTP অনুরোধ পাঠায়, আর -v দিলে পুরো কথোপকথন দেখায়। আমরা একটি ছোট lab বানিয়েছি: node:http-এ app server (port 4102) আর একটি হাতে লেখা reverse proxy (port 4180), দুটোই শুধু 127.0.0.1-এ।
সত্যিকারের run (Node.js 24.15.0, curl 8.18.0; progress লাইন বাদ দেওয়া হয়েছে):
$ curl -v "http://127.0.0.1:4102/api/products?category=rice"
* Trying 127.0.0.1:4102...
* Established connection to 127.0.0.1 (127.0.0.1 port 4102) from 127.0.0.1 port 1358
* using HTTP/1.x
> GET /api/products?category=rice HTTP/1.1
> Host: 127.0.0.1:4102
> User-Agent: curl/8.18.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Date: Sat, 31 Oct 2026 19:39:39 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked
<
* Connection #0 to host 127.0.0.1:4102 left intact
{"items":[{"id":1,"name":"Miniket Rice 5kg","price":420},{"id":2,"name":"Masoor Dal 1kg","price":135}],"page":1}পড়ার নিয়ম সহজ: * curl-এর নিজের কথা, > যা গেল, < যা এল। খালি > লাইনের অর্থ header শেষ; GET-এ body নেই। Connection: keep-alive বলছে সংযোগটি পরের অনুরোধের জন্য খোলা রাখা যায়।
সময় মাপতে -w ব্যবহার করুন। এটি এই মেশিনে একবারের মাপ, কোনো সাধারণ ফল নয়। একই মেশিনের ভেতরে network প্রায় নেই বলেই সংখ্যা এত ছোট।
$ T='time_namelookup=%{time_namelookup} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total} code=%{http_code}\n'
$ curl -s -o /dev/null -w "$T" http://127.0.0.1:4102/api/products
time_namelookup=0.000030 connect=0.000545 ttfb=0.001059 total=0.001095 code=200একক সেকেন্ড: মোট প্রায় ১.১ ms. আপনার সাইটের ওপর চালালে আসল network-এর ছবি পাবেন। Browser আর curl-এর ফল ভিন্ন হলে সন্দেহ যায় browser-এর দিকে: cache, cookie, CORS বা extension.
আরও গভীরে: Browser-এর এই সময়গুলো JavaScript থেকেও পাওয়া যায় W3C-র Navigation Timing আর Resource Timing API দিয়ে। Server চাইলে Server-Timing header-এ নিজের ভাগ (যেমন db;dur=40) পাঠাতে পারে, যা DevTools-এ দেখা যায়। আর W3C Trace Context-এর traceparent header একই অনুরোধকে অনেক service জুড়ে অনুসরণ করতে দেয়। এসবের ব্যবহারিক রূপ পর্ব ৩১ ও ৮৩-তে আসবে।
৯ · কোন হপে ভাঙল — 404, 500, 502, 504, CORS, timeout
ত্রুটি দেখলে দুটি প্রশ্ন করুন। এক, উত্তরটি কে দিল? দুই, কত দ্রুত দিল? নিচের সব status আমাদের lab-এ সত্যিই তৈরি করা।
| লক্ষণ | অর্থ | সাধারণত কোন স্তর | প্রথম যা দেখবেন |
|---|---|---|---|
404 | এই path-এ কিছু নেই | Router, বা ভুল URL | URL-এর বানান, route তালিকা, body কার |
500 | Server-এর কোডে সমস্যা | App, controller, service | App log, request id |
502 | Proxy পেছনের app থেকে ঠিকঠাক উত্তর পায়নি | App বন্ধ, crash, ভুল port | Proxy log, app প্রক্রিয়া |
504 | Proxy সময়মতো উত্তর পায়নি | ধীর query, আটকে থাকা কাজ | Database, বাইরের API |
| CORS error | Browser উত্তরটি JS-কে দিল না | Server-এর header | Console, Origin header |
| Timeout, কোনো status নেই | উত্তর আসেইনি | DNS, firewall, network | curl, DNS, app চলছে কি না |
Lab-এর proxy দিয়ে পাঠানো ফলগুলো দেখুন (সত্যিকারের run, header ছাঁটা):
$ curl -si http://127.0.0.1:4180/api/product
HTTP/1.1 404 Not Found
content-type: application/json; charset=utf-8
Via: 1.1 lab-proxy
{"error":"NOT_FOUND","path":"/api/product"}
$ curl -si http://127.0.0.1:4180/api/boom
HTTP/1.1 500 Internal Server Error
content-type: application/json; charset=utf-8
Via: 1.1 lab-proxy
{"error":"INTERNAL","requestId":"req-7f3a"}
$ curl -si http://127.0.0.1:4180/api/slow
HTTP/1.1 504 Gateway Timeout
Content-Type: text/plain
Server: lab-proxy
Gateway Timeout404 আর 500-এর body JSON, আর Via header proxy পেরিয়ে আসার চিহ্ন: উত্তর বানিয়েছে আপনার app. 504-এর body সাদা লেখা, Server: lab-proxy: উত্তর বানিয়েছে proxy নিজে। Body-র রূপ দেখেই বলা যায় উত্তর কে লিখেছে। 500-এর উত্তরে শুধু requestId আছে, stack trace নেই। আসল কারণ আছে app-এর log-এ, যা নিরাপদ অভ্যাস (পর্ব ২৮)।
504-এর সময় মাপলে পাওয়া গেল ttfb=1.007173, অর্থাৎ proxy-র ১ সেকেন্ডের সীমা শেষ হওয়া পর্যন্ত অপেক্ষা। এবার সবচেয়ে পরিচিত রহস্যটি ধরি: 502. Lab-এ app বন্ধ রেখে অনুরোধ পাঠানো হলো। প্রতিটি ধাপের আউটপুট সত্যিকারের run থেকে নেওয়া।
| বাকি স্তর |
GET /api/products 502 text/plain
Response: Bad Gateway
$ curl -sv http://127.0.0.1:4180/api/products * Established connection to 127.0.0.1 … > GET /api/products HTTP/1.1 < HTTP/1.1 502 Bad Gateway < Content-Type: text/plain < Server: lab-proxy Bad Gateway
[proxy] 502 GET /api/products upstream error: ECONNREFUSED
$ curl -sv --max-time 3 http://127.0.0.1:4102/health
* Trying 127.0.0.1:4102...
* connect to 127.0.0.1 port 4102 … failed: Connection refused
* Failed to connect to 127.0.0.1 port 4102 after 2019 ms
$ node app.js [app] config error: DATABASE_URL is not set (exit code 1)
$ DATABASE_URL=example://bazarghar node app.js
[app] listening on 127.0.0.1:4102
$ curl -si http://127.0.0.1:4180/api/products
HTTP/1.1 200 OK
Via: 1.1 lab-proxy
- লক্ষণ দেখা। Network ট্যাবে 502. Body আপনার JSON নয়, ছোট একটি সাদা লেখা। মানে আপনার app এই উত্তর লেখেনি। Browser পর্যন্ত উত্তর এসেছে, তাই browser-এর সংযোগ ঠিক আছে।
- Browser বাদ দেওয়া। curl-এও একই 502, আর
Server: lab-proxy. সমস্যা browser-এর cache বা extension নয়। উত্তর লিখেছে proxy, তাই খুঁজতে হবে proxy আর তার পেছনে। - Proxy-র log.
ECONNREFUSEDমানে সংযোগ করতে গিয়ে সরাসরি প্রত্যাখ্যাত। Proxy সে port-এ কাউকে পায়নি। এটি ধীর query-র গল্প নয়; ধীর হলে আসত timeout বা 504. - এক হপ কাছে যাওয়া। Proxy বাদ দিয়ে app-কে সরাসরি ডাকা হলো।
Connection refused: app প্রক্রিয়াটি port-এ শুনছেই না। Database পর্যন্ত যাওয়ার প্রশ্নই আসে না, তাই database সন্দেহ থেকে বাদ। - কারণ পাওয়া। App চালাতেই সে নিজের ত্রুটি লিখে বন্ধ হলো:
DATABASE_URL is not set. একটি config না থাকায় deployment-এর পর app আর ওঠেনি। 502-এর আসল কারণ proxy-তে নয়, তার পেছনে। - ঠিক করে যাচাই। Config দিয়ে app চালু করে একই পথ আবার পরীক্ষা: এবার
200 OK. শেষ ধাপটি বাদ দেবেন না। সমস্যা যে পথে দেখা গিয়েছিল, সেই পথ দিয়েই যাচাই করুন।
CORS সবচেয়ে বিভ্রান্তিকর, কারণ curl-এ সব ঠিক দেখায়। CORS নিয়ম মানে browser, server বা curl নয়। Lab-এ 127.0.0.1:4173-এর একটি পাতা থেকে 127.0.0.1:4102-তে fetch করা হলো। Port আলাদা, তাই origin আলাদা। সত্যিকারের run (Microsoft Edge 156):
Access to fetch at 'http://127.0.0.1:4102/api/products' from origin 'http://127.0.0.1:4173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
JavaScript যা পেল: TypeError: Failed to fetch
app-এর log: [app] GET /api/products
শেষ লাইনটি লক্ষ করুন। Server অনুরোধটি পেয়েছে এবং চালিয়েছে। Browser শুধু উত্তরটি JavaScript-কে পড়তে দেয়নি। আর JS-এর কাছে আসে শুধু Failed to fetch, বিস্তারিত কেবল Console-এ। POST-এ JSON পাঠালে browser আগে একটি preflight OPTIONS অনুরোধ পাঠিয়ে অনুমতি নেয়। সমাধান server-এ অনুমোদিত origin-এর তালিকা দেওয়া, যা পর্ব ২৬ ও ৫০-এ করা হবে। পটভূমি: ওয়েবের নিরাপত্তা মডেল।
কোনো status না এলে সেই অনুরোধ HTTP পর্যন্ত পৌঁছায়ইনি, বা উত্তর ফেরেনি। Network ট্যাবে তখন Status কলামে (failed) দেখায়, আর Chromium-ভিত্তিক browser ত্রুটির নামে কারণের ইঙ্গিত দেয়। ERR_NAME_NOT_RESOLVED মানে DNS নাম খুঁজে পায়নি। ERR_CONNECTION_REFUSED মানে ঠিকানায় পৌঁছেছে, কিন্তু port-এ কেউ শুনছে না। ERR_CONNECTION_TIMED_OUT মানে কোনো সাড়াই আসেনি, যা প্রায়ই firewall বা বন্ধ server-এর লক্ষণ। নামগুলো পড়লেই খোঁজার জায়গা ছোট হয়ে আসে।
বাইরে থেকে ভেতরে এক হপ করে এগোন: browser → curl দিয়ে proxy → curl দিয়ে সরাসরি app → database. যে হপে ফল বদলাল, সমস্যা সেখানে। একসাথে অনেক কিছু বদলাবেন না। পুরো network-এর পদ্ধতি আছে Network Troubleshooting পর্বে।
পরীক্ষা ও ইন্টারভিউতে:
- “URL টাইপ করলে কী হয়?” DNS, সংযোগ, HTTP অনুরোধ, proxy, app, database, উত্তর, parse ও render, ক্রম ধরে বলুন।
- 502 আর 504-এর পার্থক্য? 502: পেছনের server থেকে অকার্যকর উত্তর বা সংযোগ ব্যর্থ। 504: সময়মতো উত্তর আসেনি।
- HTTP stateless হলে login কীভাবে থাকে? Cookie-তে id যায়, তথ্য থাকে store-এ, অথবা signed token প্রতিবার পাঠানো হয়।
- CORS কে প্রয়োগ করে? Browser. Server শুধু header দিয়ে অনুমতি জানায়।
- TTFB কী? অনুরোধ পাঠানোর পর প্রথম বাইট আসা পর্যন্ত সময়: এক RTT আর server-এর প্রস্তুতি।
১০ · AI-এর যুগে অনুরোধ ট্রেস করা
AI আপনার server বা Network ট্যাব দেখতে পায় না। “502 আসছে, ঠিক করে দাও” বললে সে অনুমান করবে। তাকে প্রমাণ দিন, আর উত্তর যাচাই করুন। ফুল-স্ট্যাকে AI-এর পরিচিত ভুলগুলো হলো:
| AI-এর ভুল | কেন বিপদ | যাচাই |
|---|---|---|
CORS ঠিক করতে mode: "no-cors" বা সব origin খুলে দেওয়া | উত্তর পড়াই যায় না, অথবা দরজা সবার জন্য খোলা | Fetch ডকুমেন্টেশন, Console |
catch-এ error গিলে 200 ফেরত | ব্যর্থতা লুকিয়ে যায়, monitoring কিছু দেখে না | ইচ্ছা করে ভেঙে status দেখা |
| পুরোনো framework API বা বানানো package | চলে না, বা অচেনা কোড ঢোকে | আপনার version-এর official docs, npm view |
| 500-এর body-তে stack trace, log-এ cookie বা token | গোপন তথ্য ফাঁস | Diff পড়া, curl দিয়ে উত্তর দেখা |
| Route আছে, কিন্তু authorization নেই | চলে, কিন্তু যে কেউ অন্যের তথ্য পড়ে | অন্য user হয়ে curl বা test |
ত্রুটি পেলে AI-কে অনুমান নয়, প্রমাণ দিন, আর স্তর ধরে ভাবতে বলুন। পাঠানোর আগে cookie, token আর .env-এর মান মুছে ফেলুন।
AI-এর পরামর্শ কাজে লাগানোর আগে
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১HTTP-কে stateless বানানো হলো কেন? কার্ট আর login তো সবারই লাগে।
শুরুর ওয়েব ছিল দলিল পড়ার ব্যবস্থা। একটি অনুরোধ, একটি দলিল, কাজ শেষ। Server-কে প্রত্যেক পাঠকের কথা মনে রাখতে হলে মেমরি লাগত, আর হারিয়ে যাওয়া সংযোগের হিসাবও রাখতে হতো। স্মৃতি না রাখায় server সরল থাকল। একটি সংযোগ হঠাৎ কেটে গেলেও কোনো অসমাপ্ত হিসাব পড়ে থাকত না, তাই ব্যর্থতা সামলানোও সহজ হলো।
তারপর এল কেনাকাটা। ১৯৯৪ সালে Netscape-এ Lou Montulli cookie-র ধারণা আনেন, যাতে অনলাইন কার্টের মতো কাজ করা যায়। কৌশলটি চমৎকার: প্রোটোকল বদলাতে হলো না, শুধু একটি header জুড়ল। পরে RFC 6265 (২০১১) এর নিয়ম গুছিয়ে লেখে।
২০০০ সালে Roy Fielding তাঁর dissertation-এ REST বর্ণনা করেন, আর stateless-কে একটি সচেতন শর্ত হিসেবে রাখেন। তাঁর যুক্তি: প্রতিটি অনুরোধে দরকারি সব তথ্য থাকলে যেকোনো server যেকোনো অনুরোধ সামলাতে পারে। ফলে load balancer অবাধে অনুরোধ ভাগ করতে পারে, একটি server মরে গেলে অন্যটি কাজ নেয়, আর cache করা সহজ হয়।
দামও আছে। প্রতিটি অনুরোধে cookie বা token বয়ে নিতে হয়, যা বাইট বাড়ায়। Session store নিজেই একটি নির্ভরতা হয়ে দাঁড়ায়: Redis বন্ধ হলে সবাই logout. Stateless token (যেমন JWT) store-এর ঝামেলা কমায়, কিন্তু logout আর প্রত্যাহার কঠিন করে তোলে। এই তর্ক পর্ব ৪৫ ও ৪৬-এ বিস্তারে আসবে।
মূল উপলব্ধি: Stateless মানে state নেই, তা নয়। মানে state-কে app server থেকে সরিয়ে ভাগ করা জায়গায় রাখা, যাতে app মেশিনগুলো বদলযোগ্য থাকে। Scaling-এর অনেকটা এই একটি সিদ্ধান্তের ফল।
প্র ০২গড় latency ভালো, তবু ব্যবহারকারী অভিযোগ করেন। p99 কেন এত গুরুত্বপূর্ণ?
গড় একটি সংখ্যায় পুরো বণ্টন চেপে ফেলে। কয়েকটি ভীষণ ধীর অনুরোধ গড়ে সামান্য দাগ রাখে, অথচ সেই মানুষগুলোর অভিজ্ঞতা খারাপ। আর একজন ব্যবহারকারী এক সেশনে অনেক অনুরোধ করেন। একটি পাতায় ২০টি অনুরোধ থাকলে, প্রতিটির ১% ধীর হওয়ার সম্ভাবনা ধরলে, অন্তত একটি ধীর হওয়ার সম্ভাবনা ১ − ০.৯৯২০ ≈ ১৮%. অর্থাৎ p99 সমস্যাটি প্রায় প্রতি পাঁচটি পাতায় একটিতে দেখা দেবে।
২০১৩ সালে Google-এর Jeffrey Dean ও Luiz André Barroso “The Tail at Scale” প্রবন্ধে এই বিষয়টি বিখ্যাত করেন। তাঁদের উদাহরণ: প্রতিটি server ১% ক্ষেত্রে ধীর হলে, আর একটি অনুরোধ ১০০টি server-এর উত্তরের জন্য অপেক্ষা করলে, ১ − ০.৯৯১০০ ≈ ৬৩% অনুরোধ ধীর হয়। সবচেয়ে ধীর অংশটিই পুরো অনুরোধের সময় ঠিক করে।
ধীর লেজের কারণ অনেক: garbage collection, cache miss, database-এর lock, ব্যস্ত সংযোগ-সারি, হঠাৎ একটি ভারী query. সব দূর করা যায় না। প্রবন্ধটি তাই সহনশীল কৌশলও দেখায়, যেমন একই অনুরোধ দ্বিতীয় server-এ পাঠানো, যদি প্রথমটি দেরি করে।
বাস্তবে দলগুলো p50, p95, p99 আলাদা করে দেখে। কিছু সীমাবদ্ধতাও মনে রাখা দরকার: অল্প traffic-এ p99 অস্থির, আর গড়ের মতো percentile সহজে যোগ করা যায় না। প্রতিটি স্তরের p99 যোগ করলে মোট p99 পাওয়া যায় না।
মূল উপলব্ধি: ব্যবহারকারী গড় অনুভব করেন না, নিজের ধীরতম মুহূর্তটি মনে রাখেন। তাই performance-এর লক্ষ্য percentile-এ লিখুন।
প্র ০৩Node.js নিজেই HTTP server. তাহলে সামনে reverse proxy রাখার দরকার কী?
ছোট প্রজেক্টে দরকার না-ও হতে পারে। কিন্তু production-এ proxy এমন অনেক কাজ এক জায়গায় করে, যা প্রতিটি app-এ লিখলে সমস্যা বাড়ে।
- TLS শেষ করা: certificate এক জায়গায় থাকে, app সহজ থাকে।
- ভার ভাগ ও health check: অসুস্থ app বাদ পড়ে, নতুন version ধীরে চালু করা যায়।
- ধীর client সামলানো: Proxy উত্তর buffer করলে app দ্রুত মুক্ত হয়।
- Static ফাইল, compression, limit: বড় request আর অতিরিক্ত অনুরোধ app-এ পৌঁছানোর আগেই আটকানো যায়।
ইতিহাসেও এর শিকড় আছে। ১৯৯৯ সালে Dan Kegel-এর “C10K” লেখা জিজ্ঞেস করে, একটি server কীভাবে ১০ হাজার সংযোগ সামলাবে। প্রতি সংযোগে একটি প্রক্রিয়া বা thread-এর পুরোনো ধরন তখন বিপদে পড়ছিল। Igor Sysoev-এর nginx (প্রকাশ ২০০৪) event-ভিত্তিক নকশায় এর উত্তর দেয়, আর অনেক ব্যবস্থায় ভারী app server-এর সামনে হালকা proxy বসানোর রীতি ছড়িয়ে পড়ে।
দামও আছে। প্রতিটি স্তর একটি অতিরিক্ত hop, একটি বাড়তি config, আর একটি নতুন ব্যর্থতার জায়গা। আপনার পাওয়া 502 আর 504 তো proxy থাকার কারণেই আসে। আবার proxy-র পেছনে app-কে client-এর আসল IP জানাতে X-Forwarded-For লাগে, আর এই header বিশ্বাস করতে হয় সাবধানে, কারণ client নিজেও এটি লিখে পাঠাতে পারে। Express-এ trust proxy সেটিং এই কারণেই আছে। Managed platform-এ এই স্তর অনেক সময় অদৃশ্য থাকে, তবু থাকে। বিস্তারিত: API Gateway ও Proxy.
মূল উপলব্ধি: প্রতিটি স্তর একটি দাম দিয়ে একটি ক্ষমতা কেনে। স্তর যোগ করার আগে জিজ্ঞেস করুন, এটি কোন সমস্যা সমাধান করছে, আর ভাঙলে কে ধরবে?
প্র ০৪CORS browser কেন প্রয়োগ করে, server কেন নয়? এটি কি API-কে রক্ষা করে?
প্রথমে ভুল ধারণাটি ভাঙি: CORS আপনার API-কে রক্ষা করে না। curl, Postman বা অন্য server থেকে যে কেউ API ডাকতে পারে। CORS রক্ষা করে browser-এ বসা ব্যবহারকারীকে।
দৃশ্যটি ভাবুন। রুনা “বাজারঘর”-এ login করা অবস্থায় একটি ক্ষতিকর সাইট খুললেন। সেই সাইটের JavaScript যদি রুনার cookie-সহ bazarghar.example-এ অনুরোধ করে উত্তর পড়তে পারত, তাহলে তাঁর ঠিকানা, order সব চুরি হতো। এটি ঠেকাতে Netscape Navigator-এর ১৯৯০-এর দশকের সংস্করণ থেকে same-origin policy চালু হয়: এক origin-এর script অন্য origin-এর উত্তর পড়তে পারে না।
কিন্তু আধুনিক অ্যাপে frontend আর API প্রায়ই আলাদা origin-এ থাকে। তাই দরকার হলো নিয়মটি নিয়ন্ত্রিতভাবে শিথিল করার উপায়। সেটিই CORS, যা W3C ২০১৪ সালে Recommendation করে, আর আজ এর সংজ্ঞা WHATWG Fetch Standard-এ। Server header দিয়ে বলে কোন origin অনুমোদিত, আর browser সেই নিয়ম মানে। Server প্রয়োগ করতে পারে না, কারণ উত্তর কে পড়ছে সেটি browser-ই জানে।
এখান থেকে একটি গুরুত্বপূর্ণ ফল আসে। সরল অনুরোধ আগেই server-এ পৌঁছে চলে যায়, যেমন আমাদের lab-এর log-এ দেখা গেছে। তাই তথ্য বদলানো অনুরোধের নিরাপত্তা CORS-এর ওপর ছেড়ে দেওয়া যায় না। সেজন্য আলাদা CSRF সুরক্ষা লাগে: CSRF ও Cookie. আর authorization সবসময় server-এ যাচাই করতে হয়।
মূল উপলব্ধি: CORS কোনো তালা নয়, browser-কে দেওয়া একটি অনুমতিপত্র। “CORS ঠিক করলাম” মানে API নিরাপদ হলো, এমন নয়।
প্র ০৫অনুরোধ ব্যর্থ হলে আবার চেষ্টা করলেই তো হয়। Retry আর timeout কীভাবে বিপদ ডেকে আনে?
Timeout না থাকলে একটি আটকে থাকা database সব সংযোগ দখল করে পুরো অ্যাপ থামিয়ে দিতে পারে। আবার timeout-এর মান ভুল হলেও বিপদ।
স্তরের ক্রম: ধরুন proxy-র timeout ৫ সেকেন্ড, কিন্তু app database-এর জন্য ৩০ সেকেন্ড অপেক্ষা করে। Proxy ৫ সেকেন্ডে 504 দিয়ে দিল, অথচ app আরও ২৫ সেকেন্ড অকারণে কাজ করে চলল। সাধারণ নিয়ম: ভেতরের স্তরের timeout বাইরের চেয়ে ছোট। অনেক ব্যবস্থায় deadline অনুরোধের সাথে পাঠানো হয়, যাতে প্রতিটি স্তর জানে কত সময় বাকি।
Retry-এর ঝড়: Server ধীর হচ্ছে, client তিনবার করে চেষ্টা করল। Browser, proxy আর app, তিন স্তরে তিনবার করে হলে একটি অনুরোধ ৩ × ৩ × ৩ = ২৭ গুণ হতে পারে। ব্যস্ত server আরও চাপে পড়ে, যাকে বলে cascading failure. Google-এর Site Reliability Engineering বই (২০১৬) এই বিপদ নিয়ে বিস্তারে লিখেছে। সমাধান: retry-র সীমা, একেকবার বেশি অপেক্ষা (exponential backoff), এলোমেলো দেরি (jitter), আর শুধু এক স্তরে retry.
দুবার order: সবচেয়ে বিপজ্জনক। Timeout মানে “কাজ হয়নি” নয়, মানে “উত্তর জানি না”। Lab-এর 504-তেও app পরে ঠিকই কাজ শেষ করত। POST /orders আবার পাঠালে দুটি order, দুবার টাকা কাটা হতে পারে। GET নিরাপদে retry করা যায়। POST-এ লাগে idempotency key, যা পর্ব ৩৩ আর ৬৫-এ আসবে।
মূল উপলব্ধি: Timeout একটি প্রশ্ন রেখে যায়: কাজটি হয়েছিল কি? ব্যবস্থার নকশা এমন হওয়া চাই যাতে দুবার পাঠালেও ফল একবারের মতো থাকে। বিস্তারিত: Resilience Patterns.
অনুশীলন
হিসাব, কমান্ড, আউটপুট অনুমান আর নকশার সমালোচনা: চারটি আলাদা কাজ।
-
Latency হিসাব: DNS 20 ms, RTT 30 ms, TLS 1.3, server-এর কাজ 90 ms (এর 70 ms database), উত্তর 30 KB, গতি 4 Mbps. (ক) প্রথম অনুরোধের মোট সময় কত? (খ) খোলা সংযোগে কত? (গ) Database অংশে cache বসানো হলো: hit ratio 80%, hit-এ 2 ms, miss-এ 70 ms. খোলা সংযোগে নতুন গড় কত?
Download = ৩০ × ৮ ÷ ৪ = ৬০ ms. Waiting = এক RTT + server = ৩০ + ৯০ = ১২০ ms.
(ক) ২০ (DNS) + ৩০ (TCP) + ৩০ (TLS) + ১২০ + ৬০ = ২৬০ ms.
(খ) ১২০ + ৬০ = ১৮০ ms.
(গ) গড় database সময় = ০.৮ × ২ + ০.২ × ৭০ = ১.৬ + ১৪ = ১৫.৬ ms. Server = ২০ + ১৫.৬ = ৩৫.৬ ms. মোট = ৩০ + ৩৫.৬ + ৬০ = ১২৫.৬ ms.
-
নিজের server-এ curl: বিভাগ ৩-এর server-টি
server.jsফাইলে রাখুন,listen(3000, "127.0.0.1")করুন, আর client অংশ বাদ দিন।node server.jsচালিয়ে অন্য terminal-এcurl -v "http://127.0.0.1:3000/api/products?category=dal"চালান। আউটপুটে method, path, status আরContent-Typeকোন লাইনে আছে, চিহ্নিত করুন। তারপরcurl -X POST -vচালিয়ে দেখুন, কী বদলাল?Method ও path আছে
> GET /api/products?category=dal HTTP/1.1লাইনে। Status আছে< HTTP/1.1 200 OKলাইনে।Content-Typeআছে<দিয়ে শুরু হওয়া header-এ। Body-তে"category":"dal"পাবেন।-X POST-এ>লাইনে method বদলে POST হয়, কিন্তু উত্তর একই থাকে, কারণ আমাদের serverreq.methodযাচাই করে না। এটি একটি শিক্ষা: সত্যিকারের router ভুল method-এ 404 বা 405 দেয়। শেষে Ctrl+C দিয়ে server বন্ধ করুন। -
আউটপুট অনুমান করুন: নিচের কোড কী ছাপবে? কোন লাইন ছাপবে না, আর সত্যিকারের server-এ এই ভুলের ফল কী হতো?
const chain = [ (next) => { console.log("A in"); next(); console.log("A out"); }, (next) => { console.log("B in"); console.log("B stop"); }, (next) => { console.log("C handler"); }, ]; const run = (i) => chain[i](() => run(i + 1)); run(0);A in B in B stop A outC handlerছাপবে না, কারণ Bnext()ডাকেনি। A-রnext()ফিরে আসার পরA outছাপে। সত্যিকারের server-এ B উত্তরও পাঠায়নি, তাই অনুরোধটি ঝুলে থাকবে। Client ঘুরতে থাকবে, আর proxy থাকলে তার সময়সীমা শেষে 504 আসবে। পথের কোনো স্তর হয়next()ডাকবে, নয় উত্তর পাঠাবে। -
নকশার সমালোচনা: একজন সহকর্মী AI-এর পরামর্শে CORS ত্রুটি “ঠিক” করলেন: frontend-এ
fetch(url, { mode: "no-cors", credentials: "include" }), আর server-এAccess-Control-Allow-Origin: *. কী কী সমস্যা আছে? আপনি কী করবেন?no-cors-এ উত্তর হয় opaque: JS status বা body পড়তে পারে না। ত্রুটি চলে গেছে, কিন্তু তথ্যও নেই।credentials: "include"-এর সাথে*চলে না; browser উত্তরটি আটকে দেয়। নির্দিষ্ট origin আরAccess-Control-Allow-Credentials: trueলাগে।- ব্যক্তিগত তথ্যের API-তে সব origin খুলে দেওয়া ভুল অভ্যাস।
ভালো পথ: Server-এ অনুমোদিত origin-এর একটি তালিকা, হুবহু মিল হলে সেই origin ফেরত, সাথে
Vary: Origin. অথবা frontend ও API-কে একই origin-এ রাখা, যেমন/apipath proxy-র মাধ্যমে পাঠানো। তথ্য বদলানো অনুরোধে CSRF সুরক্ষা আলাদা ভাবতে হবে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি নতুন API অনুরোধের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- DNS থেকে IP ঠিকানা পাওয়া
- TCP আর TLS সংযোগ তৈরি
- CDN হয়ে load balancer-এ পৌঁছানো
- Router মিলিয়ে middleware চালানো
- Controller, service আর database query
- JSON উত্তর ফিরে browser-এ render
RTT 40 ms, TLS 1.3 আর নতুন TCP সংযোগ। প্রথম HTTP অনুরোধ পাঠানো শুরু করার আগে শুধু সংযোগ তৈরিতে কত সময় যাবে?
- 40 ms
- 80 ms
- 120 ms
- 0 ms
একটি API থেকে 502 Bad Gateway এল, body-তে proxy-র সাদা লেখা, আর proxy log-এ ECONNREFUSED. সবচেয়ে সম্ভাব্য কারণ কোনটি?
- Database query ধীর
- URL-এর বানান ভুল
- পেছনের app বন্ধ বা ভুল port-এ
- CORS header নেই
Browser-এর Console-এ CORS ত্রুটি দেখা গেল, কিন্তু একই URL curl-এ 200 দিচ্ছে। কোন কথাটি ঠিক?
- Server উত্তর দিচ্ছে, কিন্তু browser JS-কে সেটি পড়তে দিচ্ছে না
- Server বন্ধ আছে
- DNS ভুল আছে
- Frontend-এ mode: no-cors দিলেই পুরো সমাধান হবে
একটি app-এর session ছিল এক server-এর মেমরিতে। দ্বিতীয় app server যোগ করার পর ব্যবহারকারীরা হঠাৎ logout হচ্ছেন। সবচেয়ে ভালো ব্যাখ্যা ও সমাধান কোনটি?
- Cookie নষ্ট হয়েছে, তাই cookie বন্ধ করা উচিত
- HTTP/2 চালু করলেই ঠিক হবে
- Timeout বাড়ালেই ঠিক হবে
- Session অন্য server-এর মেমরিতে; ভাগ করা store-এ রাখা দরকার
DevTools-এর Timing-এ একটি API অনুরোধে Waiting (TTFB) 900 ms আর Content Download 5 ms. প্রথম সন্দেহ কোথায় যাওয়া উচিত?
- উত্তরের আকার অনেক বড়
- Server-এর কাজ, যেমন ধীর query বা বাইরের API
- DNS lookup
- CSS parsing
৯০টি অনুরোধ 15 ms-এ আর ১০টি অনুরোধ 185 ms-এ শেষ হলো। গড় সময় কত?
- 100 ms
- 18.5 ms
- 32 ms
- 13.5 ms
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- পথ: browser → DNS → CDN → proxy → router → middleware → controller → service → database, তারপর ফেরা।
- HTTP বার্তা মানে method, path, header, body; উত্তরে status, header, body. Cookie একটি header.
- Latency ভাগ করে দেখুন: সংযোগ, পথ, server, download. গড়ের সাথে p99-ও দেখুন।
- HTTP stateless; state থাকে cookie, ভাগ করা store আর database-এ।
- ত্রুটিতে জিজ্ঞেস করুন: কে উত্তর দিল, কত দ্রুত দিল? তারপর এক হপ করে ভেতরে যান।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ০৩ · Rendering-এর মানচিত্র — CSR, SSR, SSG আর SPA, MPAপরবর্তী পর্বHTML কোথায় আর কখন তৈরি হয়।
- ফুল-স্ট্যাক ওয়েব ফ্রেমওয়ার্ক · কোর্স হোম৯৩টি পর্বপুরো কোর্সের রোডম্যাপ।
- Computer Networks · HTTPগভীরে যানপ্রোটোকলের ভেতরের নিয়ম।
- System Design · Load Balancingপাশের ট্র্যাকঅনুরোধ ভাগ করার কৌশল ও health check.
- System Design · CDN ও Edgeপাশের ট্র্যাককোন জিনিস কাছে রাখা যায়, আর কতক্ষণ।