Foundationপ্রথম নীতি থেকে
LEVEL 7লেসন ২০/২২কঠিন৪৫ মিনিট

WebSocket — HTTP থেকে full-duplex upgrade

WebSockets

HTTP request-gate ভেঙে দুই-দিক-চালিত channel; Sec-WebSocket-Key-র GUID magic থেকে masked frame-এর proxy-poisoning history — আর scale-এ গেলে stateless-এর comfort কীভাবে হাতছাড়া।

এই লেসন শেষে আপনি পারবেন

  • Upgrade dance-এর প্রতিটা header ব্যাখ্যা করতে পারবেন — Upgrade, Connection, Sec-WebSocket-Key, আর Sec-WebSocket-Accept-এর GUID-magic computation
  • Frame format পড়তে পারবেন — FIN/opcode/MASK/payload-length — আর client-to-client masking বাধ্যতামূলক কেন (proxy cache poisoning history) বলতে পারবেন
  • Polling, long-polling, SSE, WebSocket — চার approach-এর latency/overhead/direction trade-off সাজিয়ে কোন use-case-এ কোনটা জেতে তা নির্ধারণ করতে পারবেন
  • Stateful connection-এর scaling pain বিশ্লেষণ করতে পারবেন — sticky sessions, per-connection memory, fan-out architecture — HTTP statelessness-এর সাথে তুলনা করে
  • WSS = WS over TLS এবং handshake থেকে echo server পর্যন্ত পুরো flow stdlib-only Python-এ implement করে দেখাতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

আজ পর্যন্ত আমাদের web-এর মূল ছন্দ একটাই: client জিজ্ঞেস করে, server উত্তর দেয়। Request-response। কিন্তু chat message কে deliver করবে? Game-এ opponent-এর move কে জানাবে? Collaborative editor-এ co-author type করলে সেটা আপনার screen-এ আসবে কীভাবে?

Server অপেক্ষা করতে পারে না — তার হাতে কোনো খোলা লাইনই নেই, যেটা দিয়ে সে ইচ্ছা হলেই কথা বলবে। প্রতিটা HTTP response-ই তার শেষ সুযোগ। এই একমুখী জগৎ ভেঙে দুই-দিক-চালিত (full-duplex) channel তৈরির protocol-ই WebSocket (RFC 6455, 2011)।

সবচেয়ে সুন্দর অংশটা: WebSocket শুরু হয় হুবহু একটা HTTP request দিয়ে — তারপর একটা চুক্তির মধ্যে দুই পক্ষ সম্মত হয় এই connection আর HTTP থাকবে না — এবং সেই একই TCP pipe হঠাৎ bidirectional message-channel হয়ে যায়। এই রূপান্তরটাকে বলে the upgrade dance — আজ আমরা তা step-by-step শিখব, frame-level পর্যন্ত, তারপর নিজের হাতে একটা server বানাব stdlib দিয়ে।

মূল ধারণা

Upgrade dance — চার header-এর হাত-মেলানো

Client পাঠায় একটা বৈধ HTTP GET, কিন্তু বিশেষ headers-সহ:

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket                    ← 'আমি protocol বদলাতে চাই'
Connection: Upgrade                   ← hop-by-hop: এই লক্ষ্যেই এই conn
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==    ← random 16-byte, base64
Sec-WebSocket-Version: 13

Server রাজি হলে ফেরত দেয়:

HTTP/1.1 101 Switching Protocols      ← নতুন status! 'protocol বদলালাম'
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

এরপর ওই TCP connection-এ আর কোনো HTTP নেই — চলব WebSocket frames, দুই দিকেই, যখন তখন।

Accept value-টার গণনা deterministic — এটাই dance-এর proof-of-work-lite:

Sec-WebSocket-Accept = base64( SHA1( key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ) )

ওই অদ্ভুত GUID-টা spec-এ fixed constant। কেন এই নাটক? Client যে key পাঠায় সেটা তো সে-ই জানে — server ‘আমি ঠিক আছি’ বলতে তার কী দরকার?

Frame format — wire-এর সূক্ষ্ম ব্যাকরণ

