Course-এর সূচি

2.3

Header, Keep-Alive, Compression ও CORS

যেসব header সত্যিই দরকারি, connection বারবার ব্যবহার, gzip বনাম Brotli বনাম zstd, আর browser-এর cross-origin নিয়ম শুরু থেকে শেষ।

40 মিনিট পড়া + Animation

টিপস: ডটেড-আন্ডারলাইন করা যেকোনো শব্দে ট্যাপ বা Hover করলে তার অর্থ দেখবে।

এই Lesson শেষে তুমি পারবে

  • যেকোনো HTTP Header-এর তালিকা পড়তে: Request, Response আর Representation Header আলাদা করতে, End-to-end আর Hop-by-hop Header চিনতে; নামগুলো কেন Case-insensitive আর HTTP/2-এর Pseudo-header কী, তা জানতে।
  • Production-এ দরকারি Header ঠিকভাবে ব্যবহার করতে: Host, Accept*, Content-Type, Content-Length, Authorization, Cookie, X-Forwarded-For আর Forwarded, Request ID আর traceparent, আর প্রধান Security Header।
  • Keep-alive আর Persistent Connection বোঝাতে, Load Balancer আর App-এর Idle Timeout এমনভাবে ঠিক করতে যাতে কখনো 502 না আসে, আর HTTP Keep-alive আর TCP Keepalive-এর পার্থক্য বলতে।
  • Compression কীভাবে ঠিক হয় তা বোঝাতে, সংখ্যা দিয়ে gzip, Brotli আর zstd-এর মধ্যে বাছতে, কী কখনো Compress করবে না তা জানতে, আর Dictionary Compression ও BREACH আক্রমণ বোঝাতে।
  • Same-origin Policy আর CORS শুরু থেকে শেষ বোঝাতে: Origin, Simple আর Preflight করা Request, প্রতিটা Access-Control-* Header, Credential, Cache, আর যেসব ভুল Configuration CORS-কে Data ফাঁসের রাস্তা বানিয়ে দেয়।
  • এসব Design আর Debugging-এ কাজে লাগাতে: Header কোথায় বসাবে, Preflight কীভাবে এড়াবে, আর কোন লক্ষণ কোন দিকে ইশারা করে।

#এই Lesson কোথায় বসে

Lesson ১.১-এ আমরা HTTP Message-এর গড়ন দেখেছি (প্রথম লাইন, Header, ফাঁকা লাইন, Body), সাথে রোজকার Header-এর একটা Table। Lesson ১.৩-এ শিখেছি Set-Cookie, Cookie আর তাদের Attribute; Lesson ১.৯-এ HSTS; আর Lesson ১.১০-এ দেখেছি নতুন TCP Connection কেন ধীর। Lesson ২.১-এ HTTP-কে ১.০ থেকে ৩ পর্যন্ত হেঁটে দেখেছি, আর Lesson ২.২-এ Method আর Status Code।

এই Lesson প্রতিটা Request আর Response-এর চারপাশের Metadata নিয়ে: Header, যে Connection-এর ওপর দিয়ে এরা যায়, Body কীভাবে ছোট করা হয়, আর Browser-এর সেই নিয়ম যা ঠিক করে কোন Page কোন Response পড়তে পারবে। চারটাই তুমি প্রতি সপ্তাহে Web Server, Load Balancer আর CDN-এ Configure করবে, আর চারটা থেকেই Production-এর চিরচেনা দুর্ঘটনাগুলো ঘটে।

#Header-এর গড়ন

Header হলো নাম আর মানের একটা জোড়া, মানে Metadata: Name: value। HTTP/1.1-এ প্রতিটা একটা করে লেখার লাইন; সবগুলো মিলে প্রথম লাইন আর Body-র আগের ফাঁকা লাইনের মাঝখানে বসে।

GET /api/orders?page=2 HTTP/1.1
Host: shop.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) … Chrome/141.0 Safari/537.36
Accept: application/json
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: bn-BD, en;q=0.8
Authorization: Bearer eyJhbGciOi…
Cookie: sid=9f2c…; lang=bn

যে নিয়মগুলোতে মানুষ হোঁচট খায়:

  • নাম Case-insensitive। Content-Type, content-type আর CONTENT-TYPE একই Header। মান সাধারণত Case-sensitive (একটা Token, একটা Cookie)।
  • আলাদা আলাদা Header-এর মধ্যে ক্রমের কোনো দাম নেই, তবে একই Header বারবার এলে আছে।
  • একই Header বারবার আসতে পারে, আর সেগুলোর মান কমা দিয়ে জোড়া যায়: দুটো Accept-Encoding লাইন মানে দুটো মান সহ একটা লাইন। বিখ্যাত ব্যতিক্রম Set-Cookie: এর মানে কমা থাকে (তারিখে), তাই প্রতিটা Cookie-র নিজের লাইন লাগে।
  • Accept* Header-এ Quality Value (;q=0.8) ০ থেকে ১ পর্যন্ত পছন্দের ক্রম জানায়; q=0 মানে “কখনোই না”।
  • নিজের বানানো Header চলে। পুরনো X- উপসর্গের রীতি ২০১২ সালে আনুষ্ঠানিকভাবে বাদ দেওয়া হয়েছে, কিন্তু X-Request-ID আর X-Forwarded-For এত ছড়িয়ে গেছে যে নাম আর বদলানো যায় না।

#চার রকম Header

ধরনকী বর্ণনা করেউদাহরণ
Request HeaderClient নিজে আর সে কী চায়Host, User-Agent, Accept, Accept-Encoding, Authorization, Cookie, Origin
Response HeaderServer আর পুরো উত্তরটাServer, Set-Cookie, Location, Retry-After, Access-Control-Allow-Origin, Strict-Transport-Security
Representation HeaderBody নিজে (যেকোনো পাশ পাঠাতে পারে)Content-Type, Content-Length, Content-Encoding, Content-Language
Hop-by-hop Headerশুধু একটা Connection, কখনো সামনে যায় নাConnection, Keep-Alive, Transfer-Encoding, TE, Upgrade, Proxy-Authorization

যা Hop-by-hop না, তা-ই End-to-end: প্রতিটা Proxy আর Cache পার হয়ে এটাকে শেষ প্রাপকের কাছে অপরিবর্তিত পৌঁছাতে হবে। Hop-by-hop Header শুধু এই Link-এর কথা বলে (TCP Connection খোলা রাখো, এই Link-এ Body টুকরো টুকরো আসছে), তাই সামনে পাঠানোর আগে Proxy এগুলো সরাতে বাধ্য, সাথে Connection-এর ভেতরে যেসব Header-এর নাম লেখা আছে সেগুলোও। Proxy চাইলে নিজের Header যোগও করতে পারে; X-Forwarded-For আর Via এভাবেই আসে।

#HTTP/2 আর HTTP/3-এ Header

মানে হুবহু এক, কিন্তু তারের ওপর এরা আর লেখা হিসেবে যায় না:

  • Request Line ভেঙে হয় কোলন দিয়ে শুরু হওয়া Pseudo-header: :method, :scheme, :authority (Host-এর বদলে) আর :path। Response-এ থাকে :status। Pseudo-header সবার আগে আসে।
  • সব নাম ছোট হাতের হতে হবে। বড় হাতের নাম থাকলে Message-টা ভুল গঠনের ধরা হয়।
  • Connection-নির্দিষ্ট Header নিষেধ: Connection, Keep-Alive, Proxy-Connection, Transfer-Encoding আর Upgrade। TE-তে শুধু trailers লেখা চলে।
  • Header Compress হয়ে যায়: HTTP/2-তে HPACK, HTTP/3-তে QPACK। তাই প্রতি Request-এ একই ২ KB Cookie পাঠালেও প্রথমবারের পরে খরচ মাত্র কয়েক Byte (Lesson ২.১)।

Proxy হরদম এক Version থেকে আরেকটায় অনুবাদ করে: Browser থেকে Edge পর্যন্ত HTTP/2, Edge থেকে App পর্যন্ত HTTP/1.1।

