সরাসরি পর্বে যান
পর্ব ৩৬ · ৬২-এর মধ্যে · মডিউল ৬

HTTP — request, response, header, cookie আর REST

HTTP/1.1 — URLs, Request & Response Messages, Methods, Status Codes, Headers, Persistent Connections, Cookies, CORS & REST

Taught in Bangla, this lesson covers HTTP/1.1: URL structure, the text format of requests and responses, safe and idempotent methods, status code classes, important headers, non-persistent versus persistent connections with RTT calculations, cookies and sessions, CORS, and REST API design, with hands-on examples.

এই পর্বে যা শিখবেন

  • একটি URL-এর প্রতিটি অংশ, percent-encoding, আর HTTP-র ইতিহাস ০.৯ থেকে আজকের RFC 9110/9112 পর্যন্ত
  • Request আর response-এর আসল বাইট — C-তে একটি ছোট server আর client দিয়ে নিজে দেখা
  • Method-এর safe/idempotent সারণি, প্রচলিত ১৯টি status code আর গুরুত্বপূর্ণ header-গুলো (chunked encoding-সহ)
  • Non-persistent, persistent, pipelined আর parallel সংযোগে page load-এর সময় — পরীক্ষার মতো পূর্ণ হিসাব
  • Cookie, session, CSRF ও CORS-এর ধারণা, আর একটি REST API নিজে নকশা করা

১ · ওয়েবের টুকরোগুলো — URL, HTTP আর তার জায়গা

Browser-এ https://www.example.com/notice?id=7 লিখে Enter চাপলেন। এক সেকেন্ডের মধ্যে অনেক কিছু ঘটে গেল: DNS নামটিকে IP ঠিকানায় বদলাল (পর্ব ৩৫), TCP three-way handshake হলো (পর্ব ২৯), TLS সংযোগটি encrypt করল (পর্ব ৪৮), আর শেষে browser একটি ছোট লেখা পাঠাল — “GET /notice?id=7”. এই শেষ কথোপকথনের ভাষাই HTTP. পর্ব ৩৪-এ application layer-এর client-server ধারণা দেখেছেন; এই পর্বে সেই ধারণার সবচেয়ে ব্যবহৃত protocol-টি খুঁটিয়ে দেখব।

URL — ঠিকানার প্রতিটি অংশ

ওয়েবে প্রতিটি জিনিস (পেজ, ছবি, ভিডিও, API-র ডেটা) একটি resource, আর তার ঠিকানা URL. মহল্লার বাড়ির ঠিকানার মতোই এর অংশগুলো বড় থেকে ছোটর দিকে যায়। একটি পূর্ণ উদাহরণ:

https://rafi@shop.example.com:8443/books/c-programming?lang=bn&page=2#reviews
অংশউদাহরণেকাজ
Schemehttpsকোন protocol-এ কথা হবে। http হলে ডিফল্ট port 80, https হলে 443.
Userinfo (ঐচ্ছিক)rafi@পুরোনো দিনের ব্যবহারকারী-নাম। Browser-রা এখন সতর্ক করে বা বাদ দেয়, কারণ প্রতারণায় ব্যবহার হতো; পাসওয়ার্ড URL-এ রাখা নিষিদ্ধ-প্রায়।
Hostshop.example.comকোন যন্ত্র — DNS নাম বা সরাসরি IP. বড়-ছোট হাতের অক্ষরে পার্থক্য নেই।
Port (ঐচ্ছিক):8443না লিখলে scheme-এর ডিফল্ট port.
Path/books/c-programmingServer-এর ভেতরে কোন resource. সাধারণত বড়-ছোট হাতের অক্ষর আলাদা ধরা হয় (server-এর ওপর নির্ভর করে)।
Query (ঐচ্ছিক)?lang=bn&page=2অতিরিক্ত প্রশ্ন, প্রথাগতভাবে key=value জোড়া & দিয়ে আলাদা (query string).
Fragment (ঐচ্ছিক)#reviewsপেজের ভেতরের একটি জায়গা। Browser এটি server-এ পাঠায়ই না — শুধু নিজে ব্যবহার করে।

Request-এর প্রথম লাইনে browser পাঠায় শুধু path আর query (/books/c-programming?lang=bn&page=2); host যায় আলাদা Host header-এ, আর fragment কোথাও যায় না। URL-এর সাধারণ নিয়ম RFC 3986-এ, আর browser-রা আজ WHATWG-এর URL Standard মেনে চলে — দুটির মধ্যে ছোটখাটো পার্থক্য আছে।

Percent-encoding — বিশেষ অক্ষর আর বাংলা

URL-এ কিছু অক্ষরের বিশেষ অর্থ আছে: / ? # & = :. আবার space বা বাংলা অক্ষর সরাসরি HTTP-র প্রথম লাইনে বসানো যায় না। সমাধান percent-encoding: অক্ষরটির UTF-8 বাইটগুলোকে % আর দুই hex অঙ্কে লেখা।

  • Space → %20; মানের ভেতরের & → %26, = → %3D. তাই a b&c=d হয় a%20b%26c%3Dd.
  • “ঢাকা” চারটি Unicode অক্ষর, UTF-8-এ প্রতিটি ৩ বাইট — মোট ১২ বাইট: %E0%A6%A2%E0%A6%BE%E0%A6%95%E0%A6%BE. Browser-এর ঠিকানার ঘরে আপনি বাংলা দেখেন, তারে যায় এই রূপ।
  • HTML form ডিফল্টে (application/x-www-form-urlencoded) space-কে + লেখে — URL-এর path-এ কিন্তু + মানে আসল যোগচিহ্ন। এই দুই নিয়ম গুলিয়ে ফেলা খুব প্রচলিত বাগ। Form কীভাবে ডেটা পাঠায় তা দেখতে পারেন HTML & CSS কোর্সের form পর্বে; JavaScript-এ encodeURIComponent() এই কাজ করে।

HTTP-র চারটি মৌলিক বৈশিষ্ট্য

Request–response

সবসময় client শুরু করে, server উত্তর দেয়। একটি request-এর একটি চূড়ান্ত response. Server নিজে থেকে কিছু পাঠাতে পারে না (সেজন্য WebSocket, পর্ব ৪০)।

client-server

TCP-র ওপর

HTTP/1.1 চলে TCP-এর নির্ভরযোগ্য byte stream-এর ওপর: সাধারণ HTTP-তে port 80, আর TLS-এর ভেতরে HTTPS-এ 443. বাইট হারানো বা ক্রম নিয়ে HTTP-কে ভাবতে হয় না।

80 / 443

Stateless

Server প্রতিটি request আলাদাভাবে বিচার করে; আগের request মনে রাখার কোনো নিয়ম protocol-এ নেই। মনে রাখার দরকার হলে cookie (অংশ ৭)।

প্রতিটি request স্বয়ংসম্পূর্ণ

লেখা-ভিত্তিক (১.১ পর্যন্ত)

HTTP/1.x-এর বার্তা মানুষ পড়তে পারে — ASCII লেখা। HTTP/2 আর HTTP/3 একই অর্থ রেখে বার্তাকে binary frame-এ পাঠায় (পর্ব ৩৭)।

পড়া যায়, debug সহজ

উপজেলা অফিসের রিসেপশনের কথা ভাবুন। প্রতিটি আবেদনপত্র নিজের মধ্যে সম্পূর্ণ: কী চাই, কার জন্য, সাথে কী কাগজ। রিসেপশনিস্ট আবেদন দেখে একটি সিল-মারা উত্তর দেন — “গৃহীত”, “অন্য টেবিলে যান”, “কাগজ অসম্পূর্ণ”, বা “আজ সার্ভার ডাউন”। পরের বার গেলে তিনি আপনাকে চেনেন না; চিনতে চাইলে আগের বার দেওয়া টোকেন নম্বর দেখাতে হয়। HTTP-র request হলো আবেদনপত্র, status code হলো সিল, আর টোকেন নম্বর হলো cookie.

১৯৮৯ সালে CERN-এ Tim Berners-Lee ওয়েবের প্রস্তাব দেন, আর ১৯৯০–৯১ সালে প্রথম browser আর server চালু হয়। প্রথম HTTP — পরে নাম হয় HTTP/0.9 — ছিল এক লাইনের: GET /page, কোনো header নেই, উত্তর শুধু HTML, তারপর সংযোগ বন্ধ। ১৯৯৬ সালে RFC 1945 প্রচলিত HTTP/1.0-কে লিখে রাখে: header, status code, Content-Type আর POST. HTTP/1.1 আসে ১৯৯৭ সালে RFC 2068-এ, সংশোধিত রূপে ১৯৯৯ সালে RFC 2616-এ — বাধ্যতামূলক Host, ডিফল্ট persistent সংযোগ, chunked encoding আর উন্নত caching নিয়ে। ২০১৪ সালে এটি কয়েকটি RFC-তে (7230–7235) ভাগ হয়, আর ২০২২ সালে বর্তমান রূপ পায়: RFC 9110 (সব সংস্করণের সাধারণ অর্থ — method, status, header), RFC 9111 (caching) আর RFC 9112 (HTTP/1.1-এর বার্তার গঠন)।

২ · HTTP বার্তার গঠন — request আর response, বাইট ধরে ধরে

Request আর response দুটিরই কাঠামো এক: (১) একটি start line, (২) শূন্য বা বেশি header লাইন — প্রতিটি Name: value, (৩) একটি ফাঁকা লাইন, যা বলে “header শেষ”, (৪) ঐচ্ছিক body. প্রতিটি লাইন শেষ হয় দুটি বাইটে — carriage return আর line feed, অর্থাৎ \r\n (ASCII 13 আর 10); একে বলে CRLF. Header-এর নাম বড়-ছোট হাতের অক্ষরে আলাদা নয়: Host, host, HOST একই।

Request: METHOD target HTTP/1.1 ⏎ header… ⏎ ⏎ [body]
Response: HTTP/1.1 code reason ⏎ header… ⏎ ⏎ [body]
⏎ = \r\n · ফাঁকা লাইন = শুধু \r\n

নিচের ডায়াগ্রামে একটি browser নোটিশ-বোর্ডের একটি পেজ চাইছে, আর server উত্তর দিচ্ছে। প্রতিটি ধাপে কোন লাইন তারে গেল, তার বাইট-সংখ্যা আর মোট কত বাইট হলো দেখুন। “পরে” চাপুন:

Browser → www.example.com (TCP সংযোগ আগেই তৈরি)
তারে যা গেল
অংশ
বাইট
মোট
  1. Request line. তিনটি অংশ, একটি করে space দিয়ে আলাদা: method GET, target /notice?id=7 (path আর query, host নয়), আর version HTTP/1.1. শেষে \r\n — মোট ২৭ বাইট।
  2. Host header. একই IP ঠিকানায় অনেক ওয়েবসাইট থাকতে পারে; কোনটি চাই, তা জানায় Host. HTTP/1.1-এ এটি ছাড়া request অবৈধ — server-কে 400 Bad Request দিতে হয়। এ পর্যন্ত ৫০ বাইট।
  3. বাকি header. User-Agent জানায় কোন সফটওয়্যার; Accept জানায় কোন ধরনের উত্তর চলবে; Accept-Encoding জানায় gzip বা Brotli (br)-এ সংকুচিত উত্তরও চলবে। প্রতিটি লাইন নিজের \r\n-সহ গোনা হয়েছে — মোট ১২১।
  4. ফাঁকা লাইন। শুধু \r\n — ২ বাইট। Server এখানেই বোঝে header শেষ। GET-এ body নেই, তাই request সম্পূর্ণ: ১২৩ বাইট। এই ফাঁকা লাইন না পাঠালে server অপেক্ষা করতেই থাকবে।
  5. Status line. Server-এর উত্তরের প্রথম লাইন: version, তিন অঙ্কের status code 200, আর মানুষের পড়ার জন্য reason phrase OK. প্রোগ্রাম শুধু সংখ্যাটি দেখে; reason phrase বদলালেও অর্থ বদলায় না। ১৭ বাইট।
  6. Response header. Content-Type বলে body কী ধরনের আর কোন অক্ষর-সংকেতে; Content-Length: 26 বলে body ঠিক ২৬ বাইট; Date উত্তরের সময়, সবসময় GMT-তে। এ পর্যন্ত ১১৫ বাইট।
  7. ফাঁকা লাইন আর body. আবার \r\n, তারপর ঠিক ২৬ বাইট body (শেষের \n-সহ)। Browser ২৬ বাইট গুনে নিয়েই বোঝে উত্তর শেষ — সংযোগ বন্ধ না করেই পরের request পাঠাতে পারে। মোট response ১৪৩ বাইট।

নিজের চোখে দেখা — C-তে একটি ছোট HTTP/1.1 server আর client