Connection upgrade হওয়ার পর সব data চলে frame-এ — ছোট binary unit:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| payload len |    extended length (optional) |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     masking key, length 4 (যদি MASK=1)                        |
+ - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - +
|                     payload data ...                          |
+---------------------------------------------------------------+
Fieldমানে
FINএই frame message-এর শেষ fragment কি না (বড় message ভাঙে)
opcode1=text, 2=binary, 8=close, 9=ping, 10=pong, 0=continuation
MASKpayload XOR-masked কি না
len7-bit; 126→পরের ২ byte, 127→পরের ৮ byte

Opcode-গুলোই control-plane: ping/pong হলো built-in keepalive — client/server idle-connection জীবিত রাখতে PING পাঠায়, peer তৎক্ষণাৎ PONG ফেরত দেয়; NAT/firewall-রা নিঃশব্দে idle UDP/TCP mapping মুছে ফেলে — ping-ই সেই ঘুম-ভাঙা। CLOSE (8) হলো graceful goodbye — status-code বহন করে (1000=normal, 1001=going away…)।

Masking — client MUST mask, server MUST NOT

Rule-টা asymmetric আর অদ্ভুত লাগে: browser-client প্রতিটা frame random 4-byte mask দিয়ে XOR করে পাঠাবেই, server unmasked পাঠাবে।

masked byte[i] = payload[i] XOR mask_key[i mod 4]

কারণটা ঐতিহাসিক নিরাপত্তা-ঘটনা — proxy cache poisoning: মনে করুন corporate network-এ একটা caching HTTP proxy। Malicious webpage browser-কে বলল attacker-server-এ WS connection open করতে; পুরনো proxy-রা এটাকে সাধারণ POST ভেবে ভেতরের content নিজের cache-এ বসিয়ে দিতে পারত — যাতে থাকত crafted HTTP request! পরে কোনো user ওই proxy দিয়ে victim-site request করলে cache থেকে attacker-এর response উঠে আসত। Masking এই attack-এর মূল অস্ত্র (attacker-chosen wire-bytes) নিষ্ক্রিয় করে: random mask-এর কারণে attacker wire-এ ঠিক কী bytes যাবে তা আগে থেকে predict করতে পারে না — cache-able request ‘forge’ করা অসম্ভব। Performance cost টুকু সেই নিরাপত্তার বীমা-premium।

ভেতরে কী ঘটছে

চার পথ, এক সমস্যা — realtime-এর menu

Server-push চাইলে পথ চারটা — খরচ-তুলনা করলে design-sense তৈরি হয়:

ApproachLatencyOverhead per updateDirectionState on server
Polling (৫ সে)≤5s + RTTপূর্ণ HTTP request/responsec→sনেই (stateless!)
Long-polling~RTTreconnect + full headersc→s triggerhanging request
SSE~RTT১টা persistent streams→c onlyএক open stream
WebSocket~RTTছোট frame (~৬ byte)bothপূর্ণ duplex session

SSE (Server-Sent Events) মানে: plain HTTP response যেটা কখনো শেষ হয় না (Content-Type: text/event-stream, data: lines), auto- reconnect built-in, HTTP-infra friendly — এক-directional feed (news ticker, dashboard push, AI-token streaming!) এ প্রায় সবসময় সঠিক choice। WebSocket জেতে শুধু তখনই যখন client-ও frequent পাঠায়: chat, game-state, collab-cursor, signaling। Polling জেতে update-rate খুব কম হলে (৫ মিনিট পর পর?) — complexity সবচেয়ে কম।

Scaling pain — stateless comfort হারানো

HTTP-জগতে load-balancer যে-কোনো request যে-কোনো replica-তে পাঠাতে পারে — সব replica সমান, session-state বাইরে (Redis/db)। WebSocket ভাঙে এই স্বাচ্ছন্দ্য:

                ┌── LB ──▶ replica-A ── ws-conn(user1)
user1 ──────────┤              ▲
user2 ──────────┼──────────────┼── ws-conn(user2)  ← এটা A-তেই থাকতে হবে!
                └────────────▶ replica-B