পথে Header: End-to-end বনাম Hop-by-hop

একটা Request একটা Reverse Proxy (বা Load Balancer) পার হয়ে App-এ পৌঁছায়। দেখো Proxy কোন Header ফেলে দিতে বাধ্য, কোনগুলো যোগ করে, আর কোনগুলো যেমন ছিল তেমনই চলে যায়।

পরিস্থিতি

End-to-end Header পুরো পথ যায়; Hop-by-hop Header প্রথম Hop-এই শেষ; Proxy নিজের কিছু Header যোগ করে।

Browser203.0.113.7
Reverse ProxyLB / CDN
App Server10.0.1.5
GET /api/orders

Browser → Proxy (HTTP/1.1)

Host

shop.example.com

Accept

application/json

Accept-Encoding

gzip, br, zstd

Cookie

sid=9f2c…

Connection

keep-alive

ধাপ 1 / 5
1

Browser পাঠায় Request Header (Host, Accept, Accept-Encoding, Cookie), যেগুলো একেবারে শেষের Server-এর জন্য; সাথে Connection: keep-alive, যেটা শুধু Proxy পর্যন্ত এই একটা TCP Connection-এর ব্যাপার।

#আকারের সীমা

Standard-এ কোনো সীমা নেই, কিন্তু প্রতিটা Server-এর একটা আছে, আর সীমা পেরোলে তোমার Code চলার আগেই Request ফেরত যায়: Nginx Default-এ প্রতি Header লাইনে ৮ KB দেয় (large_client_header_buffers 4 8k), Apache প্রতি Field-এ ৮,১৯০ Byte, Node.js সব Header মিলিয়ে ১৬ KB। উত্তর আসে 431 Request Header Fields Too Large, অথবা “Request Header Or Cookie Too Large” লেখা 400। সাধারণত দোষী Cookie: মিলে যাওয়া প্রতিটা Cookie প্রতিটা Request-এ যায়, Image-এর Request-এও। প্রতিটা Hop-এর (CDN, Load Balancer, Proxy, App) সীমা আলাদা, আর সবচেয়ে ছোটটাই জেতে।

#যেসব Header সত্যিই দরকারি

রোজকার Table আছে Lesson ১.১-এ। এখানে দেখব জরুরি Header-গুলো আসলে কী করে, আর কোথায় গোলমাল বাধে।

Headerকে পাঠায়কী করেকোথায় গোলমাল
HostClientএই IP-র কোন Site; একটা Server-এ হাজার Domain রাখা যায় এর জোরে (Virtual Hosting)। HTTP/1.1-এ বাধ্যতামূলক; HTTP/2-তে :authorityনা থাকলে 400। Host দেখে Routing করা Load Balancer অচেনা Host-কে Default Site-এ পাঠায়
User-AgentClientকোন Browser, OS বা Library জিজ্ঞেস করছেসহজেই জাল করা যায়; Browser এখন ছাঁটা, স্থির একটা String পাঠায় আর বিস্তারিত দেয় Client Hints দিয়ে (Sec-CH-UA*)। Security-র কাজে কখনো ব্যবহার করবে না
AcceptClientClient কোন Media Type পড়তে পারে, q-value সহঅনেক API পাত্তা দেয় না; Content Negotiation-এ কাজে লাগে (406 Not Acceptable)
Accept-LanguageClientপছন্দের ভাষা (bn-BD, en;q=0.8)এটা দেখে Output বদলালে Cache-এর জন্য Vary: Accept-Language লাগবে
Accept-EncodingClientClient কোন Compression খুলতে পারেCompression অংশে দেখো
Content-Typeদুই পাশইBody-র Media Type আর Charset: application/json, text/html; charset=utf-8ভুল Type দিলে Browser ভুল পড়ে, বা JSON API ফেরত দেয় 415 Unsupported Media Type
Content-Lengthদুই পাশইBody কত Byte, যাতে প্রাপক জানে Message কোথায় শেষCompression থাকলে এটা Compress করার পরের মাপ। Transfer-Encoding-এর সাথে না মিললে বিভিন্ন Proxy Request-এর শেষ নিয়ে একমত হয় না (Request Smuggling, Chapter 35)
Transfer-Encoding: chunkedদুই পাশই (HTTP/1.1)মাপ আগে জানা না থাকলে Body টুকরো টুকরো পাঠানো (Streaming)Hop-by-hop; HTTP/2 আর HTTP/3-এর নিজস্ব Framing আছে, এটা সেখানে নিষেধ
AuthorizationClientCredential: Bearer <token>, Basic <base64>না থাকলে বা ভুল হলে 401, সাথে WWW-Authenticate। কখনো Log করবে না
Cookie / Set-CookieClient / ServerBrowser-এর State; Attribute Secure, HttpOnly, SameSite (Lesson ১.৩)বেশি Cookie → 431; Secure ছাড়া SameSite=None বাতিল হয়
ServerServerServer Software-এর নাম, যেমন nginx/1.24.0হুবহু Version দেখে Attacker বোঝে কোন জানা Bug চেষ্টা করবে; Production-এ লুকানো হয় (server_tokens off)
LocationServerRedirect-এর ঠিকানা, বা নতুন তৈরি Resource-এর URL (201)Scheme বদলে দেওয়া Proxy-র পেছনে Relative আর Absolute URL নিয়ে গোলমাল

#আসল Client কে? X-Forwarded-For আর Forwarded

Load Balancer-এর পেছনে App-এর TCP সঙ্গী হলো Load Balancer, ব্যবহারকারী না (Lesson ১.৭: Load Balancer-ও এক রকম NAT)। তাই Edge একটা Header-এ Client-এর Address লিখে দেয়:

X-Forwarded-For: 203.0.113.7, 198.51.100.10
X-Forwarded-Proto: https
X-Forwarded-Host: shop.example.com
Forwarded: for=203.0.113.7;proto=https;host=shop.example.com, for=198.51.100.10

প্রতিটা Proxy নিজের দেখা Address তালিকার শেষে যোগ করে, তাই তালিকা বাঁ থেকে ডানে বাড়ে: প্রথমে আসল Client, শেষে শেষ Proxy। Forwarded (RFC 7239) হলো তিনটা X-Forwarded-* Header-এর Standard, গোছানো রূপ; দুটোই চালু।

ফাঁদটা হলো: Client নিজেও X-Forwarded-For পাঠাতে পারে, আর Proxy সেটার শেষেই যোগ করে। বাঁয়ের মানটা Client যা লিখেছে তা-ই। বিশ্বাস করো শুধু তোমার নিজের Proxy-র যোগ করা এন্ট্রি: সামনে একটা বিশ্বস্ত Proxy থাকলে ডানের শেষ মানটা নাও; দুটো থাকলে ডান থেকে দ্বিতীয়টা। আরও ভালো: সবচেয়ে বাইরের Proxy Header-টা পুরো নতুন করে লিখুক। Framework-গুলোতে ঠিক এর জন্যই Setting আছে (“Trusted Proxies”, “Hop-এর সংখ্যা”), আর এটা ভুল হলে Rate Limiting, Geo-blocking, Audit Log আর IP Allowlist সব ভেঙে পড়ে।

#একটা Request-এর পিছু নেওয়া: X-Request-ID আর traceparent

একটা Click একটা CDN, একটা Load Balancer, একটা API Gateway আর পাঁচটা Service পেরোতে পারে। ওই Click-এর সব Log লাইন খুঁজে পেতে Edge-এ Request-কে একটা ID দাও, তারপর সেটা হাতে হাতে পাঠাও:

  • X-Request-ID (বা X-Correlation-ID): Client না পাঠালে প্রথম Proxy একটা Random ID বানায়, সেটা প্রতিটা Service-এ যায়, প্রতিটা Log লাইনে লেখা হয়, আর Response-এও ফেরত আসে যাতে ব্যবহারকারী Support-কে নম্বরটা বলতে পারে।
  • traceparent (W3C Trace Context Standard, OpenTelemetry এটাই ব্যবহার করে): 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 = Version, ১৬ Byte-এর Trace ID, ৮ Byte-এর Parent Span ID, Flag (01 = Sample করা)। প্রতিটা Service Trace ID রেখে দেয় আর Parent ID-র জায়গায় নিজের Span বসায়, ফলে Tracing Tool পুরো Call-এর গাছটা আঁকতে পারে। tracestate বাড়তি Vendor তথ্য বয়ে নেয়। Chapter 46 এর ওপরেই দাঁড়াবে।