নিচের প্রোগ্রাম fork() দিয়ে নিজেকে দুই ভাগ করে। Child হলো একটি ছোট HTTP server (127.0.0.1-এর একটি port-এ): প্রতিটি request-এর প্রথম লাইন থেকে path পড়ে, header-এ Connection: close আছে কি না দেখে, তারপর Content-Length-সহ উত্তর দেয় — path / হলে 200, অন্য কিছু হলে 404. একটি পূর্ণ HTTP/1.1 server-কে আরও কয়েকটি যাচাই করতে হয়, যা জায়গা বাঁচাতে এখানে বাদ রাখা হয়েছে: Host header না থাকলে 400 Bad Request, আর অচেনা বা অসমর্থিত method এলে 405 Method Not Allowed (সাথে Allow header) — পর্ব ৪৫-এর প্রজেক্টে এগুলো যোগ করবেন। Parent হলো client: একই TCP সংযোগে দুটি request পাঠায় — প্রথমটিতে Connection: keep-alive, দ্বিতীয়টিতে Connection: close. সব কিছু ছাপে শুধু client, তাই আউটপুটের ক্রম নির্দিষ্ট; \r আর \n দৃশ্যমান করে ছাপা হয়েছে।

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <arpa/inet.h>
static void show(const char *who, const char *s) {  /* print bytes; make \r and \n visible */
    for (printf("%s\n    ", who); *s; s++)
        *s == '\r' ? fputs("\\r", stdout) : *s == '\n' ? fputs(s[1] ? "\\n\n    " : "\\n\n", stdout) : putchar(*s);
}
int main(void) {
    struct sockaddr_in a = {.sin_family = AF_INET, .sin_port = htons(47371), .sin_addr.s_addr = htonl(INADDR_LOOPBACK)};
    int ls = socket(AF_INET, SOCK_STREAM, 0); char q[512], p[64] = "", r[256]; ssize_t n;
    setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); fflush(stdout); /* re-run despite TIME-WAIT */
    if (bind(ls, (struct sockaddr *)&a, sizeof a) < 0 || listen(ls, 4) < 0) { perror("bind"); return 1; }
    if (fork() == 0) { int s = accept(ls, NULL, NULL); /* child = tiny HTTP/1.1 server */
        while ((n = read(s, q, sizeof q - 1)) > 0) { /* small requests: one read() each */
            q[n] = '\0'; sscanf(q, "%*s %63s", p);   /* request line: METHOD PATH VERSION */
            int ok = strcmp(p, "/") == 0, bye = strstr(q, "\r\nConnection: close\r\n") != NULL;
            const char *st = ok ? "200 OK" : "404 Not Found", *body = ok ? "Assalamu alaikum!\n" : "404 Not Found\n";
            int len = snprintf(r, sizeof r, "HTTP/1.1 %s\r\nContent-Type: text/plain\r\nContent-Length: %zu\r\n%s\r\n%s",
                               st, strlen(body), bye ? "Connection: close\r\n" : "", body);
            if (write(s, r, len) != len || bye) break;
        }
        close(s); _exit(0); }
    const char *req[2] = {"GET / HTTP/1.1\r\nHost: localhost:47371\r\nConnection: keep-alive\r\n\r\n",
                          "GET /fees HTTP/1.1\r\nHost: localhost:47371\r\nConnection: close\r\n\r\n"};
    int c = socket(AF_INET, SOCK_STREAM, 0);  if (connect(c, (struct sockaddr *)&a, sizeof a) < 0) { perror("connect"); return 1; }
    for (int i = 0; i < 2; i++) {                    /* both requests on ONE TCP connection */
        if (write(c, req[i], strlen(req[i])) < 0) return 1;
        n = read(c, q, sizeof q - 1); q[n > 0 ? n : 0] = '\0';
        printf("=== request %d on the same connection\n", i + 1); show(">>> client sent:", req[i]); show("<<< server replied:", q);
    }
    printf("=== read() after Connection: close returned %zd (server closed TCP)\n", read(c, q, sizeof q));
    close(c); wait(NULL); return 0;
}

আউটপুট:

=== request 1 on the same connection
>>> client sent:
    GET / HTTP/1.1\r\n
    Host: localhost:47371\r\n
    Connection: keep-alive\r\n
    \r\n
<<< server replied:
    HTTP/1.1 200 OK\r\n
    Content-Type: text/plain\r\n
    Content-Length: 18\r\n
    \r\n
    Assalamu alaikum!\n
=== request 2 on the same connection
>>> client sent:
    GET /fees HTTP/1.1\r\n
    Host: localhost:47371\r\n
    Connection: close\r\n
    \r\n
<<< server replied:
    HTTP/1.1 404 Not Found\r\n
    Content-Type: text/plain\r\n
    Content-Length: 14\r\n
    Connection: close\r\n
    \r\n
    404 Not Found\n
=== read() after Connection: close returned 0 (server closed TCP)

প্রথম request-এ ঠিক stepper-এর কাঠামো: request line, দুটি header, আর একা \r\n. Server উত্তর দিল 200 OK, Content-Length: 18 — “Assalamu alaikum!” ১৭টি অক্ষর আর শেষের \n মিলে ১৮ বাইট। Client ঠিক এতটুকু পড়ে থামল, সংযোগ খোলাই রইল। দ্বিতীয় request একই সংযোগে গেল (নতুন handshake ছাড়া) — path /fees server চেনে না, তাই 404 Not Found, body ১৪ বাইট। Client Connection: close বলেছিল, তাই server উত্তরেও তা জানিয়ে TCP বন্ধ করল; শেষ লাইনে read() = 0 মানে অন্য পক্ষের FIN এসেছে (পর্ব ২৯)। এই server খুব সরল — ধরে নেয় প্রতিটি request একটি read()-এ পুরো আসবে, যা loopback-এ ছোট বার্তার জন্য চলে, কিন্তু আসল নেটওয়ার্কে TCP বার্তার সীমানা রাখে না। ফাঁকা লাইন পর্যন্ত জমিয়ে পড়া, বড় body আর অনেক client সামলানো একটি সত্যিকারের server-এর কাজ — পর্ব ৪৫-এর প্রজেক্টে সেটাই বানাবেন। চালানোর পরিবেশে আগের চালানোর TIME-WAIT থাকলেও যেন bind() ব্যর্থ না হয়, সেজন্য SO_REUSEADDR.

৩ · Method — GET থেকে DELETE, আর safe বনাম idempotent

Request line-এর প্রথম শব্দ method — client কী করতে চায়। RFC 9110 প্রতিটি method-এর অর্থ নির্দিষ্ট করে দেয়, আর browser, proxy, cache সবাই সেই অর্থের ওপর ভরসা করে সিদ্ধান্ত নেয়। দুটি বৈশিষ্ট্য পরীক্ষায় সবচেয়ে বেশি আসে:

  • Safe: অনুরোধটি শুধু পড়ার — server-এর অবস্থা বদলানোর উদ্দেশ্য নেই। (Server log লিখলে বা গণনা বাড়ালে তা গোনা হয় না; ব্যাপারটা হলো client কী চেয়েছে।)
  • Idempotent: একই অনুরোধ একবার পাঠানো আর দশবার পাঠানোর চূড়ান্ত প্রভাব এক। প্রতিটি safe method idempotent, কিন্তু উল্টোটা সত্য নয়।
MethodকাজRequest bodySafeIdempotentউদাহরণ
GETResource-এর বর্তমান রূপ আনানা (অর্থহীন)✅✅GET /books/42
HEADGET-এর মতোই, কিন্তু শুধু header — body নয়না✅✅ফাইলের আকার বা পরিবর্তনের সময় দেখা
OPTIONSএই resource-এ কী কী করা যায় জানা (Allow header); CORS preflightসাধারণত না✅✅OPTIONS /books
POSTResource-কে ডেটা দিয়ে কিছু প্রক্রিয়া করানো — প্রায়ই নতুন কিছু তৈরিহ্যাঁ❌❌Form জমা, অর্ডার দেওয়া
PUTএই URL-এ পুরো resource বসানো বা বদলে দেওয়াহ্যাঁ❌✅PUT /books/42 পুরো বই-তথ্যসহ
PATCHResource-এর অংশবিশেষ বদলানো (RFC 5789)হ্যাঁ❌❌ (সাধারণভাবে)শুধু দাম বদলানো
DELETEResource মুছে ফেলাসাধারণত না❌✅DELETE /books/42

কেন PUT idempotent আর POST নয়? PUT /books/42 বলে “৪২ নম্বর বই হুবহু এটা হোক” — দশবার পাঠালেও শেষে একটিই বই, একই তথ্য। POST /orders বলে “একটি নতুন অর্ডার তৈরি করুন” — দশবার পাঠালে দশটি অর্ডার। DELETE-ও idempotent: প্রথমবার মুছে যায়, পরের বার হয়তো 404 আসে, কিন্তু server-এর অবস্থা একই (বইটি নেই)। PATCH নির্ভর করে লেখার ধরনের ওপর: “দাম ৫০০ করুন” idempotent, কিন্তু “স্টক ১ বাড়ান” নয়; তাই নিয়মে PATCH-কে idempotent ধরা হয় না।

মূল ধারণা

Idempotent হওয়ার আসল মূল্য নিরাপদে আবার চেষ্টা করা। ধরুন request পাঠানোর পর সংযোগ ভেঙে গেল, উত্তর এল না। Server কি কাজটা করেছে? জানার উপায় নেই। GET বা PUT হলে client নিশ্চিন্তে আবার পাঠায়। POST হলে আবার পাঠানো মানে হয়তো দুবার টাকা কাটা। তাই browser ব্যর্থ POST নিজে থেকে আবার পাঠায় না, আর POST-এর পর reload চাপলে “form আবার জমা দেবেন?” জিজ্ঞেস করে। পেমেন্ট API-গুলো (মোবাইল ব্যাংকিং বা কার্ড-পেমেন্ট) এজন্য সাধারণত প্রতিটি লেনদেনে একটি অনন্য অনুরোধ-নম্বর রাখার নিয়ম ব্যবহার করে (অনেক API-তে Idempotency-Key নামের header; এটি এখনো IETF-এর খসড়া পর্যায়ে) — একই নম্বরের দ্বিতীয় অনুরোধ এলে server নতুন লেনদেন না করে আগের ফল ফেরত দেয়।

GET বনাম POST — পরীক্ষার প্রিয় তুলনা

  • ডেটা কোথায়: GET-এ query string-এ (URL-এ দৃশ্যমান, history, bookmark আর server log-এ থেকে যায়); POST-এ body-তে।
  • Cache ও bookmark: GET-এর উত্তর cache করা আর link করা যায়; POST-এর সাধারণত নয়।
  • দৈর্ঘ্য: Protocol URL-এর কোনো সীমা দেয় না, কিন্তু browser আর server বাস্তবে কয়েক হাজার থেকে কয়েক দশ হাজার অক্ষরে থামায়; বড় ডেটা body-তে পাঠানোই নিয়ম।
  • “POST নিরাপদ” — ভুল ধারণা: TLS ছাড়া দুটিই তারে খোলা লেখা। POST শুধু URL আর log থেকে ডেটা সরিয়ে রাখে; গোপনীয়তা দেয় HTTPS. তবু পাসওয়ার্ড কখনো query string-এ নয়, কারণ log আর history-তে থেকে যায়।

৪ · Status code — পাঁচটি শ্রেণি আর কখন কোনটি

Response-এর তিন অঙ্কের status code-এর প্রথম অঙ্কই শ্রেণি বলে দেয়। Client কোনো নির্দিষ্ট কোড না চিনলেও প্রথম অঙ্ক দেখে আচরণ ঠিক করতে পারে — যেমন অচেনা 299-কে 200-এর মতো ধরা।

১xx · তথ্য

“পেয়েছি, চালিয়ে যান” — চূড়ান্ত উত্তরের আগের অস্থায়ী বার্তা (যেমন 100 Continue, 101 Switching Protocols).

অপেক্ষা করুন

২xx · সফল

অনুরোধ বোঝা গেছে আর সম্পন্ন হয়েছে।

গৃহীত

৩xx · অন্য জায়গায়

Redirect, বা “আপনার কাছের কপিই চলবে”।

অন্য টেবিলে যান

৪xx · client-এর ভুল · ৫xx · server-এর ভুল

৪xx: অনুরোধেই সমস্যা — অনুরোধ না বদলে আবার পাঠালে সাধারণত ফল বদলায় না (429-এর মতো কিছু ব্যতিক্রম আছে)। ৫xx: অনুরোধ ঠিক, server পারেনি — পরে চেষ্টা করলে চলতে পারে।