তিনটা ব্যথা: (১) Sticky sessions — একই user-এর connection সবসময় same-replica-তে (mid-air migrate হয় না); replica মরলে সব connection একসাথে drop। (২) Fan-out — user1-এর message user2-কে (ভিন্ন replica-তে) পৌঁছাতে pub/sub backbone (Redis/Kafka) লাগবেই। (৩) Memory-per-connection — open socket = fd + buffers + stack; ১০০k concurrent WS ≈ OS-tuning problem। HTTP-র ‘request-শেষে-সব-মুছে- যাওয়া’ আরামটাই এখানে মূল শত্রু।

উদাহরণ

পূর্ণ trace — handshake থেকে echo পর্যন্ত, byte হিসাব-সহ

# ১. Handshake (HTTP phase)
C→ GET /echo HTTP/1.1
     Upgrade: websocket / Connection: Upgrade
     Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
S→ HTTP/1.1 101 Switching Protocols
     Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

# যাচাই: SHA1(key + GUID) → base64 — আমরা build-এ নিজেই compute করব

# ২. Message phase (frame level)
C→ 81 82 7c 9e 4a 53 34 f7            ← masked TEXT 'Hi'
       │  │  └─ mask: 7c 9e 4a 53
       │  └─ MASK=1, len=2
       └─ FIN=1, op=1(text)
   unmask: 34^7c=48('H'), f7^9e=69('i')

S→ 81 02 48 69                         ← unmasked TEXT 'Hi'
       │  └─ MASK=0, len=2             ← server কখনো mask করে না!
       └─ FIN=1, op=1

# ৩. Keepalive
C→ 89 80 <mask>                        ← PING, len=0 (masked — client!)
S→ 8a 80                               ← PONG, len=0 (unmasked)

# ৪. Close
C→ 88 82 <mask> 03 e8                  ← CLOSE, code 1000 (03e8) normal
S→ 88 02 03 e8                         ← CLOSE echo — TCP close follows

Frame overhead-টা মেপে দেখুন: ‘Hi’ পাঠাতে ৮ byte (masked) — polling- এ একই কাজে যেত ২০০+ byte HTTP envelope। এই ব্যবধান-ই high-frequency app-এ WebSocket-এর আসল currency।

ছবিতে

Protocol-switch timeline — HTTP থেকে WS-তে রূপান্তরের মানচিত্র

 time ─────────────────────────────────────────────────────────────▶

 │◄────────────── HTTP phase ──────────────►│◄────── WS phase ─────────────────►│

 C│  GET /chat HTTP/1.1                      │  [msg]→      [msg]→              │
  │  Upgrade: websocket                       │  ◄[msg]          ◄[ping]         │
 S│                                           │               [pong]→           │
  │  101 Switching Protocols ◄── THE MOMENT   │        (idle NAT-timeout এড়াতে)  │
  │  Accept: SHA1(key+GUID)                   │  ◄[msg]                          │
  │                                           │                     [close]→     │
  │                                           │  ◄[close-echo]  TCP FIN          │

 দুই দিকেই যেকোনো মুহূর্তে যেকোনো সংখ্যক frame — কোনো request-gate নেই।
 LB-র দৃষ্টিতে: 101-এর পরে এটা opaque byte-pipe — path/header আর দেখা যায় না
 (WSS-এ তো encryption-ও — L7 routing তখন শুধু handshake-মুহূর্তেই সম্ভব)।
একই TCP connection-এর জীবন — phase 1-এ HTTP grammar, 101-এর মুহূর্তে grammar switch, তারপর symmetric frame-stream। Ping/pong-এর ছন্দ খেয়াল করুন।

নিজে চালিয়ে দেখুন

EXPERIMENT

Echo-server-এ live WS session — client লিখে পাঠান

macOS / Linux (python3, pip install websockets)· ১৫ মিনিট

ধাপ ১ — client install আর minimal script:

pip install websockets    # asyncio-based client/server library
# ws_client.py
import asyncio, websockets

async def main():
    async with websockets.connect('wss://echo.websocket.events') as ws:
        greet = await ws.recv()          # server প্রথমেই welcome-line পাঠায়
        print(f'welcome: {greet}')
        await ws.send('hello from the tutorial')
        reply = await asyncio.wait_for(ws.recv(), timeout=5)
        print(f'server echoed: {reply}')
        await ws.send('one'); await ws.send('two')   # full-duplex অনুভূতি
        print(await ws.recv(), '|', await ws.recv())