#Security Header, সংক্ষেপে

বেশিরভাগ Security Header এক লাইনের, খরচও নেই, তাই Edge-এ এক জায়গা থেকে বসিয়ে দাও। আক্রমণগুলো বোঝাবে Chapter 35; এখানে শুধু কোন Header কী কাজে।

Headerউদাহরণকী করে
Strict-Transport-Security (HSTS)max-age=31536000; includeSubDomainsএখন থেকে এই Site-এ Browser শুধু HTTPS ব্যবহার করবে (Lesson ১.৯)
Content-Security-Policy (CSP)default-src 'self'; script-src 'self' 'nonce-r4nd0m'কোন উৎস থেকে Script, Style, Image, Frame আসতে পারবে; Output Escaping-এর পরে XSS-এর বিরুদ্ধে সবচেয়ে শক্ত প্রতিরক্ষা
X-Content-Type-OptionsnosniffBrowser-কে Content-Type-ই মানতে হবে, আন্দাজ (“Sniff”) করে Upload করা Text File-কে Script ভাবা চলবে না
Content-Security-Policy: frame-ancestors / X-Frame-Optionsframe-ancestors 'none' / DENYকে Page-টা Frame-এ বসাতে পারবে (Clickjacking আটকায়)
Referrer-Policystrict-origin-when-cross-origin (Browser-এর Default)বর্তমান URL-এর কতটুকু অন্য Site-এ Referer-এ যাবে
Permissions-Policycamera=(), geolocation=()Page-এর দরকার নেই এমন শক্তিশালী Browser Feature বন্ধ করে
Access-Control-Allow-Originhttps://shop.example.comCORS-এর অনুমতি, এই Lesson-এর শেষে বিস্তারিত

তথ্য ফাঁস করে এমন জিনিসও সরাও: Server-এ Version নম্বর, আর X-Powered-By: Express বা PHP-র পরিচয়। পুরনো X-XSS-Protection Header এখন অচল; তার বদলে CSP ব্যবহার করো।

#Keep-alive আর Persistent Connection

ছোট একটা Request-এর সবচেয়ে দামি অংশ হলো Connection খোলা: TCP-তে এক Round Trip, TLS 1.3-এ আরও এক (TLS 1.2-তে দুই), তারপর TCP শুরু করে ছোট Congestion Window নিয়ে (Lesson ১.১০)। ৫০ ms-এর পথে প্রথম Request Byte যাওয়ার আগেই ১০০ ms, অথচ Response বানাতে হয়তো লাগে ৫ ms।

  • HTTP/1.0 প্রতিটা Response-এর পরে Connection বন্ধ করত। কিছু Client আর Server বারবার ব্যবহারের জন্য একটা অনানুষ্ঠানিক Connection: keep-alive Header জুড়ে দিত।
  • HTTP/1.1 টিকে থাকাকেই নিয়ম বানিয়েছে: কোনো পাশ Connection: close না পাঠালে Response-এর পরেও Connection খোলা থাকে। তাই HTTP/1.1 Request-এ Connection: keep-alive লেখা বাড়তি।
  • ঐচ্ছিক Keep-Alive: timeout=5, max=100 Response Header একটা ইঙ্গিত মাত্র: “অলস অবস্থায় ৫ সেকেন্ড পর্যন্ত আর সর্বোচ্চ ১০০টা Request পর্যন্ত এই Connection রাখব”। Client চাইলে উপেক্ষা করতে পারে; HTTP/2 আর HTTP/3-এ এটা নেই, বরং নিষেধ।
  • কী বাঁচে: প্রথমটার পরের প্রতিটা Request-এ TCP Handshake আর TLS দর-কষাকষি, সেই Handshake-এর Server CPU, TIME_WAIT-এ পড়ে থাকা Socket, আর ঠান্ডা Connection-এর Slow Start।
  • HTTP/1.1-এর সীমা: এক Connection-এ একবারে একটাই Request (Browser-এ Pipelining কখনো চালু হয়নি), তাই Browser প্রতি Host-এ প্রায় ৬টা Connection খোলে। HTTP/2 প্রতি Origin-এ একটা Connection-এই সব চালায়; HTTP/3 একই কাজ করে QUIC-এর ওপর।

Persistent Connection: Keep-alive কেন জরুরি

Round Trip ৫০ ms। কোনো HTTP Byte যাওয়ার আগেই TCP Handshake-এ এক Round Trip, আর TLS 1.3-এ আরও এক।

পরিস্থিতি

HTTP/1.0-এর আচরণ, বা যেকোনো Client যে Connection: close পাঠায়।

Browser203.0.113.7100 ms
Servershop.example.com
TCP + TLS Handshake
ধাপ 1 / 5
1

প্রথম Request-এর আগে: TCP Handshake আর TLS Handshake, প্রায় ২ Round Trip = ১০০ ms শুধু অপেক্ষা।

#Idle Timeout, আর যে দৌড় থেকে 502 আসে

অলস Connection Memory, একটা File Descriptor আর TLS State আটকে রাখে, তাই প্রতিটা Server একটা Idle Timeout-এর পরে এগুলো বন্ধ করে, অনেকে আবার N সংখ্যক Request-এর পরেও। চেনা Default:

SoftwareIdle Timeout-এর DefaultRequest-এর সীমা
Apache (KeepAliveTimeout, MaxKeepAliveRequests)৫ s১০০ (নোটের উদাহরণ)
Nginx (keepalive_timeout, keepalive_requests)৭৫ s১০০০
Node.js HTTP Server (server.keepAliveTimeout)৫ sনেই
Gunicorn (--keep-alive)২ sনেই
AWS Application Load Balancer (Idle Timeout)৬০ s (১–৪০০০ s)নেই

ঝামেলা শুরু হয় যখন এদের দুজন মুখোমুখি হয়। Load Balancer প্রতিটা Backend-এর সাথে Connection-এর একটা Pool রাখে আর বারবার ব্যবহার করে। Backend-এর Timeout যদি Load Balancer-এরটার চেয়ে ছোট হয়, তাহলে Backend একটা অলস Connection ঠিক সেই মুহূর্তে বন্ধ করে যখন Load Balancer তার ওপর নতুন Request পাঠাচ্ছে। Request গিয়ে পড়ে বন্ধ Socket-এ, Backend TCP Reset দিয়ে উত্তর দেয়, আর Load Balancer ফেরত দেয় 502 Bad Gateway। খুব অল্প Request-এ ঘটে, কম Traffic-এ বেশি, আর App-এর Log-এ কিছুই থাকে না। Cloud Load Balancer-এর পেছনে “হঠাৎ হঠাৎ 502”-এর এটা অন্যতম প্রধান কারণ।

নিয়মটা হলো: পুরো শিকলে, যে পাশ Request পাঠায়, অলস Connection তাকেই আগে বন্ধ করতে হবে। তাই Backend-এর Keep-alive Timeout Load Balancer-এর Idle Timeout-এর চেয়ে বেশি হবে (যেমন ৬০ s-এর ALB-এর পেছনে ৬৫ s), আর Client-এর Pool Timeout হবে তার Server-এরটার চেয়ে কম।

Load Balancer-এর পেছনে Keep-alive Timeout-এর দৌড়

Load Balancer App-এর সাথে Connection-এর একটা Pool রাখে আর সেগুলো বারবার ব্যবহার করে। অলস Connection কে আগে বন্ধ করে, তার ওপরই নির্ভর করে Request ব্যর্থ হবে কি না।