কার দোষ
কোডনামকখন হয়
200OKসফল; body-তে ফল (GET-এ resource, POST-এ কাজের ফল)।
201Createdনতুন resource তৈরি হয়েছে; Location header-এ তার URL. POST বা PUT-এর উত্তর।
204No Contentসফল, কিন্তু body নেই — যেমন DELETE বা সেটিং সেভের পর।
301Moved PermanentlyResource স্থায়ীভাবে নতুন URL-এ (Location); browser আর search engine মনে রাখে। পুরোনো client-রা POST-কে GET বানিয়ে ফেলতে পারে।
302Foundসাময়িকভাবে অন্য URL-এ; এখানেও বাস্তবে POST প্রায়ই GET হয়ে যায়।
304Not ModifiedConditional request-এর উত্তর: “আপনার কাছের কপিই ঠিক আছে” — body ছাড়া (অংশ ৫, বিস্তারিত পর্ব ৩৮)।
307Temporary Redirect302-এর মতো, কিন্তু method আর body বদলানো যাবে না — POST থাকলে POST-ই।
308Permanent Redirect301-এর মতো, method অপরিবর্তিত (RFC 7538, ২০১৫)।
400Bad Requestবার্তার গঠন ভুল — যেমন HTTP/1.1-এ Host নেই, বা JSON ভাঙা।
401Unauthorizedনাম “unauthorized” হলেও অর্থ পরিচয় দেওয়া হয়নি বা ভুল (unauthenticated); সাথে WWW-Authenticate header.
403Forbiddenপরিচয় জানা (বা দরকারই নেই), কিন্তু অনুমতি নেই; একই পরিচয়ে আবার চেষ্টা করে লাভ নেই।
404Not Foundএই URL-এ কিছু নেই (বা server জানাতে চায় না যে আছে)।
405Method Not AllowedResource আছে, কিন্তু এই method চলে না; Allow header-এ যা চলে তার তালিকা।
409ConflictResource-এর বর্তমান অবস্থার সাথে সংঘাত — যেমন একই username আগেই আছে, বা দুজন একসাথে একই রেকর্ড বদলাল।
429Too Many RequestsClient সীমার বেশি অনুরোধ পাঠাচ্ছে (rate limiting, RFC 6585); Retry-After বলে কতক্ষণ পরে।
500Internal Server ErrorServer-এর প্রোগ্রামে অপ্রত্যাশিত ত্রুটি — সাধারণত বাগ।
502Bad Gatewayমাঝের proxy বা gateway পেছনের server থেকে অবৈধ উত্তর পেয়েছে বা সংযোগই পায়নি।
503Service UnavailableServer সাময়িকভাবে অক্ষম — অতিরিক্ত চাপ বা রক্ষণাবেক্ষণ; Retry-After থাকতে পারে।
504Gateway Timeoutমাঝের proxy নির্দিষ্ট সময়ের মধ্যে পেছনের server-এর উত্তর পায়নি।

৫০২/৫০৩/৫০৪ বুঝতে মনে রাখুন বড় সাইটের গঠন: browser → load balancer বা reverse proxy (যেমন nginx) → পেছনের application server. Proxy যদি পেছনের server-এ সংযোগই না পায় বা আধা-ভাঙা উত্তর পায় — 502; সংযোগ পেয়েও সময়মতো উত্তর না পায় — 504; আর কোনো server নিজেই বলে “এখন পারছি না” — 503. পরীক্ষার ফল প্রকাশের দিন বোর্ডের সাইট খুলতে গিয়ে যে ত্রুটিগুলো দেখা যায়, সেগুলো প্রায়ই এই তিনটির কোনো একটি।

একটি প্রচলিত খারাপ অভ্যাস: সব উত্তরে 200 OK দিয়ে body-তে {"error": "not found"} লেখা। এতে cache, monitoring, retry-logic আর browser — সবাই ভুল বোঝে। ত্রুটি হলে ঠিক status code দিন, body-তে বিস্তারিত দিন।

৫ · গুরুত্বপূর্ণ header — Host থেকে Range

Host — একটি IP-তে অনেক ওয়েবসাইট

একটি server-এ একই IP ঠিকানায় শত শত ওয়েবসাইট রাখা যায়: shop.example.com, blog.example.com, আরও অনেক। TCP সংযোগ শুধু IP আর port জানে; কোন সাইট চাওয়া হলো, তা server জানে Host header থেকে। একে বলে virtual hosting. HTTP/1.0-এ Host বাধ্যতামূলক ছিল না, তাই প্রতিটি সাইটের জন্য আলাদা IP লাগত — IPv4 ঠিকানার সংকটের দিনে এ এক বড় অপচয়। HTTP/1.1 Host বাধ্যতামূলক করে, না থাকলে 400. HTTPS-এ একই সমস্যা TLS handshake-এর সময়ও আছে (সার্ভার কোন certificate দেখাবে?) — তার সমাধান SNI, পর্ব ৪৮-এ।

Content-Type — body কী ধরনের

Body-র ধরন জানায় Content-Type, যার মান একটি MIME type: text/html; charset=utf-8, application/json, image/png, form-এর জন্য application/x-www-form-urlencoded বা ফাইল আপলোডে multipart/form-data. Browser এই মান দেখেই ঠিক করে body-কে পেজ হিসেবে দেখাবে, ছবি হিসেবে আঁকবে না ডাউনলোড করবে। বাংলা লেখা ঠিকমতো দেখাতে charset=utf-8 জরুরি — না দিলে “ঢাকা”-র জায়গায় অদ্ভুত চিহ্ন দেখা যেতে পারে।

Body কোথায় শেষ — Content-Length বনাম chunked

TCP একটি সীমানাহীন byte stream; একই সংযোগে পরপর অনেক বার্তা গেলে প্রাপক জানবে কীভাবে একটি body কোথায় শেষ? HTTP/1.1-এ তিনটি উপায়:

  1. Content-Length: N — ঠিক N বাইট গুনে নিন। সহজ, কিন্তু পাঠানোর আগেই পুরো আকার জানতে হয়।
  2. Transfer-Encoding: chunked — body টুকরো টুকরো (chunk) যায়, প্রতিটির আগে তার আকার hex-এ, শেষে আকার 0-এর একটি chunk. ডেটা তৈরি হতে হতে পাঠানো যায় — যেমন ডেটাবেস থেকে একটি একটি সারি আসছে, বা বড় রিপোর্ট তৈরি হচ্ছে (chunked encoding).
  3. সংযোগ বন্ধ — HTTP/1.0-এর পুরোনো পথ: server body পাঠিয়ে TCP বন্ধ করে। এতে সংযোগ আর ব্যবহার করা যায় না, আর হঠাৎ ভাঙা সংযোগ আর বৈধ শেষের পার্থক্য বোঝা যায় না।

একটি chunked body তারে দেখতে এমন (প্রতিটি \r\n দেখানো হয়েছে):

6\r\n            ← পরের chunk ৬ বাইট
Hello \r\n
b\r\n            ← 0xb = ১১ বাইট
from Dhaka!\r\n
1a\r\n           ← 0x1a = ২৬ বাইট
 Sent in pieces, no length\r\n
0\r\n            ← শেষ chunk
\r\n

নিচের JavaScript parser একটি আস্ত response-এর লেখা নিয়ে status line, header (নাম ছোট হাতের অক্ষরে এনে, একই নামের header জোড়া দিয়ে) আর body আলাদা করে — chunked হলে প্রতিটি chunk খুলে জোড়া দেয়, নইলে Content-Length অনুযায়ী কাটে:

// HTTP/1.1 response parser: status line, case-insensitive headers, Content-Length or chunked body.
function parse(raw) {
  const end = raw.indexOf('\r\n\r\n');                  // blank line = end of headers
  const [statusLine, ...lines] = raw.slice(0, end).split('\r\n');
  const [, version, code, reason] = statusLine.match(/^HTTP\/(\d\.\d) (\d{3}) ?(.*)$/);
  const h = new Map();                                  // names are case-insensitive
  for (const l of lines) {
    const i = l.indexOf(':'), name = l.slice(0, i).trim().toLowerCase(), v = l.slice(i + 1).trim();
    h.set(name, h.has(name) ? h.get(name) + ', ' + v : v);
  }
  console.log(`status : HTTP/${version}  code=${code}  reason="${reason}"`);
  for (const [k, v] of h) console.log(`header : ${k.padEnd(18)} = ${v}`);
  let rest = raw.slice(end + 4), body = '';
  if (/chunked/i.test(h.get('transfer-encoding') || '')) {
    for (;;) {                                          // <hex size>\r\n<data>\r\n ... 0\r\n\r\n
      const nl = rest.indexOf('\r\n'), size = parseInt(rest.slice(0, nl), 16);
      console.log(`chunk  : size line "${rest.slice(0, nl)}" = ${size} bytes` +
        (size ? `  -> "${rest.substr(nl + 2, size)}"` : '  (last chunk)'));
      if (size === 0) break;
      body += rest.substr(nl + 2, size);
      rest = rest.slice(nl + 2 + size + 2);             // skip data and its trailing \r\n
    }
  } else body = rest.slice(0, Number(h.get('content-length') || 0));
  console.log(`body   : ${body.length} bytes "${body}"\n`);
}
parse('HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n' +
  'transfer-encoding: chunked\r\nVary: Accept-Encoding\r\nVARY: Accept-Language\r\n\r\n' +
  '6\r\nHello \r\nb\r\nfrom Dhaka!\r\n1a\r\n Sent in pieces, no length\r\n0\r\n\r\n');
parse('HTTP/1.1 301 Moved Permanently\r\nLocation: https://example.com/new\r\nContent-Length: 0\r\n\r\n');

আউটপুট:

status : HTTP/1.1  code=200  reason="OK"
header : content-type       = text/plain; charset=utf-8
header : transfer-encoding  = chunked
header : vary               = Accept-Encoding, Accept-Language
chunk  : size line "6" = 6 bytes  -> "Hello "
chunk  : size line "b" = 11 bytes  -> "from Dhaka!"
chunk  : size line "1a" = 26 bytes  -> " Sent in pieces, no length"
chunk  : size line "0" = 0 bytes  (last chunk)
body   : 43 bytes "Hello from Dhaka! Sent in pieces, no length"

status : HTTP/1.1  code=301  reason="Moved Permanently"
header : location           = https://example.com/new
header : content-length     = 0
body   : 0 bytes ""

প্রথম response-এ transfer-encoding ছোট হাতের অক্ষরে লেখা ছিল, আর Vary/VARY দুবার এসেছিল — parser নাম ছোট হাতের করে নিয়েছে বলে দুটি এক নামে মিলে কমা দিয়ে জোড়া লেগেছে, যা RFC 9110-এর নিয়ম (একই নামের একাধিক header মানে কমা-তালিকা; Set-Cookie এর ব্যতিক্রম, সেটি জোড়া দেওয়া যায় না)। Chunk-এর আকার b = ১১ আর 1a = ২৬ — hex, দশমিক নয়; ভুল করে দশমিক ধরলে body কেটে ভুল জায়গায় পড়বে। তিন chunk মিলে ৬ + ১১ + ২৬ = ৪৩ বাইট, যদিও কোনো Content-Length ছিল না। দ্বিতীয় response একটি redirect: body শূন্য বাইট, আর আসল তথ্য location header-এ।

Content negotiation — একই URL, আলাদা রূপ

একই resource-এর কয়েকটি রূপ থাকতে পারে — বাংলা বা ইংরেজি, সংকুচিত বা খোলা, HTML বা JSON. Client পছন্দ জানায়, server বাছাই করে (content negotiation):

Accept: text/html, application/json;q=0.9
Accept-Language: bn, en;q=0.8
Accept-Encoding: gzip, br

→ Content-Type: text/html; charset=utf-8
  Content-Language: bn
  Content-Encoding: br
  Vary: Accept-Encoding, Accept-Language

q হলো ০ থেকে ১-এর মধ্যে পছন্দের মাত্রা (না লিখলে ১)। Content-Encoding: br মানে body Brotli-তে সংকুচিত — লেখার মতো body প্রায়ই কয়েক গুণ ছোট হয়, যা ধীর মোবাইল সংযোগে বড় সুবিধা। Vary cache-কে সতর্ক করে: এই উত্তর ওই header-গুলোর ওপর নির্ভর করে, তাই অন্য Accept-Encoding-এর client-কে এই কপি দেওয়া যাবে না।

আরও কয়েকটি header — এক নজরে

Headerকে পাঠায়কাজ
User-AgentClientকোন browser বা প্রোগ্রাম (user agent); পরিসংখ্যান আর সামঞ্জস্যের জন্য। সহজেই নকল করা যায় — নিরাপত্তার সিদ্ধান্তে ব্যবহার অচল।
RefererClientকোন পেজ থেকে এই অনুরোধ এল। বানান ভুলটি (referrer-এর বদলে) ১৯৯০-এর দশকের মানদণ্ডেই থেকে গেছে। গোপনীয়তার জন্য Referrer-Policy দিয়ে সীমিত করা হয়।
LocationServer৩xx-এ কোথায় যেতে হবে; 201-এ নতুন resource-এর URL.
Connectionদুজনেইkeep-alive বা close — এই TCP সংযোগ পরের অনুরোধে থাকবে কি না। এটি শুধু পাশের hop-এর জন্য; proxy এটি সামনে পাঠায় না।
Range / Content-RangeClient / ServerRange: bytes=0-999 — শুধু প্রথম ১০০০ বাইট দিন। Server দেয় 206 Partial Content আর Content-Range: bytes 0-999/52000 (range request). ভাঙা ডাউনলোড আবার শুরু আর ভিডিওর মাঝখানে লাফ দেওয়া এভাবেই চলে।
Cache-Controlদুজনেইকে কতক্ষণ কপি রাখতে পারে: max-age=600, no-store, private — বিস্তারিত পর্ব ৩৮-এ।