asyncio.run(main())
python3 ws_client.py
# server echoed: hello from the tutorial
# one | two

wss:// prefix মানে TLS-wrapped WS — handshake-টা হয় TLS-record-এর ভেতরে, observer কিছুই দেখে না।

ধাপ ২ — masked-frame নিজের চোখে: public endpoint-এর উপর packet- capture করলে WSS-এ encrypted দেখাবে; masked-frame দেখতে local plaintext server দরকার (build section-এর server চালিয়ে tcpdump/wireshark দিয়ে port 8765 capture করুন)। প্রত্যাশিত দৃশ্য:

$ tcpdump -i lo0 -nn -X port 8765 -c 3
0x0000:  81 82 7c 9e 4a 53 34 f7     ← client frame: MASK=1, len=2
                                      masked payload — 'Hi' পড়া যায় না!
0x0010:  81 02 48 69                 ← server frame: MASK=0 — plaintext 'Hi'

Masking-এর asymmetric rule — ঠিক এই জোড়া লাইনে।

ধাপ ৩ — ভাবুন: উপরের client-এ দুই send back-to-back করলে server দুটা আলাদা frame পায়, নাকি এক merged? Library-রা buffering করে কি না যাচাই করুন — frame-boundary বনাম message-boundary-র পার্থক্যটা TCP-programming-এর সেই পুরনো পাঠের পুনরাবৃত্তি।

এটা কী প্রমাণ করে

Public echo endpoint-এ connect, send, receive করে full-duplex behavior live অনুভব করা যায় — আর library-র debug-log বা hex-dump দেখলেই masked-frame theory-র হাতে-নাগাল প্রমাণ।

নিজে বানান

BUILD IT

ws_server.py — handshake + frame codec, stdlib-only echo server

Python · ●●●○○
  1. Raw TCP listen; প্রথম bytes থেকে HTTP request parse করে Sec-WebSocket-Key বের করুন
  2. Accept = base64(SHA1(key+GUID)) হিসাব করে 101 response পাঠান
  3. Frame reader: 2-byte header, extended-length branches, mask-key, XOR-unmask
  4. Opcode dispatch: text/binary echo, ping→pong, close→close-echo
  5. Threading দিয়ে multiple client — আর experiment-এর client snippet দিয়ে test

~110 লাইনে পুরো protocol-এর core — কোনো dependency নেই:

#!/usr/bin/env python3
"""ws_server.py — minimal WebSocket echo server (stdlib only, RFC 6455 subset)"""
import base64, hashlib, socket, threading

GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'


def recv_exact(conn, n):
    buf = b''
    while len(buf) < n:
        part = conn.recv(n - len(buf))
        if not part:
            raise ConnectionError('closed')
        buf += part
    return buf


def read_http_request(conn):
    buf = b''
    while b'\r\n\r\n' not in buf:
        buf += conn.recv(4096)
    head = buf.split(b'\r\n\r\n')[0].decode('latin1')
    lines = head.split('\r\n')
    hdrs = {}
    for line in lines[1:]:
        k, _, v = line.partition(':')
        hdrs[k.strip().lower()] = v.strip()
    return hdrs


def handshake_response(hdrs):
    if 'websocket' not in hdrs.get('upgrade', '').lower():
        raise ValueError('not a websocket upgrade')
    key = hdrs['sec-websocket-key']
    accept = base64.b64encode(
        hashlib.sha1((key + GUID).encode()).digest()).decode()
    return ('HTTP/1.1 101 Switching Protocols\r\n'
            'Upgrade: websocket\r\n'
            'Connection: Upgrade\r\n'
            f'Sec-WebSocket-Accept: {accept}\r\n\r\n').encode()