পরিস্থিতি

একটা চিরচেনা ঘটনা: Node.js App (Default Keep-alive ৫ s) এমন Load Balancer-এর পেছনে যার Idle Timeout ৬০ s।

Client
Load BalancerIdle Timeout ৬০ sPool-এর Conn #৭
App ServerKeep-alive ৫ s
GET /a
ধাপ 1 / 6
1

Load Balancer Pool-এর ৭ নম্বর Connection-এ একটা Request পাঠায় আর উত্তর পায়। Connection আবার Pool-এ ফিরে যায়।

#Connection Pool

প্রতিটা গুরুতর Client একটা Pool রাখে: Browser প্রতি Origin-এ, HTTP Library প্রতি Host-এ, Load Balancer আর Reverse Proxy প্রতি Backend-এ, আর Database Driver প্রতি Database-এ। যে Setting-গুলো জানা দরকার:

  • প্রতি Host-এ সর্বোচ্চ Connection: Backend-এর ওপর চাপ বেঁধে রাখে; খুব কম হলে Request Client-এর ভেতরেই লাইনে দাঁড়ায়।
  • Idle Timeout: Server-এরটার চেয়ে ছোট, ওপরের কারণে।
  • সর্বোচ্চ আয়ু: কয়েক মিনিট পরপর Connection নতুন করো, যাতে DNS বা Load Balancer-এর পেছনে আসা নতুন Backend-ও Traffic পায়, আর Scale-out-এর পরে দীর্ঘজীবী Connection সব চাপ পুরনো Server-এ আটকে না রাখে।
  • যাচাই বা আবার চেষ্টা: Pool-এর Connection হয়তো অন্য পাশ বন্ধ করে দিয়েছে। Client নিজে থেকে আবার পাঠায় শুধু সেই Idempotent Request, যা কোনো Response Byte আসার আগেই ব্যর্থ হয়েছে (Lesson ২.২); POST-এর জন্য লাগে Idempotency Key।
  • Reverse Proxy থেকে Backend: Nginx ঐতিহাসিকভাবে Configure না করলে Upstream Connection আবার ব্যবহার করত না (upstream Block-এ keepalive 32;, সাথে proxy_http_version 1.1 আর ফাঁকা Connection Header)। এটা ছাড়া প্রতিটা Request App-এর দিকে নতুন Connection খোলে, আর চাপের সময় Ephemeral Port ফুরিয়ে যায় (Lesson ১.৯)।

#HTTP Keep-alive আর TCP Keepalive এক জিনিস না

নামে মিল, আর কোনো মিল নেই:

HTTP Keep-alive (Persistent Connection)TCP Keepalive
উদ্দেশ্যএকটা Connection অনেক Request-এ আবার ব্যবহারঅলস Connection-এ মরা সঙ্গী ধরা
কে করেHTTP স্তর (Connection Header, Server-এর Timeout)OS Kernel (SO_KEEPALIVE, খালি Probe Segment)
DefaultHTTP/1.1-এ চালুবন্ধ; চালু করলে Linux ২ ঘণ্টা অলস থাকার পরে Probe পাঠায়
অলস থাকলে কী হয়Idle Timeout-এর পরে Server Connection বন্ধ করেProbe NAT আর Firewall-এর Mapping বাঁচিয়ে রাখে, মরা সঙ্গী খুঁজে বের করে

TCP Keepalive আর Application Heartbeat (HTTP/2-এর PING Frame, WebSocket Ping) বিস্তারিত আছে Lesson ১.১০-এ।

#Compression

লেখা খুব ভালো Compress হয়: HTML, CSS, JavaScript, JSON, SVG আর XML ভরা থাকে একই শব্দ আর Markup-এ। পাঠানোর আগে এগুলো ছোট করলে Bandwidth বাঁচে আর Page তাড়াতাড়ি আসে, বিশেষ করে Mobile Network-এ।

#কীভাবে ঠিক হয়

  1. Client জানায় সে কী কী খুলতে পারে: Accept-Encoding: gzip, deflate, br, zstd (চাইলে q-value সহ, যেমন br;q=1.0, gzip;q=0.8)।
  2. Server নিজের জানা একটা বেছে নেয়, Body Compress করে, আর লেবেল লাগায়: Content-Encoding: br। Content-Length এখন Compress করা মাপ।
  3. সাথে দেয় Vary: Accept-Encoding, যাতে Cache প্রতিটা Encoding-এর আলাদা কপি রাখে (Vary বোঝাবে Lesson ২.৪)।
  4. Browser খুলে Render করে। Client কোনো Accept-Encoding না পাঠালে Server-এর উচিত Body যেমন আছে তেমন পাঠানো (identity Coding)।

Content-Encoding End-to-end, এটা Representation-এর বর্ণনা; Hop-by-hop Transfer-Encoding (chunked) একদম আলাদা জিনিস। Request-এর Body Compress করা (POST-এ Content-Encoding) সম্ভব, কিন্তু কম দেখা যায়, আর Server স্পষ্টভাবে সমর্থন করলেই চলে; নাহলে 415 আশা করো।

#gzip বনাম Brotli বনাম zstd

gzipBrotli (br)Zstandard (zstd)
উৎপত্তি১৯৯২; Deflate Algorithm (LZ77 মিল খোঁজা + Huffman Coding)Google, ২০১৫; Standard ২০১৬-তেMeta (Facebook), ২০১৬; HTTP Coding হিসেবে Standard ২০২৪-এ
লেখায় সাধারণ সাশ্রয়আসলের চেয়ে ৭০–৮০% ছোটউঁচু Level-এ gzip-এর চেয়ে ১৫–২৫% ছোট (প্রায় ১২০ KB-এর চেনা Web লেখার একটা Built-in Dictionary সাহায্য করে)মাঝারি Level-এ প্রায় Brotli-র কাছাকাছি, Compress করতে অনেক দ্রুত
গতিদুই দিকেই দ্রুতসর্বোচ্চ Level (১০–১১) খুব ধীর: Static File আগে থেকে Compress করার জন্য; Dynamic Response-এ Level ৪–৬সব Level-এ Compress আর খোলা দুটোই খুব দ্রুত
Browser-এর সমর্থন১৯৯০-এর দশক থেকে সব Clientসব আধুনিক Browser, শুধু HTTPS-এChromium Browser আর Firefox, ২০২৪ থেকে
কোথায় সেরাসবার জন্য নিরাপদ বিকল্পBuild-এর সময় একবার Compress করা Static AssetDynamic Response (API, Server-এ Render করা HTML), যেখানে CPU-র হিসাব জরুরি

এই Lesson-এর Animation-এর হিসাব: ৪১২ KB-এর একটা JavaScript Bundle gzip-এ হয় প্রায় ১১৮ KB, আর Level 11-এর Brotli-তে ৯৮ KB। Static File Build-এর সময় আগেই Compress করে রাখো (app.js.br, app.js.gz), Server ঠিক File-টা বেছে দেবে; Dynamic Response চলতে চলতে মাঝারি Level-এ Compress করো। অনেক CDN এর সবটাই তোমার হয়ে করে দেয়।

Compression: Algorithm কে বেছে নেয়

Client জানায় সে কী কী খুলতে পারে; Server একটা বেছে নিয়ে Body-তে লেবেল লাগায়। app.js আসলে ৪১২ KB, gzip-এ প্রায় ১১৮ KB, Brotli-তে ৯৮ KB।

পরিস্থিতি

CDN-এর মধ্য দিয়ে সাধারণ দর-কষাকষি।

Browser
CDNশেয়ার করা Cache
Origin Server
GET /app.js · Accept-Encoding: gzip, deflate, br, zstd
ধাপ 1 / 6
1

Accept-Encoding জানায় Browser কোন কোন Coding খুলতে পারে। Browser br আর zstd শুধু HTTPS-এই জানায়।