Conditional request — “বদলেছে কি?”

Cache-এ রাখা পুরোনো কপি এখনো ঠিক আছে কি না যাচাই করতে client জিজ্ঞেস করে, পুরো জিনিস আবার না এনে (conditional request). প্রথম উত্তরে server দিয়েছিল Last-Modified সময় বা একটি ETag (সংস্করণের ছাপ, যেমন "v7-a1b2"). পরের বার client পাঠায় If-Modified-Since: … বা If-None-Match: "v7-a1b2". না বদলালে উত্তর 304 Not Modified — body ছাড়া, কয়েকশো বাইটে কাজ শেষ; বদলালে নতুন 200. কতক্ষণ যাচাই ছাড়াই কপি চলবে, proxy আর CDN কীভাবে cache করে — এগুলো পর্ব ৩৮-এর বিষয়।

৬ · সংযোগের হিসাব — non-persistent, persistent আর pipelining

একটি ওয়েবপেজ মানে একটি base HTML ফাইল আর তার ভেতরে উল্লেখ করা অনেক বস্তু (object) — ছবি, CSS, JavaScript, ফন্ট। প্রতিটির জন্য আলাদা HTTP request লাগে। প্রশ্ন হলো, এতগুলো request কয়টি TCP সংযোগে যাবে?

  • Non-persistent: প্রতিটি বস্তুর জন্য নতুন TCP সংযোগ — handshake, একটি request-response, তারপর বন্ধ। HTTP/1.0-এর ডিফল্ট।
  • Persistent (keep-alive): একটি সংযোগে একটির পর একটি request — আগেরটির উত্তর এলে পরেরটি। HTTP/1.1-এর ডিফল্ট; বন্ধ করতে চাইলে Connection: close.
  • Persistent + pipelining: উত্তরের অপেক্ষা না করে কয়েকটি request পরপর পাঠিয়ে দেওয়া; server উত্তর দেয় একই ক্রমে।
  • Parallel connection: একসাথে কয়েকটি TCP সংযোগ খুলে সমান্তরালে আনা। Browser-রা সাধারণত এক host-এ প্রায় ৬টি পর্যন্ত খোলে (এটি browser-এর নিজস্ব সীমা, মানদণ্ড নয়)।

ধরুন RTT = ১০০ ms, পেজে base HTML আর ২টি ছবি, আর প্রতিটি বস্তু পাঠানোর সময় (transmission) নগণ্য। নিচে তিন পদ্ধতিতে সময়রেখা পাশাপাশি:

RTT = 100 ms · base HTML + ২টি ছবি · transmission নগণ্য
সময়
Non-persistent
Persistent
Pipelining
  1. শুরু। তিন পদ্ধতিতেই প্রথম কাজ একই: TCP SYN পাঠানো। Base HTML পাওয়ার আগে browser জানেই না পেজে কী কী ছবি আছে।
  2. ১ RTT পরে। SYN+ACK ফিরে এসেছে — handshake-এর খরচ এক RTT (পর্ব ২৯)। তিনজনেই এখন GET / পাঠাল।
  3. ২ RTT পরে। HTML এসেছে, browser দুটি ছবির নাম জানল। এখানেই পথ আলাদা: non-persistent সংযোগ বন্ধ করে নতুন SYN পাঠাচ্ছে; persistent একই সংযোগে প্রথম ছবি চাইছে; pipelining দুটি ছবিই একসাথে চেয়ে বসেছে।
  4. ৩ RTT পরে। Pipelining শেষ — দুটি উত্তর পরপর এসে গেছে: মোট ৩ RTT = ৩০০ ms. Persistent পেয়েছে প্রথম ছবি, দ্বিতীয়টি চাইছে। Non-persistent কেবল দ্বিতীয় সংযোগের handshake শেষ করে ছবি চাইল।
  5. ৪ RTT পরে। Persistent শেষ — ৪০০ ms: base-এর জন্য ২ RTT, প্রতিটি ছবির জন্য ১ RTT. Non-persistent প্রথম ছবি পেয়ে আবার সংযোগ ভেঙে তৃতীয় SYN পাঠাচ্ছে।
  6. ৫ RTT পরে। Non-persistent-এর তৃতীয় সংযোগের handshake শেষ, শেষ ছবির GET গেল। প্রতিটি বস্তুর পেছনে আলাদা handshake-এর দাম দিতে হচ্ছে।
  7. ৬ RTT পরে। Non-persistent অবশেষে শেষ — ৬০০ ms, প্রতিটি বস্তুতে ২ RTT. একই পেজ, একই নেটওয়ার্ক — শুধু সংযোগ ব্যবহারের ধরনে সময় দ্বিগুণ। তার ওপর প্রতিটি নতুন TCP সংযোগ ছোট congestion window দিয়ে শুরু করে (slow start, পর্ব ৩২), তাই বড় বস্তুতে পার্থক্য আরও বাড়ে।

সূত্র — পরীক্ষার মতো করে

ধরা যাক base HTML-এর পর N-টি বস্তু, প্রতিটির transmission time T (base-এরও T), আর DNS ও TLS-এর সময় বাদ। একটি নতুন সংযোগে একটি বস্তু আনতে লাগে handshake-এর ১ RTT + request-response-এর ১ RTT + T. তাহলে:

Non-persistent, একটির পর একটি: (N + 1)(2·RTT + T)
Non-persistent, P-টি parallel: (2·RTT + T) + ⌈N/P⌉·2·RTT + N·T
Persistent, pipelining ছাড়া: (2·RTT + T) + N·(RTT + T)
Persistent, pipelining-সহ: (2·RTT + T) + RTT + N·T
T নগণ্য হলে: 2(N+1)·RTT · 2(1 + ⌈N/P⌉)·RTT · (N+2)·RTT · 3·RTT

যুক্তিটা ছোট: non-persistent-এ প্রতিটি বস্তু নিজের handshake আর নিজের request মিলে ২ RTT নেয়। Persistent-এ handshake একবারই, তাই base-এর পর প্রতি বস্তুতে শুধু request-response-এর ১ RTT. Pipelining-এ সব request একসাথে যায়, প্রথম উত্তর আসে ১ RTT পরে, বাকিগুলো তার পিছু পিছু — তাই N-টি বস্তুর জন্য মোট একটি RTT আর N·T. Parallel-এ প্রতি দফায় P-টি সংযোগ একসাথে চলে, কিন্তু একই link ভাগ করে বলে মোট transmission time N·T-ই থাকে।

পূর্ণ উদাহরণ: RTT = ১০০ ms, N = ১০, T নগণ্য, parallel P = ৫. Non-persistent: ১১ × ২০০ = ২২০০ ms. Parallel: ২০০ + ২ × ২০০ = ৬০০ ms. Persistent: ২০০ + ১০ × ১০০ = ১২০০ ms. Pipelining: ২০০ + ১০০ = ৩০০ ms. নিচের হিসাবযন্ত্র এই চারটি সূত্রই চালায়, transmission time-সহও:

// Page load time: base HTML + N small objects. RTT and per-object transmission time T in ms.
// Ignores DNS and TLS; a TCP handshake costs 1 RTT, a request/response costs 1 RTT + T.
function pageLoad(RTT, N, T, P) {
  const base = 2 * RTT + T;                        // handshake + request/response for base HTML
  const rows = [
    ['non-persistent, serial', base + N * (2 * RTT + T)],
    [`non-persistent, ${P} parallel`, base + Math.ceil(N / P) * 2 * RTT + N * T],
    ['persistent, no pipelining', base + N * (RTT + T)],
    ['persistent, pipelining', base + RTT + N * T],
  ];
  console.log(`RTT=${RTT} ms, N=${N} objects, T=${T} ms/object, P=${P}`);
  for (const [name, ms] of rows) console.log(`  ${name.padEnd(28)} ${String(ms).padStart(5)} ms`);
}
pageLoad(100, 10, 0, 5);        // classic textbook case: transmission time ignored
pageLoad(100, 10, 10, 5);       // each object also needs 10 ms on the link
pageLoad(40, 30, 2, 6);         // a nearby server (e.g. a CDN), more objects

আউটপুট:

RTT=100 ms, N=10 objects, T=0 ms/object, P=5
  non-persistent, serial        2200 ms
  non-persistent, 5 parallel     600 ms
  persistent, no pipelining     1200 ms
  persistent, pipelining         300 ms
RTT=100 ms, N=10 objects, T=10 ms/object, P=5
  non-persistent, serial        2310 ms
  non-persistent, 5 parallel     710 ms
  persistent, no pipelining     1310 ms
  persistent, pipelining         410 ms
RTT=40 ms, N=30 objects, T=2 ms/object, P=6
  non-persistent, serial        2542 ms
  non-persistent, 6 parallel     542 ms
  persistent, no pipelining     1342 ms
  persistent, pipelining         182 ms

প্রথম সারিগুলো হাতে করা হিসাবের সাথে হুবহু মেলে: ২২০০, ৬০০, ১২০০, ৩০০ ms. দ্বিতীয় দফায় প্রতিটি বস্তুর ১০ ms transmission যোগ হলো: non-persistent-এ ১১টি বস্তু × ২১০ = ২৩১০; persistent-এ ২১০ + ১০ × ১১০ = ১৩১০; pipelining-এ ২১০ + ১০০ + ১০০ = ৪১০. লক্ষ করুন, parallel-এ ৭১০ — ৬০০-র ওপর ১০টি বস্তুর ১০০ ms, কারণ সংযোগ যতই হোক, link একটাই। তৃতীয় দফা কাছের server-এর (যেমন দেশের ভেতরের CDN-এর) মতো: RTT ৪০ ms হলেও ৩০টি বস্তুতে non-persistent লাগে ২৫৪২ ms, pipelining মাত্র ১৮২. শিক্ষা দুটি: RTT-এর সংখ্যা কমানো সবচেয়ে বড় লাভ, আর বস্তু যত বেশি, সংযোগ বারবার ব্যবহারের গুরুত্ব তত বেশি।

Pipelining কেন বাস্তবে টিকল না — head-of-line blocking

Pipelining-এ server-কে উত্তর দিতে হয় request-এর ক্রমেই, কারণ HTTP/1.1-এর উত্তরে কোনো “এটি কোন request-এর উত্তর” নম্বর নেই। এখন ধরুন প্রথম request একটি ধীর ডেটাবেস-রিপোর্ট (৩ সেকেন্ড), পরের দুটি ছোট আইকন (প্রতিটি ১ ms). আইকন দুটি তৈরি থাকলেও রিপোর্ট না যাওয়া পর্যন্ত আটকে থাকে — লাইনের মাথার একজনের জন্য পুরো লাইন দাঁড়িয়ে, ঠিক ব্যাংকের এক কাউন্টারে একজনের জটিল কাজের জন্য পেছনের সবার অপেক্ষার মতো। একে বলে head-of-line blocking. তার ওপর অনেক পুরোনো proxy আর server pipelining ভুলভাবে সামলাত, ফলে মাঝেমধ্যে উত্তর ওলট-পালট হতো। তাই বড় browser-গুলো pipelining ডিফল্টে কখনো চালু রাখেনি বা পরে বাদ দিয়েছে; বদলে প্রতি host-এ প্রায় ৬টি parallel সংযোগ খুলেছে। আসল সমাধান এসেছে HTTP/2-এর multiplexing-এ — একটি সংযোগে অনেক request-এর উত্তর মিশিয়ে পাঠানো, যেকোনো ক্রমে (পর্ব ৩৭)।

৭ · Cookie আর session — stateless HTTP-তে মনে রাখা

HTTP stateless, অথচ অনলাইন শপিংয়ের ঝুড়ি, লগইন করা অবস্থা, বাংলা/ইংরেজি পছন্দ — সবই মনে রাখতে হয়। সমাধান cookie: server উত্তরে একটি Set-Cookie header দেয়, browser সেটি জমিয়ে রাখে, আর পরে ওই সাইটের প্রতিটি request-এ Cookie header-এ ফেরত পাঠায়।

পাড়ার লন্ড্রির দোকানে কাপড় দিলে একটি রসিদ পান — “নম্বর ৪৭”। কাপড় থাকে দোকানে, আপনার হাতে শুধু নম্বর। পরের বার নম্বর দেখালেই দোকানি খাতা খুলে আপনার কাপড় বের করেন। ওয়েবের session ঠিক এমন: আসল তথ্য (কে লগইন, ঝুড়িতে কী) থাকে server-এর খাতায় (session store), আর browser-এর cookie-তে থাকে শুধু একটি লম্বা, অনুমান-অযোগ্য session ID. রসিদের নম্বর কেউ চুরি করলে কাপড়ও তুলে নিতে পারে — তাই এই নম্বর রক্ষা করাই cookie নিরাপত্তার মূল কথা।