def read_frame(conn):
    b1, b2 = recv_exact(conn, 2)
    fin, opcode = b1 >> 7, b1 & 0x0F
    masked, length = b2 >> 7, b2 & 0x7F
    if length == 126:
        length = int.from_bytes(recv_exact(conn, 2), 'big')
    elif length == 127:
        length = int.from_bytes(recv_exact(conn, 8), 'big')
    mask = recv_exact(conn, 4) if masked else None
    payload = bytearray(recv_exact(conn, length)) if length else bytearray()
    if mask:
        for i in range(len(payload)):
            payload[i] ^= mask[i % 4]
    return fin, opcode, bytes(payload)


def encode_frame(opcode, payload):
    head = bytes([0x80 | opcode])
    n = len(payload)
    if n < 126:
        head += bytes([n])
    elif n < 65536:
        head += bytes([126]) + n.to_bytes(2, 'big')
    else:
        head += bytes([127]) + n.to_bytes(8, 'big')
    return head + payload


def serve_conn(conn, addr):
    print(f'[+] {addr} connected')
    try:
        conn.sendall(handshake_response(read_http_request(conn)))
        print(f'[+] {addr}: handshake ok — frames now')
        while True:
            fin, op, payload = read_frame(conn)
            if op == 8:                                   # CLOSE
                conn.sendall(encode_frame(8, payload))
                break
            if op == 9:                                   # PING → PONG
                conn.sendall(encode_frame(10, payload))
                continue
            if op == 10:                                  # PONG (unsolicited)
                continue
            if op in (1, 2):                              # TEXT/BINARY echo
                text = payload.decode(errors='replace')
                print(f'[{addr}] ← {text!r}')
                conn.sendall(encode_frame(1, f'echo: {text}'.encode()))
    except (ConnectionError, ValueError) as exc:
        print(f'[-] {addr}: {exc}')
    finally:
        conn.close()
        print(f'[-] {addr} closed')


srv = socket.socket()
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(('127.0.0.1', 8765))
srv.listen(8)
print('[ws] listening on ws://127.0.0.1:8765')
while True:
    conn, addr = srv.accept()
    threading.Thread(target=serve_conn, args=(conn, addr), daemon=True).start()

Test — experiment-এর client-টা localhost-এ ঘুরিয়ে:

import asyncio, websockets

async def main():
    async with websockets.connect('ws://127.0.0.1:8765') as ws:
        await ws.send('local test')
        print(await ws.recv())      # echo: local test
asyncio.run(main())

নিজে বাড়ান: (১) fragmentation support (FIN=0 continuation frames জমান); (২) broadcast mode — সব connected client-কে relay (chat!); (৩) close-frame-এর status-code parse করে log করুন; (৪) একটা malicious frame পাঠান যেখানে declared-length আসল payload-এর চেয়ে বড় — আপনার reader কীভাবে hang/crash করে দেখুন, তারপর timeout-guard বসান (protocol hardening-এর প্রথম হাতে-কলমে পাঠ)।

বাস্তব সিস্টেমে

WebSocket বাস্তব জগতে

Chat আর collaboration। Slack, Discord, Figma, Google Docs — সবই persistent WS (+ fallbacks): cursor-position হাজার বার মিনিটে — polling-এ অসম্ভব, frame-এ তুচ্ছ। Figma-র case study পড়ার মতো: binary protocol over WS, delta-sync — bandwidth-এ বিস্ময়কর কম।

Trading আর dashboards। Exchange feed (Binance WS API!), Grafana-live, sports-score push — server-initiated updates-এর ক্লাসিক। Latency arbitrage-এ polling-এর 5-second gap মানেই টাকা হারানো।

Scaling stories। Discord: single Python process এক million concurrent WS — async event-loop + OS-fd tuning-এর textbook। Slack-এর fan-out architecture: connection-gateway layer + pub/sub backbone — আজকের scaling-section-এর তিন pain-point-এর production-grade সমাধান।

Infra reality। Modern LB/proxy WS-aware (101 pass-through + long-idle-timeout) — কিন্তু default idle-timeout (60s!) WS kill করে; production-এ ping-interval config mandatory hygiene। WSS termination edge-এ হলে end-to-end app-layer visibility হারায় — security-review-তে আলাদা topic।

WS-এর পরের অধ্যায়। h2/h3-জগতে WS-over-h2 (RFC 8441 extended CONNECT) আর WebTransport — QUIC-র streams-এর উপরে same use-case, multiplexing bonus সহ। WS-র সরল মডেল তবু টিকে আছে: এটা বুঝলে বাকি সব abstraction transparent।