#কী Compress করবে না

  • আগেই Compress করা Format: JPEG, PNG, WebP, AVIF, GIF, MP4 আর অন্য Video, MP3, WOFF2 Font, ZIP, ভেতরে Compress করা Stream সহ PDF। gzip এদের ০–২% বাঁচায়, উল্টো বড়ও করে ফেলতে পারে, অথচ দুই পাশেই CPU খায়।
  • খুব ছোট Response: প্রায় ১ KB-এর নিচে যা বাঁচে তা এক Packet-এর চেয়েও কম, আর Compression-এর নিজস্ব Header মিলে উল্টো বড় হতে পারে।
  • যে Response-এ গোপন তথ্য আর Attacker-এর লেখা মেশানো, যদি না BREACH-এর বিরুদ্ধে ব্যবস্থা থাকে (নিচে)।
  • Default-এর ফাঁদ: Nginx-এ Default হলো gzip off, আর তুমি application/json, text/css, application/javascript, image/svg+xml যোগ না করা পর্যন্ত gzip_types শুধু text/html ধরে। “Mobile-এ আমাদের API ধীর” ধরনের অনেক Ticket-এর পেছনে থাকে Compress না করা JSON।

#Compression Dictionary Transport

সাধারণ Compression প্রতিটা Response-এ শূন্য থেকে শুরু করে। Compression Dictionary Transport (২০২৫ সালে IETF-এ Standard হয়েছে; Chrome-এ Version 130 থেকে) আগে নামানো একটা File-কে পরেরটার Dictionary বানিয়ে দেয়:

  1. Server app.v1.js পাঠায় Use-As-Dictionary: match="/app.*.js" সহ। Browser এটাকে মিলে যাওয়া URL-এর জন্য Dictionary হিসেবে রেখে দেয়।
  2. Deploy-এর পরে Browser app.v2.js চায় Available-Dictionary: :hash-of-v1: সহ (রাখা File-টার SHA-256 Hash), আর Accept-Encoding-এ যোগ করে Dictionary Coding dcb (Brotli) আর dcz (zstd)।
  3. Server-এর কাছে v1 থাকলে সে v2-কে v1-এর সাপেক্ষে Compress করে উত্তর দেয় Content-Encoding: dcz: প্রায়ই শুধু বদলানো Byte-গুলো, ১০০ KB-এর বদলে কয়েক KB। Response-কে Available-Dictionary অনুযায়ীও Vary করতে হয়।

ঘনঘন Deploy হওয়া JavaScript Bundle-এ, আর বড় একই কাঠামোর API Response-এ (সাধারণ Response থেকে বানানো Custom Dictionary দিয়ে) এটা দারুণ কাজ দেয়।

#Compression যখন গোপন তথ্য ফাঁস করে: CRIME আর BREACH

Compression বারবার আসা লেখা খুঁজে দ্বিতীয় কপিটাকে একটা ছোট পেছনের-রেফারেন্স দিয়ে বদলে দেয়। Attacker যদি গোপন তথ্যের সাথে একই Compress করা Message-এ নিজের অনুমান ঢোকাতে পারে, তাহলে ঠিক অনুমানে Message ছোট হয়ে যায়, আর সেই দৈর্ঘ্য TLS-এর ভেতর দিয়েও দেখা যায়।

  • CRIME (২০১২) আক্রমণ করেছিল TLS স্তরের Compression-এ (আর SPDY-র Header Compression-এ), Cookie চুরি করতে। এরপর সব জায়গায় TLS Compression বন্ধ হয়, আর TLS 1.3 এটা বাদই দিয়েছে; HPACK এমনভাবে বানানো যাতে Header Compression এভাবে ফাঁস না করে।
  • BREACH (২০১৩) আক্রমণ করে HTTP Body-র Compression-এ। এর জন্য তিনটা জিনিস একসাথে লাগে: Body Compress করা, তাতে গোপন তথ্য আছে (একটা CSRF Token), আর তাতে Attacker-এর দেওয়া লেখা ফিরে আসে (URL-এর একটা Search শব্দ)। Attacker-এর Page ভুক্তভোগীর Browser-কে দিয়ে হাজারো অনুমান পাঠায়, আর Network-এ বসে Response-এর মাপ দেখে।

প্রতিরক্ষা, যেকোনো একটাই আক্রমণ ভেঙে দেয়: প্রতি Response-এ গোপন তথ্যকে মুখোশ পরাও (প্রতিবার Token-কে একটা নতুন Random মানের সাথে XOR করো, Django আর Rails যেমন করে), SameSite Cookie যাতে Cross-site Request লগইন ছাড়াই আসে, গোপন তথ্য আর ফিরে আসা লেখা মেশানো Response Compress কোরো না, আর Rate Limit দাও। BREACH-এর জন্য কেউ পুরো Site-এর Compression বন্ধ করে না।

BREACH: Compression যখন গোপন তথ্য ফাঁস করে

Compression বারবার আসা লেখাকে একটা ছোট পেছনের-রেফারেন্স দিয়ে বদলে দেয়। Attacker-এর বাছাই করা লেখা যদি কোনো গোপন তথ্যের সাথে মিলে যায়, Response ছোট হয়, আর TLS-এর ভেতর দিয়েও সেই দৈর্ঘ্য দেখা যায়।

পরিস্থিতি

Page-এ একটা গোপন Token আছে, Search-এর শব্দটা Page-এ ফিরে আসে, আর Body Compress করা।

Attacker-এর Scriptভুক্তভোগীর Browser-এ
Wi-Fi-তে Attackerশুধু মাপ দেখে
Bank Server
GET /search?q=token=a
ধাপ 1 / 4
1

ভুক্তভোগী Attacker-এর Page খুলেছে; তার Script Browser-কে দিয়ে Bank-এ Request পাঠায়, Bank-এর Cookie সহ। Script উত্তরগুলো পড়তে পারে না।

#CORS, শুরু থেকে শেষ

#Same-origin Policy

Browser অনেক Site-এর Code পাশাপাশি চালায়, সবগুলোর হাতেই ব্যবহারকারীর Cookie। কোনো নিয়ম না থাকলে তুমি যে Page-এই যাও, তার Script তোমার Cookie দিয়ে তোমার Bank-এর API Call করে তোমার Balance পড়ে ফেলতে পারত। Same-origin Policy (SOP) এটা নিষেধ করে: একটা Script অবাধে Response পড়তে পারে শুধু নিজের Origin থেকে।

Origin মানে Scheme + Host + Port। https://shop.example.com-এর (Port 443 ধরে নেওয়া) সাথে তুলনা:

URLএকই Origin?কেন
https://shop.example.com/cart?x=1✅ হ্যাঁPath আর Query গোনা হয় না
https://shop.example.com:443/✅ হ্যাঁhttps-এর Default Port 443
http://shop.example.com❌ নাScheme আলাদা (আর Port 80)
https://api.example.com❌ নাHost আলাদা, যদিও একই Domain-এর Subdomain
https://example.com❌ নাHost আলাদা
https://shop.example.com:8443❌ নাPort আলাদা

Site আরেকটু বড়: Scheme আর নিবন্ধনযোগ্য Domain। shop.example.com আর api.example.com একই Site, কিন্তু আলাদা Origin। এজন্যই SameSite Cookie (Lesson ১.৩, এটা Site ধরে কাজ করে) এদের মধ্যে যায়, অথচ Same-origin Policy (Origin ধরে) তখনো খাটে।

নিয়মটা মানুষ যতটা ভাবে তার চেয়ে সরু। এটা অন্য জায়গায় Request পাঠানো বা অন্য Origin-কে বসানো আটকায় না: <img>, <script>, <link rel=stylesheet>, <iframe>, <video> আর HTML Form জমা দেওয়া, সবই Cross-origin চলে, ১৯৯০-এর দশকে যেমন চলত। এটা আটকায় পড়া: Script Cross-origin fetch-এর Response দেখতে পারে না, Canvas-এ আঁকা Cross-origin Image-এর Pixel পড়তে পারে না, Cross-origin Frame-এর ভেতরে উঁকি দিতে পারে না।