Browser ⇄ https://shop.example.com
তারে যা গেল
Browser-এর cookie jar
Server-এর session store
  1. Form আনা। সাধারণ GET. এখনো কোনো cookie নেই, server-এর কাছে এই browser অচেনা।
  2. লগইন জমা। Form-এর তথ্য POST-এর body-তে যায় (URL-এ নয়)। পুরো সংযোগ HTTPS-এ, তাই পাসওয়ার্ড তারে খোলা থাকে না। Server সংরক্ষিত password hash-এর সাথে মেলায়।
  3. Set-Cookie. মিলে গেছে। Server একটি এলোমেলো session ID বানাল, নিজের খাতায় লিখল “এই ID = রাফি”, আর উত্তরে cookie পাঠাল। 303 See Other browser-কে বলে “এবার GET দিয়ে /dashboard-এ যান” — এতে reload চাপলে POST আবার যায় না (Post/Redirect/Get নকশা)।
  4. Cookie ফেরত। এর পর shop.example.com-এর প্রতিটি request-এ browser নিজে থেকে Cookie: sid=… যোগ করে। Server ID দেখে খাতা থেকে বোঝে এটা রাফি। HTTP নিজে stateless রইল — state থাকল server-এর খাতায়, আর তার চাবি cookie-তে।
  5. Logout. Server খাতা থেকে session মুছল আর Max-Age=0 দিয়ে cookie-টিও মুছতে বলল। শুধু cookie মুছলে যথেষ্ট নয় — কেউ আগেই ID চুরি করে থাকলে server-এর খাতায় সেটি তখনো বৈধ থাকত।

Cookie-র attribute — প্রতিটি কেন আছে

Attributeঅর্থকী থেকে রক্ষা করে
Expires / Max-Ageকখন মেয়াদ শেষ (নির্দিষ্ট সময় / এখন থেকে কত সেকেন্ড)। দুটি থাকলে Max-Age জেতে। কোনোটি না থাকলে “session cookie” — browser বন্ধ হলে সাধারণত মুছে যায়।চুরি-হওয়া ID-র আয়ু সীমিত রাখা
Domainনা দিলে শুধু ঠিক এই host (host-only). Domain=example.com দিলে সব subdomain-ও পায়।দেওয়া মানে বেশি জায়গায় ছড়ানো — দরকার না হলে বাদ দেওয়াই নিরাপদ
Pathকোন path-এর নিচে পাঠানো হবে, যেমন /cart.শুধু সুবিধা — নিরাপত্তার সীমা হিসেবে ভরসাযোগ্য নয়
Secureশুধু HTTPS-এ পাঠানো হবে।খোলা Wi-Fi-তে তার থেকে cookie পড়ে নেওয়া
HttpOnlyPage-এর JavaScript (document.cookie) পড়তে পারে না; শুধু HTTP request-এ যায়।XSS-এ ঢোকানো script দিয়ে session ID চুরি
SameSiteStrict: অন্য সাইট থেকে শুরু হওয়া কোনো request-এ নয়। Lax: অন্য সাইট থেকে শুধু উপরের-স্তরের GET navigation-এ (link চাপা)। None: সবখানে — কিন্তু Secure বাধ্যতামূলক (SameSite).CSRF, আর তৃতীয় পক্ষের ট্র্যাকিং

নিচের simulator একটি browser-এর cookie jar-এর মতো আচরণ করে: https://shop.example.com/login-এর উত্তরে আসা আটটি Set-Cookie যাচাই করে রাখে বা বাতিল করে, তারপর বিভিন্ন সময়ে বিভিন্ন URL-এ request গেলে কোন cookie যাবে তা ঠিক করে — domain, path, Secure, মেয়াদ আর SameSite মিলিয়ে। এখানে “site” সরলভাবে host-এর শেষ দুই অংশ (example.com); আসল browser Public Suffix List দেখে, scheme-ও তুলনা করে।

// Cookie jar (simplified RFC 6265 + SameSite). site = last two labels; clock t=0 is 2026-10-26.
const site = h => h.split('.').slice(-2).join('.'), jar = [], T0 = Date.UTC(2026, 9, 26);
const inDomain = (host, c) => host === c.domain || (!c.hostOnly && host.endsWith('.' + c.domain));
function setCookie(url, header, now) {
  const u = new URL(url), [nv, ...attrs] = header.split(';').map(s => s.trim());
  const c = { name: nv.split('=')[0], domain: u.hostname, hostOnly: true, path: '/', secure: false, sameSite: 'Lax', exp: Infinity };
  for (const a of attrs) {
    const [k, v = ''] = a.split('='), key = k.toLowerCase();
    if (key === 'domain') Object.assign(c, { domain: v.replace(/^\./, ''), hostOnly: false });
    if (key === 'path') c.path = v;
    if (key === 'secure') c.secure = true; else if (key === 'samesite') c.sameSite = v;
    if (key === 'max-age') c.exp = now + Number(v);                     // Max-Age beats Expires
    if (key === 'expires' && c.exp === Infinity) c.exp = (Date.parse(v) - T0) / 1000;
  }
  const bad = !inDomain(u.hostname, { ...c, hostOnly: false }) ? 'Domain does not cover this host'
    : c.sameSite === 'None' && !c.secure ? 'SameSite=None needs Secure' : c.exp <= now ? 'expired (deletes old copy)' : '';
  console.log(`set ${c.name.padEnd(5)} ${bad ? 'REJECTED: ' + bad : `ok (${c.hostOnly ? 'host-only' : 'domain ' + c.domain}, path ${c.path}, ${c.sameSite})`}`);
  if (!bad) jar.push(c); }
function send(url, now, from, how) {            // from = the page that started this request
  const u = new URL(url), cross = site(new URL(from).hostname) !== site(u.hostname);
  const ok = jar.filter(c => now < c.exp && inDomain(u.hostname, c) && (!c.secure || u.protocol === 'https:')
    && (u.pathname === c.path || u.pathname.startsWith(c.path.replace(/\/?$/, '/')))
    && (!cross || c.sameSite === 'None' || (c.sameSite === 'Lax' && how === 'top-level GET')));
  console.log(`t=${String(now).padStart(4)}s ${url.padEnd(34)} ${cross ? 'cross-site ' + how : 'same-site'}` +
    `\n        Cookie: ${ok.map(c => c.name).join('; ') || '(none)'}`);
}
const L = 'https://shop.example.com/login';
['sid=a1; Path=/; Secure; HttpOnly; SameSite=Lax', 'lang=bn; Domain=example.com; Max-Age=3600',
 'cart=3; Path=/cart; Max-Age=60', 'pref=dark; Secure; SameSite=Strict', 'ad=u7; Domain=example.com; Secure; SameSite=None',
 'bad=1; SameSite=None', 'evil=1; Domain=example.org', 'old=1; Expires=Thu, 01 Jan 2015 00:00:00 GMT'].forEach(h => setCookie(L, h, 0));
send('https://shop.example.com/cart/view', 10, L, 'fetch');  send('http://shop.example.com/cart', 20, 'http://shop.example.com/', 'link');
send('https://blog.example.com/', 30, L, 'link');  send('https://shop.example.com/', 120, 'https://news.example.net/', 'top-level GET');
send('https://shop.example.com/cart', 130, 'https://news.example.net/', 'POST form');  send('https://blog.example.com/', 4000, 'https://blog.example.com/', 'link');

আউটপুট:

set sid   ok (host-only, path /, Lax)
set lang  ok (domain example.com, path /, Lax)
set cart  ok (host-only, path /cart, Lax)
set pref  ok (host-only, path /, Strict)
set ad    ok (domain example.com, path /, None)
set bad   REJECTED: SameSite=None needs Secure
set evil  REJECTED: Domain does not cover this host
set old   REJECTED: expired (deletes old copy)
t=  10s https://shop.example.com/cart/view same-site
        Cookie: sid; lang; cart; pref; ad
t=  20s http://shop.example.com/cart       same-site
        Cookie: lang; cart
t=  30s https://blog.example.com/          same-site
        Cookie: lang; ad
t= 120s https://shop.example.com/          cross-site top-level GET
        Cookie: sid; lang; ad
t= 130s https://shop.example.com/cart      cross-site POST form
        Cookie: ad
t=4000s https://blog.example.com/          same-site
        Cookie: ad

প্রথমে বাতিলের তিন কারণ: bad-এ SameSite=None আছে কিন্তু Secure নেই — আধুনিক browser এমন cookie নেয় না; evil একটি অন্য domain (example.org)-এর নামে cookie বসাতে চেয়েছে — কোনো সাইট অন্যের cookie লিখতে পারে না; old-এর মেয়াদ ২০১৫ সালেই শেষ, এভাবেই সাইটগুলো cookie মোছে। এবার request-গুলো: t=10-এ নিজের সাইটে HTTPS-এ /cart/view — পাঁচটিই যায়, কারণ /cart/view পড়ে /cart-এর নিচে। t=20-এ একই host কিন্তু http — Secure চিহ্নিত sid, pref, ad যায়নি, গেছে শুধু lang আর cart; HTTPS না থাকলে কী কী তারে খোলা যেত, তা ভাবুন। t=30-এ blog.example.com: host-only sid যায় না, Domain=example.com থাকা lang আর ad যায়। t=120-এ news.example.net-এর একটি link থেকে আসা — cross-site, কিন্তু উপরের-স্তরের GET, তাই Lax-এর sid আর lang যায়, Strict-এর pref যায় না; ততক্ষণে cart-এর ৬০ সেকেন্ড শেষ। t=130-এ অন্য সাইটের form থেকে POST — শুধু SameSite=None-এর ad যায়; sid না যাওয়ায় অন্য সাইট রাফির নামে অর্ডার দিতে পারে না। শেষে t=4000-এ lang-এর এক ঘণ্টা (৩৬০০ সেকেন্ড) শেষ, টিকে আছে শুধু ad.

SameSite না লিখলে কী হয়, তা browser-ভেদে আলাদা। Chrome ২০২০ সালের দিকে SameSite না-থাকা cookie-কে Lax ধরা শুরু করে (ওপরের simulator-ও তা-ই করে), কিন্তু অন্য কিছু browser একই ডিফল্ট নেয়নি বলে জানা যায়। তাই নিরাপদ নিয়ম: SameSite সবসময় নিজে লিখে দিন।

তৃতীয় পক্ষের cookie আর গোপনীয়তা

একটি খবরের সাইটে ads.example.net-এর একটি বিজ্ঞাপন বা “Like” বোতাম বসানো থাকলে browser সেই তৃতীয় host-এও request পাঠায়, আর তার cookie-ও যায় (SameSite=None হলে)। একই বিজ্ঞাপন-সংস্থা হাজার সাইটে বসানো থাকলে সে একই cookie দেখে বুঝতে পারে আপনি কোন কোন সাইটে গেছেন — এটাই third-party cookie-ভিত্তিক ট্র্যাকিং। প্রতিক্রিয়ায় Safari ২০২০ সাল নাগাদ তৃতীয় পক্ষের cookie ডিফল্টে পুরো বন্ধ করে, Firefox ২০২২ সাল থেকে ডিফল্টে প্রতিটি সাইটের জন্য আলাদা cookie-খোপ (Total Cookie Protection) চালু করে। Chrome বহু বছর ধরে এগুলো তুলে দেওয়ার পরিকল্পনা ঘোষণা আর বারবার পিছিয়েছে, আর ২০২৪–২৫ সালে জানিয়েছে আপাতত পুরো বন্ধ করছে না — এই ক্ষেত্রটি দ্রুত বদলায়, তাই সর্বশেষ অবস্থা browser-এর নিজের ঘোষণা থেকে দেখে নিন।

৮ · পরিচয় আর সুরক্ষা — authentication, CSRF আর CORS

Authentication-এর দুটি প্রচলিত header

  • Basic: Authorization: Basic cmFmaTpzZWNyZXQ= — এটি rafi:secret-এর Base64 রূপ। Base64 কোনো encryption নয়, যে কেউ এক লাইনে উল্টে পড়তে পারে। তাই Basic চলে শুধু HTTPS-এর ভেতরে, আর সাধারণত অভ্যন্তরীণ টুল বা ছোট API-তে। পরিচয় না দিলে server দেয় 401 আর WWW-Authenticate: Basic realm="…".
  • Bearer token: Authorization: Bearer eyJhbGciOi… — লগইনের পর পাওয়া একটি token; যার হাতে আছে (bearer), তাকেই বিশ্বাস করা হয় (OAuth 2.0-এর জন্য RFC 6750). Mobile app আর API-তে বহুল ব্যবহৃত। Token-ও session ID-র মতোই চাবি: HTTPS-এ পাঠাতে হয়, স্বল্প মেয়াদ দিতে হয়, log-এ লিখতে নেই।

Cookie browser নিজে থেকে পাঠায়; Authorization header পাঠায় আপনার কোড। এই পার্থক্যের ফলেই পরের আক্রমণটি cookie-র বেলায় ঘটে, Bearer token-এর বেলায় সাধারণত নয়।

CSRF — আপনার নামে অন্য সাইটের অনুরোধ