যে ভুলগুলো সবাই করে

“WebSocket "HTTP replace" করে — এখন সব কিছু WS দিয়েই করা উচিত।”

WS একটা specialized tool — REST/HTTP-র বিকল্প নয়। CRUD APIs, page loads, file uploads — request-response world এখনো HTTP-র; সেখানে WS যোগ করবে stateful-session burden, custom framing, আর infra-complexity — লাভ শূন্য। Rule of thumb: interaction-pattern দেখুন — client-driven, discrete operations → HTTP; continuous, both-direction, low-latency → WS। বেশিরভাগ product-এ দুটোই পাশাপাশি চলে: HTTP-তে auth+bootstrap, WS-তে live-layer।

“Masking একটা encryption — নিরাপত্তা দেয়।”

XOR-মাস্ক 4-byte key, যেটা একই frame-এই plaintext-এ যায়! এটা confidentiality মুহূর্তেই শূন্য — কেউ wire পড়লে সঙ্গে সঙ্গে unmask। Masking-এর একমাত্র কাজ: attacker-কে predictable wire-bytes choose করতে না দেওয়া — proxy-cache-poisoning era-র defense। Confidentiality চাইলে সেটা দেয় WSS (TLS layer) — আলাদা স্তর, আলাদা দায়িত্ব। Masking = anti-tampering-at-transit-tools, encryption নয়।

“WS connection-এর ভেতরের messages guaranteed ordered আর reliable — TCP-র মতোই ভরসা।”

Transport হিসেবে TCP reliable-ordered — কিন্তু application-level guarantee নয়! Connection drop হলে in-flight messages হারায়; client reconnect করলে মিস-হওয়া window কে fill করবে? কেউই না — যদি না আপনি sequence-numbers + resume-logic নিজে build করেন। Production chat-system সবাই তাই ack/redelivery/idempotency app-layer-এ রাখে। TCP-র promise শুধু ‘যা পৌঁছাল, order-এ পৌঁছাল’ — ‘সব পৌঁছাবেই’ নয়। Distributed-systems module-এ এই gap-টাই মূল পাঠ হবে।

“SSE পুরনো/দুর্বল — WebSocket-ই modern solution।”

Use-case mismatch-ই এখানে মূল ভুল। One-directional server-feed-এ (token-by-token AI responses!, news, metrics) SSE simpler: plain HTTP (auto-proxy-friendly), auto-reconnect + last-event-ID built-in, no custom server-lib needed। WS তখনই দরকার যখন upstream traffic বিশাল। AI-chat-app-গুলোর বেশিরভাগই আজ SSE-ই চালায় — নতুনতম workload-এ পুরনো ‘দুর্বল’ tool-এর জয়। Right-sizing-ই জেতে, novelty নয়।

বুঝেছেন কি না দেখুন

1

Sec-WebSocket-Key/Accept exchange-টা challenge-response authentication নয় — কারণ কী? তাহলে এর প্রকৃত কাজটা এক বাক্যে কী?

যুক্তি

Authentication-এ secret দরকার যা একপক্ষ জানে অন্যপক্ষ নয়; এখানে key client-ই generate করে, algorithm public, GUID spec-এ printed — কোনো secret নেই, তাই verify-ই করে না কেউ। প্রকৃত কাজ: capability/ awareness proof — server প্রমাণ করে সে RFC 6455-বোঝা active implementation (arbitrary computation perform করতে পারে), আর একই সাথে cross-protocol/speech-act confusion বন্ধ করে। এটা handshake-as- handshake-recognition — identity-proof নয়, protocol-capability-proof।

2

Client পাঠাল frame-header: 81 FE 01 F4 + 4-byte mask + payload। Opcode কী, payload কত বড়? একই payload server-এর পাঠালে header কত byte?

প্রয়োগ

81: FIN=1, opcode=1 (text)। FE: MASK=1, 7-bit-len=126 → extended 16-bit length follows = 01 F4 = 500 byte। Total client-header: 2+2+4 = 8 byte। Server-এর পাঠালে (MASK=0): 81 7E 01 F4 — 4 byte (126 marker + 2-byte length)। Asymmetric overhead — masking-tax শুধু client-direction-এ।