#CORS: Server নিজে দরজা খোলে

Cross-Origin Resource Sharing (CORS) হলো নিয়ন্ত্রিত ব্যতিক্রম। Browser Server-কে জানায় কোন Origin জিজ্ঞেস করছে (Origin Request Header, Browser নিজে বসায়, Page-এর Script এটা জাল করতে পারে না), আর Server Access-Control-* Header দিয়ে জানায় কে পড়তে পারবে। উত্তরে Page-এর Origin-কে অনুমতি না দিলে Browser Response-টা Script-এর কাছ থেকে লুকিয়ে রাখে।

মানে Same-origin Policy হলো দেয়াল, আর CORS হলো সেই দরজা যেটা Server খুলবে কি না ঠিক করে। নোটের উদাহরণ: https://shop.example.com-এর একটা Front-end https://api.example.com-কে Call করছে। Browser দেখে API ওই Origin-কে অনুমতি দিয়েছে কি না; দিলেই কেবল JavaScript Response পড়তে পারে।

#Simple Request

কিছু Request CORS আসার আগেই সাধারণ HTML Form দিয়ে পাঠানো যেত, তাই CORS এগুলো সরাসরি পাঠায়, বাড়তি কোনো Round Trip ছাড়া। Request Simple হয় যখন:

  • Method হলো GET, HEAD বা POST;
  • এটা শুধু CORS-safelisted Header বসায়: Accept, Accept-Language, Content-Language, Content-Type, Range;
  • Content-Type থাকলে সেটা application/x-www-form-urlencoded, multipart/form-data বা text/plain-এর একটা।

Browser Origin সহ Request পাঠায়, Server উত্তর দেয়, তারপর Browser Access-Control-Allow-Origin মেলায় Page-এর Origin-এর সাথে। এর দুটো ফল মানুষকে অবাক করে:

  1. আটকানো Request-ও Server-এ পৌঁছেছে। Handler চলেছে, Database-এ Query হয়েছে, 200 Log হয়েছে। শুধু Page-এর Script ফলটা পায়নি, পেয়েছে একটা সাধারণ TypeError: Failed to fetch; কারণটা থাকে Developer Console-এ, ইচ্ছা করেই Code-এর চোখের আড়ালে।
  2. আটকানো থাকলেও একটা Simple POST Data বদলে দিতে পারে। এজন্যই CSRF-এর বিরুদ্ধে CORS কিছুই করে না; তার জন্য লাগে SameSite Cookie, CSRF Token বা Origin যাচাই (Lesson ১.৩, Chapter 35)।

Default অবস্থায় Cross-origin Script শুধু Safelisted Response Header পড়তে পারে (Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified, Pragma)। অন্যগুলো, যেমন X-Request-ID, ETag বা Location পড়াতে হলে Server সেগুলোর নাম দেয় Access-Control-Expose-Headers-এ।

Simple Request-এ CORS

Page আর API-এর Host আলাদা, তাই এরা আলাদা Origin। Simple GET সাথে সাথে পাঠানো হয়; Page উত্তরটা পড়তে পারবে কি না, Browser সেটা পরে ঠিক করে।

পরিস্থিতি

API Page-এর Origin-এর নাম বলে দেয়।

Browserpage: https://shop.example.com
APIhttps://api.example.com
curl / কোনো ServerBrowser নেই
fetch('https://api.example.com/products')
ধাপ 1 / 4
1

Scheme আর Port এক, Host আলাদা, তাই এটা Cross-origin Request। Custom Header ছাড়া GET হলো “Simple” Request: Preflight লাগে না।

#Preflight করা Request

বাকি সব (PUT, PATCH বা DELETE; Authorization বা নিজের বানানো Header; Content-Type: application/json) এমন কাজ করতে পারে যা পুরনো কোনো Form পারত না। পুরনো Server অন্য Site থেকে এমন Request আশাই করত না, তাই Browser আগে একটা OPTIONS Request দিয়ে অনুমতি চায়, এটাই Preflight:

OPTIONS /users/42 HTTP/1.1
Host: api.example.com
Origin: https://shop.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-type
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://shop.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Max-Age: 600
Vary: Origin

Preflight-কে এমন একটা 2xx উত্তর পেতে হবে যেটা Origin, Method আর তালিকার প্রতিটা Header-এর অনুমতি দেয়। তবেই আসল PUT যায়, আর তার Response-এরও নিজস্ব Access-Control-Allow-Origin লাগে। Preflight কখনো Cookie, Authorization বা Body বহন করে না, আর এখান থেকেই আসে CORS-এর সবচেয়ে চেনা Bug: Authentication Middleware CORS Handler-এর আগে চলে আর Preflight-কে 401 দিয়ে ফিরিয়ে দেয়।

Preflight-এ একটা Round Trip খরচ। Access-Control-Max-Age দিলে Browser উত্তরটা Cache করে রাখে, কিন্তু শুধু Origin আর URL ধরে, আর Browser এর একটা সীমাও বাঁধে (Chrome ২ ঘণ্টা, Firefox ২৪ ঘণ্টা; Header না থাকলে ৫ সেকেন্ড)। Path-এ ID থাকা REST API (/users/42, /users/43) প্রতিটা নতুন URL-এর জন্য Preflight দেয়।

Headerদিকমানে
OriginRequestযে Page Call করছে তার Origin (Browser বসায়)
Access-Control-Request-MethodPreflight Requestআসল Request কোন Method ব্যবহার করবে
Access-Control-Request-HeadersPreflight RequestSafelisted-এর বাইরের কোন Header-এর নাম পাঠাবে
Access-Control-Allow-OriginResponseকোন Origin পড়তে পারবে: হুবহু একটা Origin, *, বা (বিপজ্জনকভাবে) null
Access-Control-Allow-MethodsPreflight Responseঅনুমোদিত Method
Access-Control-Allow-HeadersPreflight Responseঅনুমোদিত Request Header
Access-Control-Allow-CredentialsResponsetrue হলে Cookie বা HTTP Auth সহ Request-এর উত্তর পড়া যাবে
Access-Control-Max-AgePreflight ResponsePreflight-এর ফল কত সেকেন্ড Cache করা যাবে
Access-Control-Expose-HeadersResponseJavaScript বাড়তি কোন Response Header পড়তে পারবে

Access-Control-Allow-Origin-এ তালিকা রাখা যায় না। কয়েকটা Origin-কে অনুমতি দিতে হলে Server Request-এর Origin নিজের অনুমোদিত তালিকার সাথে মিলিয়ে যেটা মিলল সেটাই ফেরত লেখে, আর সাথে দেয় Vary: Origin, যাতে কোনো Cache এক Origin-এর উত্তর আরেকজনকে না দেয়।

Preflight: পাঠানোর আগে জিজ্ঞেস করা

JSON Body আর Authorization Header সহ একটা PUT এমন কাজ করতে পারে যা ১৯৯০-এর দশকের কোনো Form পারত না, তাই Browser আগে OPTIONS দিয়ে Server-কে জিজ্ঞেস করে।

পরিস্থিতি

Server Method আর দুটো Header-ই অনুমতি দেয়।

Browserpage: https://shop.example.com
APIhttps://api.example.com
fetch(url, { method: 'PUT', headers: { Authorization, 'Content-Type': 'application/json' } })
ধাপ 1 / 5
1

তিনটা জিনিস এটাকে Simple থাকতে দেয় না: PUT Method, Authorization Header আর application/json Content Type। এর যেকোনো একটাই Preflight ডেকে আনত।

নিজেকে যাচাই করোপ্রশ্ন

https://shop.example.com-এর একটা Page চালায় fetch('https://api.example.com/orders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body })। Browser প্রথমে কী পাঠাবে?