ধরুন আপনি ব্যাংকের সাইটে লগইন অবস্থায় আছেন, আর অন্য ট্যাবে একটি অচেনা সাইট খুললেন। সেই সাইটের পেজে লুকানো একটি form আছে, যা নিজে নিজে আপনার ব্যাংকের “টাকা পাঠান” URL-এ POST জমা দেয়। Browser নিয়মমতো ব্যাংকের cookie জুড়ে দিত — ব্যাংক ভাবত অনুরোধ আপনিই করেছেন। এটাই Cross-Site Request Forgery; এখানে শুধু ধারণাটি, আক্রমণের কৌশল নয়। প্রতিরক্ষা কয়েক স্তরে:

  • Session cookie-তে SameSite=Lax বা Strict — অন্য সাইট থেকে আসা POST-এ cookie যায়ই না (simulator-এর t=130).
  • অবস্থা-বদলানো প্রতিটি form-এ একটি গোপন, অনুমান-অযোগ্য CSRF token, যা অন্য সাইট জানে না; server মিলিয়ে দেখে।
  • Server-এ Origin বা Sec-Fetch-Site header দেখে অন্য সাইটের অনুরোধ বাতিল।
  • GET দিয়ে কখনো কিছু বদলানো নয় — safe method-এর নিয়ম মানাই প্রথম প্রতিরক্ষা।

আর session চুরির বিরুদ্ধে: HTTPS সবখানে (Secure cookie, সাথে HSTS — পর্ব ৪৮), HttpOnly যাতে XSS-এর script cookie পড়তে না পারে, লগইনের পর নতুন session ID দেওয়া, আর logout-এ server-এর দিক থেকেও session মোছা।

CORS — browser-এর “অন্যের চিঠি পড়বেন না” নিয়মে ছাড়

Browser-এর একটি মৌলিক নিয়ম same-origin policy: একটি পেজের JavaScript অন্য origin-এর উত্তর পড়তে পারে না। Origin = scheme + host + port; তাই https://shop.example.com আর https://api.example.com আলাদা origin, http:// আর https://-ও আলাদা। নইলে যেকোনো সাইটের script আপনার লগইন-করা ইমেইল বা ব্যাংকের পেজ পড়ে ফেলতে পারত।

কিন্তু নিজের frontend থেকে নিজের আলাদা API-তে fetch() করা তো বৈধ প্রয়োজন। তখন CORS (Cross-Origin Resource Sharing): API server উত্তরে জানায় Access-Control-Allow-Origin: https://shop.example.com — “এই origin আমার উত্তর পড়তে পারে”। JSON পাঠানো বা বিশেষ header-যুক্ত অনুরোধের আগে browser নিজে একটি OPTIONS “preflight” request পাঠিয়ে অনুমতি জেনে নেয়।

CORS server-কে রক্ষা করে না — এটি browser-এর একটি নিষেধ, যা server চাইলে শিথিল করে। curl বা অন্য কোনো server CORS মানেই না। তাই API-র আসল সুরক্ষা authentication আর authorization; CORS error “ঠিক” করতে Access-Control-Allow-Origin: * দিয়ে সবকিছু খুলে দেওয়া, বিশেষ করে cookie-নির্ভর API-তে, একটি বিপজ্জনক অভ্যাস (cookie-সহ অনুরোধে * নিয়মেই চলে না)। শুধু যে origin-গুলো সত্যিই দরকার, সেগুলোর নাম লিখুন। Browser-এর দিক থেকে fetch কীভাবে কাজ করে, তা দেখতে পারেন JavaScript কোর্সের fetch পর্বে, আর web নিরাপত্তার পুরো ছবি web security পর্বে।

৯ · REST — HTTP দিয়ে API নকশা

আজকের mobile app, ওয়েবসাইটের frontend আর microservice একে অপরের সাথে কথা বলে মূলত HTTP-র ওপর বানানো API দিয়ে। এদের বেশিরভাগ একটি নকশা-রীতি মানার চেষ্টা করে: REST (REpresentational State Transfer). ধারণাটি দেন Roy Fielding — HTTP/1.1-এর মানদণ্ড লেখকদের একজন — তাঁর ২০০০ সালের PhD গবেষণাপত্রে। REST কোনো protocol বা মানদণ্ড নয়; এটি কিছু নীতি: client-server, stateless, cacheable, স্তরে স্তরে সাজানো সিস্টেম, আর সবার জন্য একই ধরনের ইন্টারফেস। বাস্তবে “REST API” বলতে সাধারণত নিচের রীতিগুলো বোঝায়।

১. Resource হলো বিশেষ্য, URL তার ঠিকানা

URL-এ কাজের নাম (ক্রিয়া) নয়, জিনিসের নাম (বিশেষ্য) থাকে, সাধারণত বহুবচনে: /books (সংগ্রহ), /books/42 (একটি), /members/7/loans (একজন সদস্যের ধার)। /getBook?id=42 বা /deleteBook/42 REST-রীতির বাইরে — কাজটি তো method-ই বলে দেয়।

২. Method = CRUD, status code = ফল

কাজ (CRUD)Requestসফল হলেপ্রচলিত ব্যর্থতা
Create — তৈরিPOST /books + JSON201 Created + Location: /books/43400 (ভুল তথ্য), 409 (ISBN আগেই আছে)
Read — পড়াGET /books/43, GET /books?author=humayun&page=2200 OK + JSON404
Update — পুরো বদলPUT /books/43 + পূর্ণ JSON200 বা 204400, 404, 409
Update — আংশিকPATCH /books/43 + শুধু বদলানো ঘর200 বা 204400, 404
Delete — মোছাDELETE /books/43204 No Content404, 403

সব endpoint-এর জন্য সাধারণ: পরিচয় নেই — 401, অনুমতি নেই — 403, সীমার বেশি অনুরোধ — 429, server-এর বাগ — 500.

৩. Stateless, JSON আর versioning

  • Stateless: প্রতিটি request নিজেই সব তথ্য বহন করে — কে (token), কী, কোন সংস্করণ। Server আগের request মনে না রাখায় যেকোনো request যেকোনো server-এ যেতে পারে; load balancer-এর পেছনে ১০টি server রাখা সহজ হয় (পর্ব ৫২)।
  • JSON body: Content-Type: application/json; ঘরের নামে একটি নিয়ম (যেমন snake_case) পুরো API-তে মানা; ত্রুটির উত্তরেও একই কাঠামো (RFC 9457-এর application/problem+json একটি প্রমিত রূপ)।
  • Versioning: পুরোনো app ভেঙে না দিয়ে API বদলানোর পথ — URL-এ /v1/, /v2/ (সবচেয়ে প্রচলিত), অথবা header-এ। নতুন ঘর যোগ করা সাধারণত নিরাপদ; ঘর মুছে ফেলা বা অর্থ বদলানো মানে নতুন সংস্করণ।
  • বড় তালিকা: ?page=2&limit=20 বা cursor দিয়ে ভাগে ভাগে (pagination); পুরো ডেটাবেস এক উত্তরে নয়।

৪. একটি পূর্ণ নকশা — পাড়ার পাঠাগারের API

একটি পাঠাগারের app বানাতে হবে: বইয়ের তালিকা, সদস্য, আর বই ধার দেওয়া-ফেরত। নকশা:

GET    /v1/books?q=feluda&page=1      বই খোঁজা              → 200 + তালিকা
GET    /v1/books/42                    একটি বই               → 200 / 404
POST   /v1/books                       নতুন বই (লাইব্রেরিয়ান) → 201 + Location / 403
PATCH  /v1/books/42                    কপির সংখ্যা বদল        → 200 / 404
GET    /v1/members/7/loans             সদস্য ৭-এর ধার         → 200
POST   /v1/loans                       বই ধার দেওয়া          → 201 / 409 (কপি নেই)
DELETE /v1/loans/981                   বই ফেরত               → 204 / 404

বই ফেরত মানে ধার-রেকর্ডটি “শেষ” — এখানে তা DELETE হিসেবে নকশা করা হয়েছে; অনেকে ইতিহাস রাখতে PATCH /v1/loans/981 দিয়ে returned_at বসান, দুটিই চলে, শুধু পুরো API-তে একটি নিয়ম মানতে হয়। একটি ধার দেওয়ার আসল আদান-প্রদান:

POST /v1/loans HTTP/1.1
Host: api.library.example
Authorization: Bearer eyJ…
Content-Type: application/json
Content-Length: 34

{"member_id": 7, "book_id": 42}

HTTP/1.1 201 Created
Location: /v1/loans/981
Content-Type: application/json

{"id": 981, "member_id": 7, "book_id": 42, "due": "2026-11-09"}

লক্ষ করুন: ধার একটি নতুন জিনিস তৈরি করে, তাই POST আর 201; নতুন রেকর্ডের ঠিকানা Location-এ; কপি না থাকলে 409, কারণ অনুরোধ ঠিক কিন্তু resource-এর বর্তমান অবস্থার সাথে সংঘাত। POST idempotent নয় — সংযোগ ভেঙে গেলে app আবার পাঠালে একই বই দুবার ধার হয়ে যেতে পারে; এজন্যই ওপরে বলা idempotency key-র মতো কৌশল। Node.js-এ এমন একটি API বানানো দেখতে পারেন JavaScript কোর্সের Express REST API পর্বে; C-তে নিজের HTTP server বানানো পর্ব ৪৫-এ।

আরও গভীরে: HTTP/1.1-এ একটি বার্তায় Content-Length আর Transfer-Encoding: chunked দুটোই থাকলে RFC 9112 বলে Transfer-Encoding জেতে, আর এমন বার্তাকে সন্দেহজনক ধরে সংযোগ বন্ধ করাই নিরাপদ। সামনের proxy আর পেছনের server দুই নিয়মে body-র শেষ ধরলে একটির ভেতরে আরেকটি request “লুকিয়ে” পাঠানো সম্ভব হতো — এই শ্রেণির সমস্যার নাম request smuggling, আর তার প্রতিরক্ষা হলো কঠোর, একই নিয়মে parse করা আর অস্পষ্ট বার্তা বাতিল করা। এজন্যই RFC 9112 পুরোনো “header-এর লাইন ভেঙে পরের লাইনে চালানো” (obs-fold) নিষিদ্ধ করেছে আর header-এর নাম আর কোলনের মাঝে space থাকলে request বাতিল করতে বলে। আধুনিক server আর proxy-গুলো ক্রমশ এই নিয়মগুলো কড়াভাবে প্রয়োগ করছে।

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • HTTP-কে stateless বলা হয় কেন, তবু লগইন মনে থাকে কীভাবে? — Protocol আগের request মনে রাখে না; cookie-তে session ID বা token পাঠিয়ে server নিজের খাতা থেকে state খোঁজে।
  • Non-persistent সংযোগে base + N বস্তু, T নগণ্য — মোট সময়? — 2(N+1)·RTT; persistent-এ (N+2)·RTT; pipelining-এ 3·RTT.
  • PUT আর POST-এর পার্থক্য? — PUT নির্দিষ্ট URL-এ পুরো resource বসায় ও idempotent; POST প্রক্রিয়া করায়/নতুন তৈরি করে, idempotent নয়।
  • 401 আর 403-এর পার্থক্য? — 401: পরিচয় নেই বা ভুল; 403: পরিচয় জানা, অনুমতি নেই।
  • HTTP/1.1-এ Host header বাধ্যতামূলক কেন? — এক IP-তে অনেক সাইট (virtual hosting); না থাকলে server-কে 400 দিতে হয়।

১০ · AI-এর যুগে HTTP — API লেখা আর debug করা

“একটা REST API বানিয়ে দিন” বা “CORS error ঠিক করুন” — AI কয়েক সেকেন্ডে কোড দেয়। কিন্তু HTTP-র অর্থ (কোন method, কোন code, কোন header) ভুল হলে কোড চললেও সিস্টেম ভুল আচরণ করে, আর নিরাপত্তার ভুল চোখেই পড়ে না। চেনা ভুলগুলো:

AI-এর পরামর্শসমস্যাযাচাইয়ের উপায়
CORS error-এ Access-Control-Allow-Origin: * আর credentials চালুনিয়মেই চলে না, আর “সবাইকে খুলে দিন” মানসিকতা বিপজ্জনকশুধু দরকারি origin-এর তালিকা; browser DevTools-এর Network ট্যাবে preflight দেখুন
ত্রুটিতেও 200 + {"error": …}, বা 401/403 উল্টাপাল্টাCache, retry আর monitoring ভুল বোঝেRFC 9110-এর status code অংশের সাথে মেলান
GET দিয়ে মোছা (GET /delete?id=5)Prefetch, crawler বা CSRF-এ নিজে নিজে মুছে যেতে পারেঅবস্থা-বদলানো কাজ শুধু POST/PUT/PATCH/DELETE-এ
TLS error এড়াতে curl -k বা certificate যাচাই বন্ধMITM-এর দরজা খোলাCertificate ঠিক করুন; যাচাই বন্ধ কখনো production-এ নয়
Page load-এর ভুল সূত্র (handshake-এর RTT ভুলে যাওয়া)Non-persistent-এ প্রতি বস্তুতে ২ RTT, ১ নয়এই পর্বের হিসাবযন্ত্রে বা হাতে আবার করুন
বানানো header বা RFC নম্বরআত্মবিশ্বাসী ভুল তথ্যRFC Editor-এর সাইটে নম্বর আর MDN-এ header খুঁজুন