3

Proxy-cache-poisoning attack-টায় masking না থাকলে ঠিক কোন step-টা সম্ভব হতো? একটা আধুনিক HTTPS-only world-এ এই attack কি আজও possible?

যুক্তি

Attack-chain-এর মূল ধাপ: attacker চাইত wire-এ ঠিক যে bytes যাবে সেগুলো control করা — যাতে মাঝের naive proxy ওই bytes-কে বৈধ HTTP request হিসেবে cache-এ বসায়। Masking ছাড়া attacker app-payload choose করলেই wire-bytes deterministic — forge সম্ভব। Random mask মানে wire-bytes = payload XOR random — attacker predict করতে পারে না যে proxy কী দেখবে। আজকের HTTPS-world-এ proxy তো ciphertext দেখে (cache-poisoning impossible at that layer) — কিন্তু WS plaintext (ws://) deployment আজও আছে intranet-এ, আর defense-in-depth principle: layer-assumption-এর উপর security দাঁড় করানো বিপজ্জনক। তাই rule টিকে আছে।

4

একটা collaborative whiteboard-app design করছেন: 50-user room, প্রতি user সেকেন্ডে ~10 cursor-update, drawing-strokes occasional, undo/history critical। Transport + delivery-design sketch করুন — কোন data কোন mechanism-এ, fan-out কীভাবে, কোন guarantee কোথায় লাগবে।

ডিজাইন

Transport: এক WSS connection per user (bidirectional দরকার-ই — cursor upstream-এ যায়)। Cursor-updates: unreliable/fast-path — coalesce করে (latest-wins; পুরনো position replay অর্থহীন), binary frames, no-ack — loss টিকে যাবে পরের update-এ (10Hz-এ self-healing)। Drawing strokes: reliable ordered stream — stroke-loss মানে artifact; idempotent stroke-id সহ। Undo/history: reliable + persisted event-log (append-only ops, sequence-numbered) — reconnect- এ last-seq দিয়ে catch-up query; এটাই CRDT/event-sourcing-এর প্রথম ধাপ (distributed-systems module-এ পূর্ণ form)। Fan-out: room-scoped pub/sub — gateway replicas আলাদা হলে Redis/Kafka backbone। Design-moral: এক connection, traffic-class-wise ভিন্ন guarantee — QUIC-lesson-এর WS-flavored echo।

5

Opcode 0, 1, 2, 8, 9, 10 — প্রতিটার নাম আর কাজ এক লাইনে।

স্মরণ

0 continuation — আগের fragmented message-এর ধারাবাহিক অংশ; 1 text-message (UTF-8); 2 binary-message; 8 close-handshake (status-code বহন); 9 ping — keepalive probe; 10 pong — ping-এর জবাব। Control-frames (8,9,10) যেকোনো মুহূর্তে interleaved হতে পারে, max 125-byte payload — data-frame-এর সাথে সম-অবস্থানে।

এরপর কী

এরপর কী

Application-টা এখন real-time — কিন্তু যখন million-user-এ scale হবে, এক origin-server যথেষ্ট নয়: দরকার edge-এ caches, পেছনে load-balancer, আর সবার সামনে reverse-proxy। এই তিন ভূমিকার স্থাপত্য — forward/reverse confusion-এর চির-নিবৃত্তি, round-robin থেকে consistent-hashing, anycast- এ BGP-magic, আর CDN-এর hit-ratio economics — পরের লেসনে।

আরও পড়ুন

  • RFC 6455 — The WebSocket Protocol — I. Fette, A. Melnikov · Handshake, framing, masking requirement-এর rationale — protocol-এর মূল specification
  • RFC 9110 — HTTP Semantics (Upgrade mechanism context) — R. Fielding et al. · 101 Switching Protocols আর Connection/Upgrade header-এর HTTP-side contract
  • RFC 8441 — Bootstrapping WebSockets with HTTP/2 — P. McManus · WS-over-h2 extended CONNECT — multiplexed transport-এ WebSocket tunneling