Default অবস্থায় Cross-origin Request-এ fetch কোনো Cookie পাঠায় না। Page-কে নিজে বলতে হয় credentials: 'include' (পুরনো XMLHttpRequest-এ withCredentials)। তখন Response পড়া যায় শুধু যদি:

  • Access-Control-Allow-Credentials: true থাকে, আর
  • Access-Control-Allow-Origin-এ হুবহু Origin-টা লেখা থাকে, কখনো * না। Credential থাকলে Allow-Headers, Allow-Methods আর Expose-Headers-এর *-কেও Wildcard না ধরে আক্ষরিক নাম হিসেবে ধরা হয়।

কারণটা সোজা: * মানে “যেকোনো Site এটা পড়তে পারে”। ব্যবহারকারীর Cookie-র সাথে মিললে এটা যেকোনো Website-কে লগইন করা যেকোনো ব্যবহারকারীর Data পড়তে দিত, তাই নিয়মেই এই জোড়া নিষেধ। সত্যিকারের Public, Credential ছাড়া Data-র জন্য * ঠিক আছে, বরং সেটাই সঠিক: একটা Public API, CDN-এর Font বা Image।

Cookie-র নিজেরও নিয়ম আছে। একই Site-এর API-তে (shop.example.com থেকে api.example.com) SameSite=Lax Cookie যায়। অন্য Site-এর API হলে Cookie-তে লাগবে SameSite=None; Secure, আর যেসব Browser Third-party Cookie আটকায়, তারা তবুও ফেলে দিতে পারে। Web App-এর API নিজের Site-এ, আরও ভালো হয় নিজের Origin-এ রাখার এটা আরেকটা কারণ।

Credential সহ CORS আর এর ভুল Configuration

ব্যবহারকারী একটা Session Cookie দিয়ে api.example.com-এ লগইন করা। কোন Page সেই ব্যবহারকারীর Data পড়তে পারবে?

পরিস্থিতি

credentials: include আর Access-Control-Allow-Origin: * একসাথে কখনো চলে না।

BrowserAPI-এর Cookie আছেpage: shop.example.com
APIhttps://api.example.com
GET /me · Cookie: sid=… (credentials: 'include')
ধাপ 1 / 3
1

Page Browser-কে বলে Cross-origin Call-এ Cookie জুড়ে দিতে।

CDN-এর পেছনে Vary: Origin কেন জরুরি

API দুটো Origin-কে অনুমতি দেয়, আর যে জিজ্ঞেস করে তারটাই ফেরত লেখে। সামনে একটা শেয়ার করা Cache বসানো।

পরিস্থিতি

যে Origin আগে জিজ্ঞেস করে, সে দ্বিতীয়জনের জন্য Cache নষ্ট করে দেয়।

Shop Pageshop.example.com
Admin Pageadmin.example.com
CDN
API
GET /catalog · Origin: shop
ধাপ 1 / 6
1

Shop Page আগে চায়।

#CORS Browser-এর সুরক্ষা, Server-এর Security না

নোটের Best Practice, কারণ সহ:

  • নির্দিষ্ট বিশ্বস্ত Origin-কেই অনুমতি দাও, একটা স্থির তালিকার সাথে পুরো String মিলিয়ে।
  • Public না এমন কিছুর জন্য Production-এ * ব্যবহার কোরো না।
  • Credential আর Wildcard Origin একসাথে চলে না, নকশাতেই এমন।
  • Cookie-ভিত্তিক Authentication-এ CSRF প্রতিরক্ষাও লাগবে, কারণ Simple Request তবুও চলে যায়।
  • CORS-কে Firewall ভেবো না।

#চেনা ভুল Configuration

ভুল Configurationকী ঘটেসমাধান
Allow-Credentials: true সহ যেকোনো Origin ফেরত লেখাযেকোনো Website লগইন করা যেকোনো ব্যবহারকারীর Data পড়তে পারে: CORS না থাকার চেয়েও খারাপনির্দিষ্ট অনুমোদিত তালিকা
null-কে অনুমতিSandboxed iframe, file:// Page আর কিছু Redirect Origin: null পাঠায়; Attacker ইচ্ছামতো এটা বানাতে পারেProduction-এ কখনো null-কে অনুমতি দেবে না
ঢিলেঢালা মেলানো: “example.com দিয়ে শেষ”, “https://shop.example.com দিয়ে শুরু”, Regex-এ Escape না করা বিন্দুevilexample.com, shop.example.com.evil.net বা shopxexample.com ঢুকে যায়হুবহু String মেলাও
সব Subdomain-কে বিশ্বাস*.example.com-এর যেকোনো একটায় XSS Bug বা বেদখল হওয়া Subdomain থেকেই API পড়া যায়যাদের দরকার শুধু সেই Subdomain-কে অনুমতি
CORS-এর আগে AuthPreflight পায় 401/403; Simple না এমন সব Call ব্যর্থAuthentication-এর আগে OPTIONS সামলাও
Origin ফেরত লেখার সময় Vary: Origin নেইCDN এক Origin-এর অনুমতি আরেকজনকে দেয়; প্রতিবার Cache মেয়াদ শেষে ব্যর্থতা পাল্টে যায়Vary: Origin দাও
Error-এ CORS Header নেইAllow-Origin ছাড়া 500 বা 401 Browser-এ অস্পষ্ট CORS Error হয়ে দেখা দেয়, আসল Status ঢাকা পড়েError Response-এও CORS Header দাও
দুবার Header যোগ (Proxy আর App দুজনেই)Access-Control-Allow-Origin: https://a, https://a অবৈধ, তাই আটকানোঠিক একটা স্তরে CORS বসাও