AI-কে API নকশা বা debug করতে বললে প্রেক্ষাপট দিন আর যাচাইযোগ্য উত্তর চান:

আমি একটি পাঠাগারের REST API নকশা করছি (Node.js, HTTPS, browser frontend আলাদা origin-এ)। ১. প্রতিটি endpoint-এর জন্য method, URL, সফল ও ব্যর্থ status code একটি সারণিতে দাও; প্রতিটি code কেন, RFC 9110 অনুযায়ী এক লাইনে বলো। ২. কোন endpoint idempotent নয় আর retry হলে কী বিপদ, তার সমাধান বলো। ৩. Session cookie-র Set-Cookie লাইন দাও (Secure, HttpOnly, SameSite কেন), আর CORS header শুধু আমার frontend origin-এর জন্য — wildcard নয়। ৪. যা নিশ্চিত নও, স্পষ্ট লিখবে; কোনো RFC নম্বর বানাবে না।

AI-এর HTTP কোড ব্যবহারের আগে — চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কমান্ড-জানা মানুষ থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১HTTP-কে ইচ্ছে করে stateless বানানো হলো কেন? Cookie দিয়ে আবার state জুড়ে দেওয়া কি সেই নকশার বিরোধিতা নয়?

Tim Berners-Lee-র প্রথম ওয়েব ছিল নথি পড়ার ব্যবস্থা: একটি link চাপুন, একটি নথি আসুক। প্রতিটি অনুরোধ স্বয়ংসম্পূর্ণ হলে server-কে কিছু মনে রাখতে হয় না, কোনো client মাঝপথে হারিয়ে গেলে কিছু পরিষ্কারও করতে হয় না, আর একটি সাধারণ যন্ত্রও হাজার ব্যবহারকারী সামলাতে পারে। এই সরলতাই ওয়েবকে দ্রুত ছড়াতে দিয়েছে — যে কেউ কয়েকশো লাইনে একটি server লিখতে পারতেন।

কিন্তু ১৯৯৪ সালে ই-কমার্স আসতেই সমস্যা দেখা দিল: ঝুড়িতে কী রাখা হয়েছে, তা পরের পেজে মনে থাকবে কীভাবে? Netscape-এর প্রকৌশলী Lou Montulli সেই বছর cookie-র ধারণা দেন — server একটি ছোট লেখা দেবে, browser সেটি ফেরত পাঠাবে। ১৯৯৭ সালে RFC 2109, পরে ২০১১ সালে RFC 6265 একে মানদণ্ড করে।

এটি বিরোধিতা নয়, বরং চতুর আপস। HTTP protocol নিজে stateless রইল — প্রতিটি request এখনো স্বয়ংসম্পূর্ণ, কারণ cookie-টাও request-এর ভেতরেই আছে। State-এর দায় গেল application-এর কাছে। Roy Fielding ২০০০ সালে REST-এর নীতিতে ঠিক এই কথাই বলেন: session state থাকবে client-এর দিকে, প্রতিটি request তার প্রয়োজনীয় সব তথ্য বহন করবে।

বাস্তবে বড় সিস্টেমে এর প্রভাব স্পষ্ট। Session server-এর মেমরিতে রাখলে load balancer-কে একই ব্যবহারকারীকে সবসময় একই server-এ পাঠাতে হয় (sticky session), আর সেই server পড়ে গেলে সবার লগইন হারায়। তাই প্রতিষ্ঠানগুলো session রাখে একটি ভাগ-করা দ্রুত store-এ (যেমন Redis), অথবা স্বাক্ষর-করা token ব্যবহার করে, যাতে যেকোনো server নিজেই যাচাই করতে পারে। দুটির দাম আলাদা: token server-এ খোঁজ ছাড়াই চলে, কিন্তু মেয়াদের আগে বাতিল করা কঠিন।

মূল উপলব্ধি: Protocol-কে stateless রেখে state-কে স্পষ্টভাবে request-এর ভেতরে বহন করানো — এই নকশাই ওয়েবকে কোটি ব্যবহারকারী পর্যন্ত বাড়তে দিয়েছে; state কোথায় থাকবে, সেটি একটি সচেতন স্থাপত্য-সিদ্ধান্ত।

প্র ০২Host header-এর ছোট্ট একটি লাইন কীভাবে IPv4 ঠিকানা বাঁচিয়েছে, আর HTTPS-এর যুগে একই সমস্যা কোথায় ফিরে এসেছে?

HTTP/1.0-এর request line-এ শুধু path থাকত, যেমন GET /index.html. Server জানত কোন IP আর port-এ সংযোগ এসেছে, কিন্তু browser কোন নাম লিখেছিল তা জানত না। তাই একটি যন্ত্রে দশটি ওয়েবসাইট রাখতে হলে যন্ত্রটিকে দশটি IP ঠিকানা দিতে হতো, আর server ঠিকানা দেখে সাইট বাছত। ১৯৯০-এর দশকের মাঝামাঝি ওয়েবসাইটের সংখ্যা বিস্ফোরিত হচ্ছিল, আর একই সময়ে IPv4 ঠিকানা ফুরিয়ে যাওয়ার আশঙ্কা নিয়ে উদ্বেগ বাড়ছিল (পর্ব ১৬, ২১)। প্রতিটি ছোট ব্লগের জন্য আলাদা ঠিকানা — এটি টেকসই ছিল না।

HTTP/1.1 সমাধান দিল একটি লাইনে: Host: www.example.com, আর তাকে বাধ্যতামূলক করল। এখন একটি IP-তে হাজার সাইট — shared hosting-এর পুরো ব্যবসা, যেখানে অল্প খরচে বাংলাদেশের অসংখ্য ছোট প্রতিষ্ঠান নিজের ওয়েবসাইট রাখে, এর ওপরই দাঁড়িয়ে। একই ধারণা আজ reverse proxy আর CDN-এর মূলে: একটি edge server লাখো ডোমেইনের অনুরোধ নেয়, Host দেখে ঠিক গ্রাহকের কাছে পাঠায়।

HTTPS এসে সমস্যাটি এক স্তর নিচে ফিরিয়ে আনল। TLS handshake হয় HTTP request-এর আগে, আর server-কে তখনই ঠিক certificate দেখাতে হয় — কিন্তু Host header তো তখনো আসেনি! সমাধান হলো TLS-এর SNI (Server Name Indication) extension, যা ২০০৩ সালে RFC 3546-এ আসে: client handshake-এর প্রথম বার্তায় নাম জানায়। পুরোনো কিছু client (যেমন Windows XP-র পুরোনো browser) SNI না জানায় বহু বছর HTTPS সাইটগুলোকে আলাদা IP রাখতে হয়েছিল। আবার SNI খোলা লেখায় যায় বলে পথের যন্ত্র দেখতে পারে আপনি কোন সাইটে যাচ্ছেন; এর সমাধান হিসেবে Encrypted Client Hello (ECH) নিয়ে কাজ চলছে, যার প্রচলন এখনো বাড়ছে (পর্ব ৪৮)।

মূল উপলব্ধি: একটি নির্দেশক (নাম) কোন স্তরে বহন করা হচ্ছে, তা পুরো ইন্টারনেটের অর্থনীতি বদলে দিতে পারে; আর প্রতিটি নতুন স্তর (TLS) যোগ হলে পুরোনো প্রশ্ন নতুন জায়গায় আবার জবাব চায়।

প্র ০৩Pipelining তো HTTP/1.1-এর মানদণ্ডেই ছিল — তবু browser-রা কেন তা প্রায় কখনো চালু করেনি, আর তার বদলে কী কী কৌশল জন্মাল?

কাগজে pipelining চমৎকার: আমাদের হিসাবে ১০টি বস্তুর পেজ ১২০০ ms থেকে ৩০০ ms-এ নামে। কিন্তু দুটি বাস্তব সমস্যা একে আটকে দিল। প্রথমটি নকশাগত — head-of-line blocking. উত্তর ক্রম মেনে আসতে হয়, তাই একটি ধীর উত্তর পেছনের সবাইকে আটকায়, আর কোন request ধীর হবে browser আগে জানে না। দ্বিতীয়টি বাস্তবায়নের — ইন্টারনেটের পথে অসংখ্য proxy, firewall আর পুরোনো server pipelining ঠিকমতো সামলাত না: কেউ দ্বিতীয় request ফেলে দিত, কেউ উত্তর মিশিয়ে ফেলত। ভুল পেজ দেখানোর ঝুঁকি browser নিতে পারে না।

ফলে বড় browser-গুলো pipelining ডিফল্টে বন্ধ রাখে; কয়েকটি পরীক্ষামূলকভাবে চালু করে দেখে পরে বাদ দিয়েছে বলে জানা যায়। তার জায়গায় ২০০০-এর দশকে একগুচ্ছ “কৌশল” জন্মায়: প্রতি host-এ প্রায় ৬টি parallel সংযোগ; domain sharding — ছবি img1.example.com, img2.example.com-এ ভাগ করা, যাতে browser আরও বেশি সংযোগ খোলে; CSS sprite — অনেক ছোট ছবি এক ফাইলে জোড়া দিয়ে; আর সব JavaScript একটি বড় ফাইলে bundle করা। সবগুলোর লক্ষ্য এক — request বা RTT-এর সংখ্যা কমানো। কিন্তু প্রতিটির দাম ছিল: বেশি সংযোগ মানে বেশি handshake আর প্রতিটির আলাদা slow start, যা নেটওয়ার্কের জ্যামে ন্যায্যতাও নষ্ট করে।

আসল সমাধান এল ২০১৫ সালে HTTP/2-তে (Google-এর SPDY পরীক্ষা থেকে): একটি সংযোগে অনেক stream, প্রতিটি frame-এ stream নম্বর, তাই উত্তর যেকোনো ক্রমে মিশিয়ে আসতে পারে। HTTP/2-এর যুগে domain sharding আর sprite উল্টো ক্ষতিকর হয়ে দাঁড়ায়। তবু TCP-র নিজের স্তরে head-of-line থেকে যায় — একটি segment হারালে সব stream আটকায় — যার সমাধান QUIC-এর ওপর HTTP/3 (পর্ব ৩৩, ৩৭)।

মূল উপলব্ধি: একটি বৈশিষ্ট্য মানদণ্ডে থাকা আর ইন্টারনেটে কাজ করা এক জিনিস নয়; পথের পুরোনো যন্ত্রগুলোর আচরণও নকশার অংশ। আর একটি সমস্যার অস্থায়ী কৌশল পরের প্রজন্মে নিজেই সমস্যা হয়ে যেতে পারে।

প্র ০৪Cookie একটি ঝুড়ি মনে রাখার জন্য বানানো হয়েছিল। এটি কীভাবে বিশ্বজুড়ে নজরদারির যন্ত্র হলো, আর সমাজ ও browser কীভাবে সাড়া দিল?

Montulli-র মূল নকশাতেই একটি ছিদ্র ছিল: একটি পেজ অন্য host থেকে ছবি বা script আনলে, সেই অন্য host-ও নিজের cookie বসাতে ও পড়তে পারত। প্রথম দিকের বিজ্ঞাপন-নেটওয়ার্কগুলো দ্রুত বুঝে গেল — হাজার সাইটে একই বিজ্ঞাপন-সংস্থার ছবি থাকলে একই cookie দিয়ে একজন ব্যবহারকারীকে সাইট থেকে সাইটে অনুসরণ করা যায়। ১৯৯৭ সালের RFC 2109 আসলে সুপারিশ করেছিল তৃতীয় পক্ষের cookie ডিফল্টে বন্ধ রাখতে, কিন্তু browser-রা তা মানেনি — বিজ্ঞাপন তখন ওয়েবের অর্থনীতির কেন্দ্রে।

পরের দুই দশকে ট্র্যাকিং আরও সূক্ষ্ম হলো: cookie মুছে দিলেও browser-এর বৈশিষ্ট্য মিলিয়ে চেনা (fingerprinting), এক cookie দিয়ে অন্যটি আবার তৈরি করা ইত্যাদি। প্রতিক্রিয়া এলো আইনে আর প্রযুক্তিতে। ইউরোপীয় ইউনিয়নের ePrivacy নির্দেশনা আর ২০১৮ সালের GDPR-এর ফলে ইউরোপে সাইটগুলোকে সম্মতি নিতে হয় — সেই পরিচিত “cookie banner”. Browser-এর দিকে Safari ২০১৭ সাল থেকে Intelligent Tracking Prevention দিয়ে ধাপে ধাপে কড়াকড়ি শুরু করে আর ২০২০ সাল নাগাদ তৃতীয় পক্ষের cookie পুরো বন্ধ করে; Firefox ২০১৯ সালে পরিচিত ট্র্যাকার আটকানো আর ২০২২ সালে প্রতিটি সাইটের জন্য আলাদা cookie-খোপ ডিফল্ট করে। Chrome বহু বছর বিকল্প প্রযুক্তি (Privacy Sandbox) নিয়ে কাজ আর তৃতীয় পক্ষের cookie তুলে দেওয়ার সময়সূচি কয়েকবার পিছিয়েছে, আর পরে জানিয়েছে আপাতত তা পুরো বন্ধ করছে না — সর্বশেষ অবস্থা বদলাতে পারে।