#CORS-কে মাথায় রেখে Design

  • পুরোপুরি এড়িয়ে যাও: Reverse Proxy বা CDN-এর Path Rule দিয়ে API-কে একই Origin-এর নিচে দাও (https://shop.example.com/api/… → API Service)। Preflight নেই, CORS Configuration নেই, Cookie First-party।
  • স্বাভাবিকভাবে যেখানে সম্ভব Call Simple রাখো: Custom Header ছাড়া GET-এ Preflight লাগে না।
  • যুক্তিসঙ্গত Access-Control-Max-Age দাও (যেমন ৬০০–৭২০০ s), আর সম্ভব হলে ব্যস্ত Preflight-ওয়ালা Endpoint-এর Path-এ অনন্য ID না রেখে GET-এ Query Parameter হিসেবে পাঠাও।
  • CORS এক জায়গায় Configure করো, API Gateway-তে বা একটা Middleware-এ, যাচাই করা অনুমোদিত তালিকা থেকে।

#System Design-এ Header, Keep-alive, Compression আর CORS

  • সবার জন্য খাটে এমন Header Edge-এ বসাও। Security Header, X-Request-ID, X-Forwarded-*-এর পরিচ্ছন্নতা, Compression আর CORS প্রতিটা Service-এ আলাদা না করে CDN, Load Balancer বা API Gateway-তে একবার ঠিকঠাক করা সবচেয়ে সহজ।
  • Timeout শিকলের মতো করে মাপো। Client Pool < Load Balancer-এর Idle Timeout < Backend-এর Keep-alive Timeout; আর Deploy-এর সময় Connection: close (বা HTTP/2 GOAWAY) দিয়ে Backend খালি করো।
  • সবখানে Connection আবার ব্যবহার করো: Browser থেকে Edge (HTTP/2 বা HTTP/3), Edge থেকে Backend (Upstream Keep-alive), Service থেকে Service (Client Pool), App থেকে Database (Connection Pool, Chapter 18)।
  • লেখা Compress করো, Media না, Static File আগেই Brotli দিয়ে Compress করে রাখো, gzip রাখো বিকল্প হিসেবে, Dynamic Response-এ zstd ভেবে দেখো, আর সবসময় Vary: Accept-Encoding পাঠাও।
  • Header-কে অবিশ্বস্ত Input ধরো: তোমার Infrastructure নতুন করে না লিখলে X-Forwarded-For, Host আর User-Agent সবই Client-এর লেখা।
  • নিজের Web Front-end-এর জন্য CORS-এর চেয়ে একই Origin ভালো; CORS রাখো সত্যিকারের আলাদা ব্যবহারকারীদের জন্য।
লক্ষণসম্ভাব্য কারণকোথায় দেখবে
Load Balancer-এর পেছনে মাঝেমধ্যে হঠাৎ 502, App-এর Log-এ কিছু নেইBackend-এর Keep-alive Timeout Load Balancer-এর Idle Timeout-এর চেয়ে ছোটদুটো Timeout; Backend > LB করো
প্রতিটা Deploy-এ 502ব্যবহারে থাকা Connection সহ Backend মেরে ফেলাDraining, Connection: close, ভদ্রভাবে বন্ধ
কিছু ব্যবহারকারী পায় 400/431, বাকিরা ঠিকHeader-এর আকারের সীমা ছাড়ানো Cookie-র স্তূপCookie-র সংখ্যা আর মাপ; প্রতিটা Hop-এর সীমা
Rate Limiter বা Geo-blocking সহজেই ফাঁকি দেওয়া যায়বাঁয়ের X-Forwarded-For-কে বিশ্বাসTrusted Proxy-র Setting
বড় JSON Response Mobile-এ ধীরCompression বন্ধ, বা gzip_types-এ JSON নেইcurl -H 'Accept-Encoding: br, gzip' -I
কিছু Client CDN থেকে হিজিবিজি Response পায়Vary: Accept-Encoding নেইCache করা Object-এর Response Header
Console-এ “CORS Error”, Server Log-এ 200Access-Control-Allow-Origin নেই বা ভুলNetwork Tab-এ Response Header
“CORS Error”, Server Log-এ শুধু OPTIONS 401Auth Middleware Preflight ফিরিয়ে দিচ্ছেMiddleware-এর ক্রম
এক Front-end-এ CORS চলে, আরেকটায় এলোমেলোভাবে ব্যর্থVary: Origin ছাড়া ফেরত লেখা Origin Cache হয়েছেCDN-এর Cache Key, Vary
প্রতিটা API Call-এ দুই Round Tripপ্রতি Call-এ Preflight (Custom Header, Path-এ ID, Max-Age নেই)একই Origin-এর Proxy, Max-Age, সরল Request

#নিজে করে দেখো

# Request আর Response-এর সব Header দেখো (-v), বা শুধু Response Header (-I)
curl -v https://example.com -o /dev/null
curl -sI https://example.com

# Compression চালু কি? Brotli বা gzip চাও, তারপর Content-Encoding আর Vary দেখো
curl -sI -H 'Accept-Encoding: br, gzip' https://example.com | grep -i -E 'content-encoding|vary|content-length'

# একই Resource-এর Compress করা মাপ তুলনা করো
curl -s -o /dev/null -w '%{size_download} bytes\n' https://example.com/
curl -s -o /dev/null -w '%{size_download} bytes\n' -H 'Accept-Encoding: gzip' https://example.com/

# Keep-alive: এক curl-এ দুটো URL দিলে Connection আবার ব্যবহার হয় ("Re-using existing connection")
curl -v https://example.com/ https://example.com/ -o /dev/null -o /dev/null 2>&1 | grep -i -E 're-using|connected to'

# নিজের API-তে একটা CORS Preflight সাজিয়ে পাঠাও, আর Access-Control-* উত্তর পড়ো
curl -i -X OPTIONS https://api.example.com/users/42 \
  -H 'Origin: https://shop.example.com' \
  -H 'Access-Control-Request-Method: PUT' \
  -H 'Access-Control-Request-Headers: authorization, content-type'

# ফেরত লেখার ভুল আছে কি না যাচাই: যে Origin-কে অনুমতি দেওয়ার কথা না, API কি তাকেও দিচ্ছে?
curl -sI https://api.example.com/me -H 'Origin: https://evil.example.net' | grep -i access-control

Browser-এ Developer Tools → Network খোলো, একটা Request-এ Click করে Headers পড়ো: Request আর Response Header, Cross-origin Call-এ Origin Header, আর OPTIONS Preflight আলাদা একটা সারি হিসেবে (Method দিয়ে Filter করো)। তোমার JavaScript যে CORS-এর কারণ দেখতে পায় না, Console সেটা হুবহু দেখায়। HTTP/2 Traffic-এ Pseudo-header দেখা যায় :authority, :method, :path আর :scheme হিসেবে।

#সারসংক্ষেপ

মূল শিক্ষা

  • Header হলো Case-insensitive নাম-মানের Metadata: Request, Response আর Representation Header। End-to-end Header প্রাপকের কাছে পৌঁছাতে হবে; Hop-by-hop Header (Connection, Keep-Alive, Transfer-Encoding, TE, Upgrade) প্রতিটা Proxy সরিয়ে ফেলে। HTTP/2 আর HTTP/3-তে ছোট হাতের নাম আর Pseudo-header (:method, :scheme, :authority, :path, :status)।
  • জরুরি Header: Host, User-Agent, Accept*, Content-Type, Content-Length, Authorization, Cookie/Set-Cookie, X-Forwarded-For/Forwarded (শুধু তোমার Proxy যা যোগ করে সেটুকু বিশ্বাস করো), X-Request-ID/traceparent, আর Security Header (HSTS, CSP, nosniff, frame-ancestors, Referrer-Policy)। Server-এর Version লুকাও; Header-এর আকারের সীমা মাথায় রাখো (431)।
  • Keep-alive: HTTP/1.1-এ Connection নিজে থেকেই টিকে থাকে; Connection: close বন্ধ করে; Keep-Alive: timeout=5, max=100 শুধু ইঙ্গিত, HTTP/2 আর HTTP/3-তে নিষেধ। বারবার ব্যবহারে Handshake, CPU আর Slow Start বাঁচে।
  • Idle Timeout: যে Request পাঠায় তাকেই আগে বন্ধ করতে হবে, তাই Backend-এর Timeout Load Balancer-এরটার চেয়ে বেশি (৬৫ s > ৬০ s), নাহলে দৌড় থেকে হঠাৎ হঠাৎ 502। Pool-এ লাগে ঠিকঠাক আকার, Idle Timeout, আয়ু আর শুধু Idempotent Request-এ আবার চেষ্টা। HTTP Keep-alive ≠ TCP Keepalive।
  • Compression: Accept-Encoding → Content-Encoding + Vary: Accept-Encoding। gzip ৭০–৮০% ছোট, সবাই বোঝে; Brotli gzip-এর চেয়ে ১৫–২৫% ছোট, Browser শুধু HTTPS-এ দেয়; zstd দ্রুত, ২০২৪ থেকে Chromium আর Firefox-এ। Image, Video, Font বা Archive Compress কোরো না। Dictionary Transport শুধু পার্থক্য পাঠায়; BREACH-এর জন্য লাগে Compression + গোপন তথ্য + ফিরে আসা Input।
  • Same-origin Policy: Origin মানে Scheme + Host + Port; Script Cross-origin Response পড়তে পারে না, যদিও Request পাঠাতে আর Resource বসাতে পারে। CORS দিয়ে Server Access-Control-* Header-এ অনুমতি দেয়।
  • Simple Request (GET/HEAD/POST, Safelisted Header আর Content Type) সরাসরি যায়, পরে লুকানো হয়; বাকি সবকিছুর আগে Credential ছাড়া একটা OPTIONS Preflight। Max-Age প্রতি URL ধরে Cache করে (Chrome ≤ ২ ঘণ্টা)। Credential-এ লাগে হুবহু Origin, Allow-Credentials: true আর Vary: Origin; কখনো * না।
  • CORS Browser-এর সুরক্ষা, Server-এর Security না: curl এটা মানে না, এটা CSRF থামায় না, আর যেকোনো Origin ফেরত লেখা বা null-কে অনুমতি দেওয়া ব্যবহারকারীর Data ফাঁস করে। পারলে API একই Origin থেকে দাও।