একই সময়ে বৈধ ব্যবহারও চাপে পড়ে: অন্য domain-এর লগইন (single sign-on), embedded পেমেন্ট বা মন্তব্যের widget. তাই নতুন ব্যবস্থা এসেছে — CHIPS-এর মতো “partitioned” cookie, যা প্রতিটি উপরের-স্তরের সাইটের জন্য আলাদা খোপে থাকে; SameSite=None-এ Secure বাধ্যতামূলক করা।

মূল উপলব্ধি: একটি protocol বৈশিষ্ট্যের সামাজিক প্রভাব তার নকশাকারীরা প্রায়ই আগে দেখতে পান না; ডিফল্ট মান কী হবে — তা প্রযুক্তিগত প্রশ্নের চেয়ে বেশি অর্থনীতি আর গোপনীয়তার প্রশ্ন।

প্র ০৫“GET কখনো কিছু বদলাবে না” — এই নিয়ম ভাঙলে বাস্তবে কী ঘটে? safe আর idempotent-এর ধারণা কেন এত গুরুত্বপূর্ণ?

HTTP-র পুরো পরিবেশ ধরে নেয় GET নিরাপদ। Search engine-এর crawler প্রতিটি link অনুসরণ করে; browser দ্রুততার জন্য link আগেভাগে খুলে রাখে (prefetch); antivirus বা ইমেইলের নিরাপত্তা-স্ক্যানার link খুলে দেখে বিপজ্জনক কি না; proxy আর browser ব্যর্থ GET নিজে থেকে আবার পাঠায়। এদের কেউ আপনাকে জিজ্ঞেস করে না।

২০০৫ সালে Google Web Accelerator নামে একটি টুল ব্যবহারকারীর পেজের link আগেভাগে আনত, যাতে পরের পেজ দ্রুত খোলে। কিছু ওয়েব অ্যাপ “মুছুন” কাজটি সাধারণ link — অর্থাৎ GET — দিয়ে বানিয়েছিল; ফলে টুলটি নিজে নিজে সেই link-গুলো খুলে ব্যবহারকারীদের ডেটা মুছে দিয়েছিল বলে সেই সময় ব্যাপক আলোচনা হয়। দোষ কার? ডেভেলপারদের মধ্যে বিতর্ক হয়েছিল, কিন্তু মানদণ্ড স্পষ্ট: GET safe, তাই prefetch বৈধ; অবস্থা-বদলানো কাজ GET-এ রাখাই ভুল।

নিরাপত্তার দিকেও একই কথা। অবস্থা-বদলানো GET থাকলে অন্য সাইটের একটি সাধারণ ছবির ট্যাগই আপনার browser-কে দিয়ে সেই URL খুলিয়ে নিতে পারে — CSRF সবচেয়ে সহজ হয়ে যায়, আর SameSite=Lax-ও উপরের-স্তরের GET navigation-এ cookie পাঠায় বলে পুরো রক্ষা করে না। Idempotency-র গুরুত্ব আবার নির্ভরযোগ্যতায়: মোবাইল নেটওয়ার্কে সংযোগ ভাঙা স্বাভাবিক ঘটনা, আর idempotent অনুরোধ নির্ভয়ে আবার পাঠানো যায়। এজন্যই TLS 1.3 আর QUIC-এর 0-RTT-তে সাধারণত শুধু safe বা idempotent অনুরোধ পাঠানোর পরামর্শ (RFC 8470; পর্ব ৩৩), আর পেমেন্ট API-তে POST-কে idempotency key দিয়ে কৃত্রিমভাবে idempotent বানানো হয়।

মূল উপলব্ধি: Method-এর অর্থ শুধু “শিষ্টাচার” নয়, এটি client, proxy, cache আর crawler-এর সাথে একটি চুক্তি। চুক্তি মানলে পুরো ইন্টারনেট আপনার সিস্টেমকে নিরাপদে দ্রুত করে; ভাঙলে সেই একই যন্ত্রগুলো ক্ষতি করে।

অনুশীলন

খাতা-কলম নিয়ে বসুন — হিসাব আর পূর্বানুমানই আসল কাজ।

  1. হাতে হিসাব: একটি পেজে base HTML আর ৬টি ছবি। RTT = ৮০ ms, transmission time নগণ্য, DNS বাদ। (ক) non-persistent একটির পর একটি, (খ) non-persistent ৩টি parallel সংযোগ, (গ) persistent pipelining ছাড়া, (ঘ) persistent pipelining-সহ — প্রতিটিতে মোট সময় কত?
    • (ক) 2(N+1)·RTT = ২ × ৭ × ৮০ = ১১২০ ms.
    • (খ) base-এ ২ RTT = ১৬০; তারপর ৬ ÷ ৩ = ২ দফা, প্রতি দফায় ২ RTT = ১৬০ → ১৬০ + ৩২০ = ৪৮০ ms.
    • (গ) (N+2)·RTT = ৮ × ৮০ = ৬৪০ ms.
    • (ঘ) 3·RTT = ২৪০ ms.

    এই পর্বের হিসাবযন্ত্রে pageLoad(80, 6, 0, 3) চালালে একই চারটি সংখ্যা আসে। লক্ষ করুন, pipelining-এ উত্তর N-এর ওপর নির্ভর করে না (T নগণ্য হলে)।

  2. Chunked body হাতে খুলুন: একটি response-এ Transfer-Encoding: chunked, আর body-র বাইটগুলো (প্রতিটি \r\n দেখানো): a\r\nBangladesh\r\n7\r\n Metro \r\n4\r\nRail\r\n0\r\n\r\n. আসল body কী, কত বাইট, আর একই উত্তর Content-Length দিয়ে পাঠালে মান কত হতো?

    আকারগুলো hex: a = ১০ → “Bangladesh” (১০ অক্ষর); 7 = ৭ → “ Metro ” (দুই পাশে space-সহ ৭); 4 = ৪ → “Rail”; 0 = শেষ। Body: Bangladesh Metro Rail, ১০ + ৭ + ৪ = ২১ বাইট, তাই Content-Length: 21. Chunk-এর আকার-লাইন আর প্রতিটি chunk-এর পরের \r\n body-র অংশ নয়, তাই গোনা হয় না। সবচেয়ে প্রচলিত ভুল a-কে অক্ষর ভেবে আকার না পড়া, বা 10 লেখা থাকলে দশমিক ১০ ধরা — hex 10 মানে ১৬।

  3. কোন cookie যাবে — অনুমান করুন: https://app.example.com/account-এর উত্তরে এল: a=1; Path=/account; Secure, b=2; Domain=example.com, c=3; SameSite=Strict. (তিনটির কোনোটিতে মেয়াদ নেই, আর SameSite না লেখা থাকলে Lax ধরুন।) নিচের প্রতিটি request-এ Cookie header-এ কী যাবে? (১) একই সাইটের ভেতরে https://app.example.com/account/settings; (২) ঠিকানার ঘরে নিজে লিখে http://app.example.com/; (৩) https://forum.example.org-এর একটি link চেপে https://www.example.com/.
    • (১) a=1; b=2; c=3 — path /account/settings পড়ে /account-এর নিচে, HTTPS, same-site.
    • (২) b=2; c=3 — a Secure, তাই http-তে যায় না; a-এর path-ও মেলে না। c-এর Path দেওয়া নেই; /account থেকে ডিফল্ট path হয় /, তাই মেলে। ঠিকানার ঘরে লেখা মানে অন্য সাইট শুরু করেনি, তাই Strict-এও যায়।
    • (৩) শুধু b=2 — a আর c host-only (app.example.com), www-তে যায় না; b Domain=example.com, আর cross-site হলেও উপরের-স্তরের GET link, তাই Lax-এ যায়।
  4. কমান্ড লিখুন: curl দিয়ে (ক) শুধু response header দেখুন (HEAD), (খ) পুরো request আর response-এর header দেখুন, (গ) একটি JSON body POST করুন https://api.example.com/v1/members-এ, (ঘ) একটি ফাইলের প্রথম ১০০ বাইট চান। (ঘ)-তে server range সমর্থন করলে কোন status code আশা করবেন?
    curl -I https://example.com/                               # (ক) HEAD: শুধু header
    curl -v https://example.com/ -o /dev/null                  # (খ) > request, < response header
    curl -X POST https://api.example.com/v1/members \
         -H 'Content-Type: application/json' \
         -d '{"name": "Rafi"}'                                 # (গ)
    curl -H 'Range: bytes=0-99' -o part.bin https://example.com/file.zip   # (ঘ)

    (ঘ)-তে আশা করবেন 206 Partial Content আর Content-Range: bytes 0-99/…; server range না বুঝলে পুরো ফাইল আসবে 200-এ। -d দিলে curl নিজেই POST ধরে, তবু -X POST লিখে রাখা পরিষ্কার। Windows-এর নতুন সংস্করণেও curl.exe থাকে, তবে PowerShell-এ curl নামটি কখনো কখনো অন্য কমান্ডের ডাকনাম — তখন curl.exe লিখুন, আর quote-এর নিয়ম আলাদা।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমBrowser-এ প্রথমবার একটি https:// URL খুললে ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. DNS দিয়ে host-এর নাম থেকে IP ঠিকানা জানা
  2. Port 443-এ TCP three-way handshake
  3. TLS handshake — সংযোগ encrypt করা
  4. Host header-সহ GET request পাঠানো
  5. Status line আর header, তারপর body পাওয়া
  6. একই সংযোগে পেজের ছবি আর CSS-এর request

কোন HTTP method idempotent, কিন্তু safe নয়?

  • GET
  • POST
  • PUT
  • OPTIONS

একটি HTTP/1.1 request-এ কোন header না থাকলে server-কে 400 Bad Request দিতে হয়?

  • User-Agent
  • Host
  • Accept
  • Connection

একটি পেজে base HTML আর ৪টি ছবি; RTT = ৫০ ms, transmission time নগণ্য। Non-persistent HTTP-তে একটির পর একটি আনলে মোট সময় কত?

  • ২৫০ ms
  • ৩০০ ms
  • ৪০০ ms
  • ৫০০ ms

একটি session cookie-তে HttpOnly attribute দেওয়ার মূল উদ্দেশ্য কী?

  • পেজের JavaScript যেন cookie পড়তে না পারে
  • Cookie যেন শুধু HTTPS-এ যায়
  • অন্য সাইটের request-এ cookie যেন না যায়
  • Browser বন্ধ হলে cookie যেন মুছে যায়

একটি reverse proxy পেছনের application server-এ সংযোগ পেয়েছে, কিন্তু নির্দিষ্ট সময়ের মধ্যে কোনো উত্তর পায়নি। Browser কোন status code দেখবে?

  • 500 Internal Server Error
  • 502 Bad Gateway
  • 504 Gateway Timeout
  • 408 Request Timeout

একটি REST API-তে POST /v1/books সফলভাবে নতুন বই তৈরি করলে সবচেয়ে উপযুক্ত উত্তর কোনটি?

  • 200 OK, body-তে "created"
  • 201 Created, সাথে Location: /v1/books/43
  • 204 No Content
  • 302 Found, Location: /v1/books

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. URL = scheme://host:port/path?query#fragment; fragment server-এ যায় না; বিশেষ অক্ষর আর বাংলা যায় percent-encoding-এ। HTTP stateless request–response, TCP 80 বা TLS 443-এ; বর্তমান মানদণ্ড RFC 9110/9112 (২০২২)।
  2. বার্তা = start line + header + ফাঁকা লাইন + body, প্রতিটি লাইন \r\n-এ শেষ; HTTP/1.1-এ Host বাধ্যতামূলক; body-র শেষ জানায় Content-Length বা chunked (আকার hex-এ)।
  3. GET/HEAD/OPTIONS safe; PUT/DELETE idempotent কিন্তু safe নয়; POST/PATCH কোনোটিই নয়। ২xx সফল, ৩xx redirect/304, ৪xx client, ৫xx server; 401 ≠ 403, 502/503/504 আলাদা।
  4. Non-persistent: 2(N+1)·RTT; persistent: (N+2)·RTT; pipelining: 3·RTT (T নগণ্য)। Pipelining-এর head-of-line সমস্যার সমাধান HTTP/2-এর multiplexing.
  5. Cookie-তে session ID; Secure, HttpOnly, SameSite চুরি আর CSRF ঠেকায়; CORS browser-এর নিষেধে ছাড়, server-এর সুরক্ষা নয়; REST-এ URL = বিশেষ্য, method = CRUD, status code = ফল।

পরের পর্বে: HTTP/1.1-এর head-of-line সমস্যা আর লেখা-ভিত্তিক header-এর অপচয় কীভাবে মিটল — HTTP/2-এর binary frame আর stream multiplexing, HPACK দিয়ে header সংকোচন, আর QUIC-এর ওপর HTTP/3.

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান