Compare commits
3 Commits
f56095c312
...
49114c30a6
| Author | SHA1 | Date | |
|---|---|---|---|
| 49114c30a6 | |||
| e859075245 | |||
| 68f5c0b8aa |
13
.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.git/
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.uvicorn.pid
|
||||||
|
|
||||||
|
docs/
|
||||||
|
README.md
|
||||||
85
DEPLOYMENT.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Demo Deployment
|
||||||
|
|
||||||
|
This project can run behind the existing Caddy edge service with this temporary route:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Caddy -> chatroom-nginx -> chatroom-app
|
||||||
|
```
|
||||||
|
|
||||||
|
The chatroom compose stack joins the existing external Docker network named `caddy_proxy`. No app ports are published to the host.
|
||||||
|
|
||||||
|
## Start the Chatroom Stack
|
||||||
|
|
||||||
|
From this project directory:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
docker compose up -d --build
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
Test the FastAPI health endpoint through the Nginx container:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
docker compose exec chatroom-nginx wget -qO- http://chatroom-app:8000/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## Temporary Caddy Change
|
||||||
|
|
||||||
|
Do this manually in:
|
||||||
|
|
||||||
|
```text
|
||||||
|
D:\Programing\Scripts\Home Lab\Interanet\caddy-deployment\config\caddy\Caddyfile
|
||||||
|
```
|
||||||
|
|
||||||
|
Add this temporary block:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
chat.amiirkhl.ir {
|
||||||
|
import {$CADDY_TLS_MODE:custom_tls}
|
||||||
|
reverse_proxy chatroom-nginx:80
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the current custom certificate does not include `chat.amiirkhl.ir`, use this instead:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
chat.amiirkhl.ir {
|
||||||
|
import letsencrypt_tls
|
||||||
|
reverse_proxy chatroom-nginx:80
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Reload Caddy from the Caddy deployment directory:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
|
||||||
|
```
|
||||||
|
|
||||||
|
## Browser Test
|
||||||
|
|
||||||
|
Open:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://chat.amiirkhl.ir
|
||||||
|
```
|
||||||
|
|
||||||
|
Use two browser tabs, join the same room, and send messages both ways. The frontend builds its WebSocket URL from the current page, so HTTPS should connect with:
|
||||||
|
|
||||||
|
```text
|
||||||
|
wss://chat.amiirkhl.ir/ws/{room}/{user}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Undo After the Demo
|
||||||
|
|
||||||
|
1. Remove the `chat.amiirkhl.ir` block from the Caddyfile.
|
||||||
|
2. Reload Caddy:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Stop and remove the chatroom stack from this project directory:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
15
Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY app ./app
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers", "--forwarded-allow-ips", "*"]
|
||||||
30
docker-compose.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
services:
|
||||||
|
chatroom-app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
image: chatroom-app:demo
|
||||||
|
restart: unless-stopped
|
||||||
|
expose:
|
||||||
|
- "8000"
|
||||||
|
networks:
|
||||||
|
- chatroom_internal
|
||||||
|
|
||||||
|
chatroom-nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- chatroom-app
|
||||||
|
expose:
|
||||||
|
- "80"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
networks:
|
||||||
|
- chatroom_internal
|
||||||
|
- caddy_proxy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
chatroom_internal:
|
||||||
|
driver: bridge
|
||||||
|
caddy_proxy:
|
||||||
|
external: true
|
||||||
|
name: caddy_proxy
|
||||||
8
docs/slides/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# WebSocket Presentation
|
||||||
|
|
||||||
|
- Open `index.html` in a browser.
|
||||||
|
- Use Left/Right arrows to move between slides.
|
||||||
|
- Press `N` to show speaker notes.
|
||||||
|
- Press `F` for fullscreen mode.
|
||||||
|
- The deck uses the local Vazirmatn font file.
|
||||||
|
- Visible slide content is limited to titles, subtitles, diagrams, images, and bullet points.
|
||||||
47
docs/slides/assets/app-demo.svg
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Chatroom app mockup</title>
|
||||||
|
<desc id="desc">English mockup of the WebSocket chatroom user interface.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
</style>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".16"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="960" height="540" fill="#262a3a"/>
|
||||||
|
<path d="M42 430 L260 322 L358 488 L96 548 Z" fill="#f4b740" opacity=".13"/>
|
||||||
|
<path d="M720 34 L930 96 L820 228 L640 140 Z" fill="#6d8cff" opacity=".14"/>
|
||||||
|
<g id="diagram-content-shift" transform="translate(0,-28)">
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="88" y="74" width="784" height="392" rx="28" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<text x="132" y="122" fill="#f8fafc" font-size="28" font-weight="800">Simple WebSocket Chatroom</text>
|
||||||
|
<rect x="710" y="98" width="96" height="34" rx="17" fill="#43d9a3" opacity=".22" stroke="#43d9a3"/>
|
||||||
|
<text x="758" y="121" text-anchor="middle" fill="#9ff4d5" font-size="14" font-weight="800">Connected</text>
|
||||||
|
<rect x="132" y="164" width="496" height="232" rx="18" fill="#ffffff" opacity=".07" stroke="#ffffff"/>
|
||||||
|
<text x="162" y="204" fill="#f8fafc" font-size="20" font-weight="800">Room: internet-engineering</text>
|
||||||
|
<text x="162" y="232" fill="#a8adbd" font-size="15">You are chatting as Ali.</text>
|
||||||
|
<rect x="230" y="266" width="244" height="48" rx="14" fill="#ffffff" opacity=".10" stroke="#ffffff"/>
|
||||||
|
<text x="352" y="296" text-anchor="middle" fill="#f8fafc" font-size="15">Hello from WebSocket!</text>
|
||||||
|
<rect x="362" y="332" width="210" height="48" rx="14" fill="#6d8cff" opacity=".32" stroke="#6d8cff"/>
|
||||||
|
<text x="467" y="362" text-anchor="middle" fill="#f8fafc" font-size="15">Instant delivery</text>
|
||||||
|
<rect x="132" y="412" width="496" height="34" rx="12" fill="#ffffff" opacity=".09" stroke="#ffffff"/>
|
||||||
|
<text x="162" y="434" fill="#a8adbd" font-size="14">Type a message...</text>
|
||||||
|
<rect x="548" y="412" width="80" height="34" rx="12" fill="#f4b740"/>
|
||||||
|
<text x="588" y="434" text-anchor="middle" fill="#1f2937" font-size="14" font-weight="800">Send</text>
|
||||||
|
<rect x="660" y="164" width="166" height="282" rx="18" fill="#ffffff" opacity=".07" stroke="#ffffff"/>
|
||||||
|
<text x="743" y="204" text-anchor="middle" fill="#f8fafc" font-size="20" font-weight="800">Online Users</text>
|
||||||
|
<text x="704" y="252" fill="#a8adbd" font-size="16">Ali</text>
|
||||||
|
<text x="704" y="292" fill="#a8adbd" font-size="16">Sara</text>
|
||||||
|
<text x="704" y="332" fill="#a8adbd" font-size="16">Reza</text>
|
||||||
|
<circle cx="684" cy="246" r="6" fill="#43d9a3"/>
|
||||||
|
<circle cx="684" cy="286" r="6" fill="#43d9a3"/>
|
||||||
|
<circle cx="684" cy="326" r="6" fill="#43d9a3"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
54
docs/slides/assets/chatroom-architecture.svg
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="500" viewBox="0 0 1200 500" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Chatroom architecture</title>
|
||||||
|
<desc id="desc">A compact architecture diagram for the FastAPI WebSocket chatroom project.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
.title { fill: #f8fafc; font-size: 38px; font-weight: 850; }
|
||||||
|
.sub { fill: #a8adbd; font-size: 18px; font-weight: 500; }
|
||||||
|
.node-title { fill: #f8fafc; font-size: 22px; font-weight: 850; }
|
||||||
|
.node-sub { fill: #a8adbd; font-size: 16px; }
|
||||||
|
.label { font-size: 16px; font-weight: 850; }
|
||||||
|
</style>
|
||||||
|
<marker id="arrow" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#2563eb"/></marker>
|
||||||
|
<marker id="arrow-green" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#10b981"/></marker>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".16"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="1200" height="500" fill="#262a3a"/>
|
||||||
|
<path d="M42 396 L258 286 L374 438 L92 532 Z" fill="#f4b740" opacity=".13"/>
|
||||||
|
<path d="M880 42 L1146 112 L1030 262 L812 182 Z" fill="#6d8cff" opacity=".16"/>
|
||||||
|
<g id="diagram-content-shift" transform="translate(0,-28)">
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="106" y="174" width="236" height="88" rx="22" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="106" y="306" width="236" height="88" rx="22" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="476" y="164" width="248" height="230" rx="28" fill="#2563eb"/>
|
||||||
|
<rect x="858" y="164" width="236" height="96" rx="24" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="858" y="316" width="236" height="96" rx="24" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<text x="224" y="212" text-anchor="middle" class="node-title">Client A</text>
|
||||||
|
<text x="224" y="240" text-anchor="middle" class="node-sub">Browser tab</text>
|
||||||
|
<text x="224" y="344" text-anchor="middle" class="node-title">Client B</text>
|
||||||
|
<text x="224" y="372" text-anchor="middle" class="node-sub">Browser tab</text>
|
||||||
|
<text x="600" y="232" text-anchor="middle" fill="#ffffff" font-size="30" font-weight="850">Server</text>
|
||||||
|
<text x="600" y="272" text-anchor="middle" fill="#dbeafe" font-size="17">WebSocket Endpoint</text>
|
||||||
|
<text x="600" y="306" text-anchor="middle" fill="#dbeafe" font-size="17">ConnectionManager</text>
|
||||||
|
<text x="600" y="342" text-anchor="middle" fill="#ffffff" font-size="17" font-weight="850">/ws/{room}/{user}</text>
|
||||||
|
<text x="976" y="204" text-anchor="middle" class="node-title">Room: general</text>
|
||||||
|
<text x="976" y="234" text-anchor="middle" class="node-sub">A - B - C</text>
|
||||||
|
<text x="976" y="356" text-anchor="middle" class="node-title">Room: class</text>
|
||||||
|
<text x="976" y="386" text-anchor="middle" class="node-sub">D - E</text>
|
||||||
|
<path d="M342 218 C408 218 428 232 476 232" fill="none" stroke="#2563eb" stroke-width="5" marker-end="url(#arrow)"/>
|
||||||
|
<path d="M342 350 C408 350 428 326 476 326" fill="none" stroke="#2563eb" stroke-width="5" marker-end="url(#arrow)"/>
|
||||||
|
<path d="M724 238 C790 210 812 212 858 212" fill="none" stroke="#10b981" stroke-width="5" marker-end="url(#arrow-green)"/>
|
||||||
|
<path d="M724 320 C790 354 812 364 858 364" fill="none" stroke="#10b981" stroke-width="5" marker-end="url(#arrow-green)"/>
|
||||||
|
<text x="376" y="188" fill="#f7c75f" class="label">WebSocket Frames</text>
|
||||||
|
<text x="778" y="164" fill="#43d9a3" class="label">Broadcast by room</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/slides/assets/home.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
43
docs/slides/assets/http-mechanism.svg
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="500" viewBox="0 0 1200 500" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">HTTP mechanism</title>
|
||||||
|
<desc id="desc">A short HTTP request response exchange.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
.title { fill: #f8fafc; font-size: 38px; font-weight: 850; }
|
||||||
|
.sub { fill: #a8adbd; font-size: 18px; font-weight: 500; }
|
||||||
|
.node-title { fill: #f8fafc; font-size: 25px; font-weight: 850; }
|
||||||
|
.node-sub { fill: #a8adbd; font-size: 16px; }
|
||||||
|
.label { font-size: 18px; font-weight: 850; }
|
||||||
|
</style>
|
||||||
|
<marker id="arrow-blue" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#2563eb"/></marker>
|
||||||
|
<marker id="arrow-green" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#10b981"/></marker>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".16"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="1200" height="500" fill="#262a3a"/>
|
||||||
|
<path d="M42 396 L258 286 L374 438 L92 532 Z" fill="#f4b740" opacity=".13"/>
|
||||||
|
<path d="M880 42 L1146 112 L1030 262 L812 182 Z" fill="#6d8cff" opacity=".16"/>
|
||||||
|
<g id="diagram-content-shift" transform="translate(0,-28)">
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="92" y="166" width="250" height="132" rx="26" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="858" y="166" width="250" height="132" rx="26" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<text x="217" y="220" text-anchor="middle" class="node-title">Browser</text>
|
||||||
|
<text x="217" y="254" text-anchor="middle" class="node-sub">HTTP Client</text>
|
||||||
|
<text x="983" y="220" text-anchor="middle" class="node-title">Server</text>
|
||||||
|
<text x="983" y="254" text-anchor="middle" class="node-sub">HTTP Endpoint</text>
|
||||||
|
<line x1="372" y1="188" x2="828" y2="188" stroke="#2563eb" stroke-width="5" marker-end="url(#arrow-blue)"/>
|
||||||
|
<text x="600" y="166" text-anchor="middle" fill="#f7c75f" class="label">1. GET /resource</text>
|
||||||
|
<line x1="828" y1="252" x2="372" y2="252" stroke="#10b981" stroke-width="5" marker-end="url(#arrow-green)"/>
|
||||||
|
<text x="600" y="236" text-anchor="middle" fill="#43d9a3" class="label">2. 200 OK Response</text>
|
||||||
|
<line x1="372" y1="304" x2="828" y2="304" stroke="#fb7185" stroke-width="4" stroke-dasharray="12 10"/>
|
||||||
|
<text x="600" y="334" text-anchor="middle" fill="#fb7185" class="label">3. Connection closes</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
46
docs/slides/assets/long-polling-mechanism.svg
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="500" viewBox="0 0 1200 500" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Long Polling mechanism</title>
|
||||||
|
<desc id="desc">An HTTP request held by the server until data is ready.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
.title { fill: #f8fafc; font-size: 38px; font-weight: 850; }
|
||||||
|
.sub { fill: #a8adbd; font-size: 18px; font-weight: 500; }
|
||||||
|
.node-title { fill: #f8fafc; font-size: 25px; font-weight: 850; }
|
||||||
|
.node-sub { fill: #a8adbd; font-size: 16px; }
|
||||||
|
.label { font-size: 18px; font-weight: 850; }
|
||||||
|
</style>
|
||||||
|
<marker id="arrow-blue" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#2563eb"/></marker>
|
||||||
|
<marker id="arrow-green" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#10b981"/></marker>
|
||||||
|
<marker id="arrow-purple" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#7c3aed"/></marker>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".16"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="1200" height="500" fill="#262a3a"/>
|
||||||
|
<path d="M42 396 L258 286 L374 438 L92 532 Z" fill="#f4b740" opacity=".13"/>
|
||||||
|
<path d="M880 42 L1146 112 L1030 262 L812 182 Z" fill="#6d8cff" opacity=".16"/>
|
||||||
|
<g id="diagram-content-shift" transform="translate(0,-28)">
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="92" y="166" width="250" height="132" rx="26" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="858" y="166" width="250" height="132" rx="26" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<text x="217" y="220" text-anchor="middle" class="node-title">Browser</text>
|
||||||
|
<text x="217" y="254" text-anchor="middle" class="node-sub">Waiting request</text>
|
||||||
|
<text x="983" y="220" text-anchor="middle" class="node-title">Server</text>
|
||||||
|
<text x="983" y="254" text-anchor="middle" class="node-sub">Holds response</text>
|
||||||
|
<line x1="372" y1="174" x2="828" y2="174" stroke="#2563eb" stroke-width="5" marker-end="url(#arrow-blue)"/>
|
||||||
|
<text x="600" y="152" text-anchor="middle" fill="#f7c75f" class="label">1. HTTP Request</text>
|
||||||
|
<line x1="468" y1="230" x2="732" y2="230" stroke="#f4b740" stroke-width="8" stroke-linecap="round" stroke-dasharray="14 12"/>
|
||||||
|
<text x="600" y="216" text-anchor="middle" fill="#f7c75f" class="label">2. Hold Open</text>
|
||||||
|
<line x1="828" y1="292" x2="372" y2="292" stroke="#10b981" stroke-width="5" marker-end="url(#arrow-green)"/>
|
||||||
|
<text x="600" y="278" text-anchor="middle" fill="#43d9a3" class="label">3. Message Response</text>
|
||||||
|
<line x1="372" y1="332" x2="828" y2="332" stroke="#7c3aed" stroke-width="4" stroke-dasharray="10 9" marker-end="url(#arrow-purple)"/>
|
||||||
|
<text x="600" y="362" text-anchor="middle" fill="#aebdff" class="label">4. New Request</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
49
docs/slides/assets/polling-mechanism.svg
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="500" viewBox="0 0 1200 500" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Polling mechanism</title>
|
||||||
|
<desc id="desc">Repeated HTTP requests on a timer.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
.title { fill: #f8fafc; font-size: 38px; font-weight: 850; }
|
||||||
|
.sub { fill: #a8adbd; font-size: 18px; font-weight: 500; }
|
||||||
|
.node-title { fill: #f8fafc; font-size: 25px; font-weight: 850; }
|
||||||
|
.node-sub { fill: #a8adbd; font-size: 16px; }
|
||||||
|
.label { font-size: 17px; font-weight: 850; }
|
||||||
|
</style>
|
||||||
|
<marker id="arrow-blue" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#2563eb"/></marker>
|
||||||
|
<marker id="arrow-green" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#10b981"/></marker>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".16"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="1200" height="500" fill="#262a3a"/>
|
||||||
|
<path d="M42 396 L258 286 L374 438 L92 532 Z" fill="#f4b740" opacity=".13"/>
|
||||||
|
<path d="M880 42 L1146 112 L1030 262 L812 182 Z" fill="#6d8cff" opacity=".16"/>
|
||||||
|
<g id="diagram-content-shift" transform="translate(0,-28)">
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="92" y="166" width="250" height="132" rx="26" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="858" y="166" width="250" height="132" rx="26" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<text x="217" y="220" text-anchor="middle" class="node-title">Browser</text>
|
||||||
|
<text x="217" y="254" text-anchor="middle" class="node-sub">Timer based checks</text>
|
||||||
|
<text x="983" y="220" text-anchor="middle" class="node-title">Server</text>
|
||||||
|
<text x="983" y="254" text-anchor="middle" class="node-sub">HTTP Endpoint</text>
|
||||||
|
<line x1="372" y1="146" x2="828" y2="146" stroke="#2563eb" stroke-width="5" marker-end="url(#arrow-blue)"/>
|
||||||
|
<text x="600" y="132" text-anchor="middle" fill="#f7c75f" class="label">Request 1</text>
|
||||||
|
<line x1="828" y1="182" x2="372" y2="182" stroke="#10b981" stroke-width="5" marker-end="url(#arrow-green)"/>
|
||||||
|
<text x="600" y="176" text-anchor="middle" fill="#43d9a3" class="label">Empty Response</text>
|
||||||
|
<line x1="372" y1="224" x2="828" y2="224" stroke="#2563eb" stroke-width="5" marker-end="url(#arrow-blue)"/>
|
||||||
|
<text x="600" y="218" text-anchor="middle" fill="#f7c75f" class="label">Request 2</text>
|
||||||
|
<line x1="828" y1="260" x2="372" y2="260" stroke="#10b981" stroke-width="5" marker-end="url(#arrow-green)"/>
|
||||||
|
<text x="600" y="254" text-anchor="middle" fill="#43d9a3" class="label">Empty Response</text>
|
||||||
|
<line x1="372" y1="302" x2="828" y2="302" stroke="#2563eb" stroke-width="5" marker-end="url(#arrow-blue)"/>
|
||||||
|
<text x="600" y="296" text-anchor="middle" fill="#f7c75f" class="label">Request 3</text>
|
||||||
|
<line x1="828" y1="338" x2="372" y2="338" stroke="#10b981" stroke-width="5" marker-end="url(#arrow-green)"/>
|
||||||
|
<text x="600" y="332" text-anchor="middle" fill="#43d9a3" class="label">Message Response</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
45
docs/slides/assets/websocket-handshake.svg
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="500" viewBox="0 0 1200 500" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">WebSocket handshake</title>
|
||||||
|
<desc id="desc">A compact HTTP Upgrade handshake diagram.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
.title { fill: #f8fafc; font-size: 38px; font-weight: 850; }
|
||||||
|
.sub { fill: #a8adbd; font-size: 18px; font-weight: 500; }
|
||||||
|
.node-title { fill: #f8fafc; font-size: 25px; font-weight: 850; }
|
||||||
|
.node-sub { fill: #a8adbd; font-size: 16px; }
|
||||||
|
.label { font-size: 18px; font-weight: 850; }
|
||||||
|
</style>
|
||||||
|
<marker id="arrow-blue" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#2563eb"/></marker>
|
||||||
|
<marker id="arrow-green" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#10b981"/></marker>
|
||||||
|
<marker id="arrow-purple" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#7c3aed"/></marker>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".16"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="1200" height="500" fill="#262a3a"/>
|
||||||
|
<path d="M42 396 L258 286 L374 438 L92 532 Z" fill="#f4b740" opacity=".13"/>
|
||||||
|
<path d="M880 42 L1146 112 L1030 262 L812 182 Z" fill="#6d8cff" opacity=".16"/>
|
||||||
|
<g id="diagram-content-shift" transform="translate(0,-28)">
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="92" y="166" width="250" height="132" rx="26" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="858" y="166" width="250" height="132" rx="26" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<text x="217" y="220" text-anchor="middle" class="node-title">Browser</text>
|
||||||
|
<text x="217" y="254" text-anchor="middle" class="node-sub">JavaScript WebSocket</text>
|
||||||
|
<text x="983" y="220" text-anchor="middle" class="node-title">Server</text>
|
||||||
|
<text x="983" y="254" text-anchor="middle" class="node-sub">/ws/{room}/{user}</text>
|
||||||
|
<line x1="372" y1="176" x2="828" y2="176" stroke="#2563eb" stroke-width="5" marker-end="url(#arrow-blue)"/>
|
||||||
|
<text x="600" y="154" text-anchor="middle" fill="#f7c75f" class="label">1. GET + Upgrade</text>
|
||||||
|
<line x1="828" y1="240" x2="372" y2="240" stroke="#10b981" stroke-width="5" marker-end="url(#arrow-green)"/>
|
||||||
|
<text x="600" y="224" text-anchor="middle" fill="#43d9a3" class="label">2. 101 Switching Protocols</text>
|
||||||
|
<line x1="372" y1="298" x2="828" y2="298" stroke="#7c3aed" stroke-width="5" stroke-dasharray="10 9" marker-end="url(#arrow-purple)"/>
|
||||||
|
<line x1="828" y1="336" x2="372" y2="336" stroke="#7c3aed" stroke-width="5" stroke-dasharray="10 9" marker-end="url(#arrow-purple)"/>
|
||||||
|
<text x="600" y="360" text-anchor="middle" fill="#aebdff" class="label">3. Full-duplex WebSocket Frames</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
63
docs/slides/assets/websocket-history.svg
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">WebSocket history</title>
|
||||||
|
<desc id="desc">A compact Persian timeline with English technical names.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
</style>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".12"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="960" height="540" fill="#262a3a"/>
|
||||||
|
<path d="M34 112 L238 34 L340 146 L156 238 Z" fill="#f4b740" opacity=".12"/>
|
||||||
|
<path d="M700 410 L930 318 L982 504 L746 548 Z" fill="#6d8cff" opacity=".16"/>
|
||||||
|
<g id="diagram-content-shift" transform="translate(0,-28)">
|
||||||
|
<line x1="128" y1="282" x2="832" y2="282" stroke="#4a5168" stroke-width="10" stroke-linecap="round"/>
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="76" y="164" width="808" height="238" rx="26" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<g text-anchor="middle">
|
||||||
|
<g>
|
||||||
|
<circle cx="150" cy="282" r="24" fill="#2563eb"/>
|
||||||
|
<text x="150" y="289" fill="#fff" font-size="16" font-weight="800">1</text>
|
||||||
|
<text x="150" y="338" fill="#f8fafc" font-size="18" font-weight="800">Classic HTTP</text>
|
||||||
|
<text x="150" y="365" fill="#a8adbd" font-size="15">Request / Response</text>
|
||||||
|
<text x="150" y="390" fill="#a8adbd" font-size="15">No Server Push</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="330" cy="282" r="24" fill="#2563eb"/>
|
||||||
|
<text x="330" y="289" fill="#fff" font-size="16" font-weight="800">2</text>
|
||||||
|
<text x="330" y="338" fill="#f8fafc" font-size="18" font-weight="800">Polling</text>
|
||||||
|
<text x="330" y="365" fill="#a8adbd" font-size="15">Repeated request</text>
|
||||||
|
<text x="330" y="390" fill="#a8adbd" font-size="15">More overhead</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="510" cy="282" r="24" fill="#2563eb"/>
|
||||||
|
<text x="510" y="289" fill="#fff" font-size="16" font-weight="800">3</text>
|
||||||
|
<text x="510" y="338" fill="#f8fafc" font-size="18" font-weight="800">Long Polling</text>
|
||||||
|
<text x="510" y="365" fill="#a8adbd" font-size="15">Open request</text>
|
||||||
|
<text x="510" y="390" fill="#a8adbd" font-size="15">Long wait</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="690" cy="282" r="24" fill="#2563eb"/>
|
||||||
|
<text x="690" y="289" fill="#fff" font-size="16" font-weight="800">4</text>
|
||||||
|
<text x="690" y="338" fill="#f8fafc" font-size="18" font-weight="800">WebSocket</text>
|
||||||
|
<text x="690" y="365" fill="#a8adbd" font-size="15">Persistent connection</text>
|
||||||
|
<text x="690" y="390" fill="#a8adbd" font-size="15">Full-duplex</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="830" cy="282" r="24" fill="#10b981"/>
|
||||||
|
<text x="830" y="289" fill="#fff" font-size="16" font-weight="800">5</text>
|
||||||
|
<text x="830" y="338" fill="#f8fafc" font-size="18" font-weight="800">Real-time Web</text>
|
||||||
|
<text x="830" y="365" fill="#a8adbd" font-size="15">Chat - Game</text>
|
||||||
|
<text x="830" y="390" fill="#a8adbd" font-size="15">Dashboard - IoT</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
57
docs/slides/assets/websocket-osi.svg
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="500" viewBox="0 0 1200 500" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">WebSocket in OSI model</title>
|
||||||
|
<desc id="desc">A compact OSI stack showing WebSocket at the application layer over TCP/IP.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
.layer { fill: #f8fafc; font-size: 18px; font-weight: 850; }
|
||||||
|
.value { fill: #a8adbd; font-size: 18px; font-weight: 500; }
|
||||||
|
.strong { fill: #ffffff; font-size: 19px; font-weight: 850; }
|
||||||
|
.strongValue { fill: #ffffff; font-size: 19px; font-weight: 500; }
|
||||||
|
.mutedRow { fill: #ffffff; opacity: .08; stroke: #ffffff; }
|
||||||
|
</style>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".16"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width="1200" height="500" fill="#262a3a"/>
|
||||||
|
<path d="M42 396 L258 286 L374 438 L92 532 Z" fill="#f4b740" opacity=".13"/>
|
||||||
|
<path d="M880 42 L1146 112 L1030 262 L812 182 Z" fill="#6d8cff" opacity=".16"/>
|
||||||
|
|
||||||
|
<!-- Diagram is intentionally placed slightly above center, with all rows inside the outer panel. -->
|
||||||
|
<g id="diagram-content">
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="150" y="80" width="900" height="350" rx="30" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<rect x="190" y="105" width="820" height="48" rx="15" fill="#2563eb"/>
|
||||||
|
<text x="222" y="136" class="strong">Layer 7 - Application</text>
|
||||||
|
<text x="712" y="136" class="strongValue">WebSocket - JSON - Chat</text>
|
||||||
|
|
||||||
|
<rect x="190" y="165" width="820" height="40" rx="14" class="mutedRow"/>
|
||||||
|
<text x="222" y="191" class="layer">Layer 6 - Presentation</text>
|
||||||
|
<text x="712" y="191" class="value">UTF-8 - Text</text>
|
||||||
|
|
||||||
|
<rect x="190" y="215" width="820" height="40" rx="14" class="mutedRow"/>
|
||||||
|
<text x="222" y="241" class="layer">Layer 5 - Session</text>
|
||||||
|
<text x="712" y="241" class="value">Long-lived conversation</text>
|
||||||
|
|
||||||
|
<rect x="190" y="265" width="820" height="48" rx="15" fill="#10b981"/>
|
||||||
|
<text x="222" y="296" class="strong">Layer 4 - Transport</text>
|
||||||
|
<text x="712" y="296" class="strongValue">TCP reliable stream</text>
|
||||||
|
|
||||||
|
<rect x="190" y="325" width="820" height="40" rx="14" class="mutedRow"/>
|
||||||
|
<text x="222" y="351" class="layer">Layer 3 - Network</text>
|
||||||
|
<text x="712" y="351" class="value">IP routing</text>
|
||||||
|
|
||||||
|
<rect x="190" y="375" width="820" height="40" rx="14" class="mutedRow"/>
|
||||||
|
<text x="222" y="401" class="layer">Layers 2-1 - Link / Physical</text>
|
||||||
|
<text x="712" y="401" class="value">Ethernet - Wi-Fi</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
44
docs/slides/assets/websocket-pros-cons.svg
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">WebSocket pros and cons</title>
|
||||||
|
<desc id="desc">A compact pros and cons diagram for WebSocket.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
</style>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".12"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="960" height="540" fill="#262a3a"/>
|
||||||
|
<path d="M38 412 L244 320 L330 484 L96 548 Z" fill="#43d9a3" opacity=".12"/>
|
||||||
|
<path d="M720 34 L936 112 L828 236 L640 136 Z" fill="#fb7185" opacity=".12"/>
|
||||||
|
<g id="diagram-content-shift" transform="translate(0,-28)">
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="92" y="154" width="350" height="286" rx="28" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="518" y="154" width="350" height="286" rx="28" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<text x="130" y="206" fill="#047857" font-size="25" font-weight="800">Pros</text>
|
||||||
|
<text x="158" y="256" fill="#f8fafc" font-size="17">Low latency</text>
|
||||||
|
<text x="158" y="304" fill="#f8fafc" font-size="17">Full-duplex</text>
|
||||||
|
<text x="158" y="352" fill="#f8fafc" font-size="17">Less HTTP overhead</text>
|
||||||
|
<text x="158" y="400" fill="#f8fafc" font-size="17">Good for Chat / Dashboard</text>
|
||||||
|
<circle cx="136" cy="250" r="7" fill="#10b981"/>
|
||||||
|
<circle cx="136" cy="298" r="7" fill="#10b981"/>
|
||||||
|
<circle cx="136" cy="346" r="7" fill="#10b981"/>
|
||||||
|
<circle cx="136" cy="394" r="7" fill="#10b981"/>
|
||||||
|
<text x="556" y="206" fill="#b42318" font-size="25" font-weight="800">Cons</text>
|
||||||
|
<text x="584" y="256" fill="#f8fafc" font-size="17">Server state</text>
|
||||||
|
<text x="584" y="304" fill="#f8fafc" font-size="17">Open socket memory</text>
|
||||||
|
<text x="584" y="352" fill="#f8fafc" font-size="17">Reconnect handling</text>
|
||||||
|
<text x="584" y="400" fill="#f8fafc" font-size="17">Scaling needs design</text>
|
||||||
|
<circle cx="562" cy="250" r="7" fill="#ef4444"/>
|
||||||
|
<circle cx="562" cy="298" r="7" fill="#ef4444"/>
|
||||||
|
<circle cx="562" cy="346" r="7" fill="#ef4444"/>
|
||||||
|
<circle cx="562" cy="394" r="7" fill="#ef4444"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
145
docs/slides/index.html
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>WebSocket Protocol</title>
|
||||||
|
<link rel="stylesheet" href="styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="deck" id="deck">
|
||||||
|
<section class="slide active center-slide" data-title="Title">
|
||||||
|
<div class="center-content">
|
||||||
|
<h1>Websocket Protocol</h1>
|
||||||
|
<p class="subtitle">Socket Programming with a FastAPI real-time demo chat app</p>
|
||||||
|
<p class="members">Amirhossein Khalili • Morteza Khanbabaie • Alireza Khosravi</p>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">Open with the project name and explain that the presentation focuses on WebSocket concepts through a simple chatroom implementation.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="Timeline">
|
||||||
|
<h2>WebSocket History Timeline</h2>
|
||||||
|
<p class="subtitle">The communication model evolved from simple request/response to persistent real-time channels.</p>
|
||||||
|
<div class="timeline-only glass-panel">
|
||||||
|
<div class="timeline-method"><span>Classic HTTP</span><small>1991</small></div>
|
||||||
|
<div class="timeline-method"><span>Polling</span><small>1995+</small></div>
|
||||||
|
<div class="timeline-method"><span>Long Polling</span><small>2006</small></div>
|
||||||
|
<div class="timeline-method"><span>WebSocket</span><small>2011</small></div>
|
||||||
|
<div class="timeline-method"><span>Real-time Web</span><small>2010s+</small></div>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">Use this slide to name the major approaches only. The next slides explain how each mechanism works and what it costs.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide protocol-slide" data-title="HTTP">
|
||||||
|
<h2>HTTP</h2>
|
||||||
|
<p class="subtitle">A short-lived request/response cycle with no native live push.</p>
|
||||||
|
<div class="protocol-layout">
|
||||||
|
<div class="protocol-visual glass-panel">
|
||||||
|
<img src="assets/http-mechanism.svg" alt="HTTP request response mechanism" />
|
||||||
|
</div>
|
||||||
|
<div class="protocol-points glass-panel">
|
||||||
|
<article><b>Mechanism</b><ul><li>One request returns one response</li><li>The connection closes after the exchange</li></ul></article>
|
||||||
|
<article><b>Cost</b><ul><li>Every update needs a new HTTP cycle</li><li>The server cannot push live data by itself</li></ul></article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">HTTP is excellent for normal pages, forms, and APIs. Its basic model is not optimized for continuous server-to-client updates.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide protocol-slide" data-title="Polling">
|
||||||
|
<h2>Polling</h2>
|
||||||
|
<p class="subtitle">The browser repeatedly asks the server for new data.</p>
|
||||||
|
<div class="protocol-layout">
|
||||||
|
<div class="protocol-visual glass-panel">
|
||||||
|
<img src="assets/polling-mechanism.svg" alt="Polling repeated request mechanism" />
|
||||||
|
</div>
|
||||||
|
<div class="protocol-points glass-panel">
|
||||||
|
<article><b>Mechanism</b><ul><li>The client checks on a fixed timer</li><li>The server answers even when nothing changed</li></ul></article>
|
||||||
|
<article><b>Cost</b><ul><li>Many empty requests waste network work</li><li>Latency depends on the timer interval</li></ul></article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">Polling is simple but wasteful. Short intervals improve latency but increase server and network cost.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide protocol-slide" data-title="Long Polling">
|
||||||
|
<h2>Long Polling</h2>
|
||||||
|
<p class="subtitle">The server holds the request until an update is available.</p>
|
||||||
|
<div class="protocol-layout">
|
||||||
|
<div class="protocol-visual glass-panel">
|
||||||
|
<img src="assets/long-polling-mechanism.svg" alt="Long Polling held request mechanism" />
|
||||||
|
</div>
|
||||||
|
<div class="protocol-points glass-panel">
|
||||||
|
<article><b>Mechanism</b><ul><li>The server keeps the request open</li><li>The client reconnects after each response</li></ul></article>
|
||||||
|
<article><b>Cost</b><ul><li>Open requests consume server state</li><li>Reconnect and timeout handling is required</li></ul></article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">Long Polling reduces empty responses compared with Polling, but it still works through repeated HTTP request cycles.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide protocol-slide" data-title="WebSocket">
|
||||||
|
<h2>WebSocket</h2>
|
||||||
|
<p class="subtitle">An HTTP Upgrade creates one persistent Full-duplex channel.</p>
|
||||||
|
<div class="protocol-layout">
|
||||||
|
<div class="protocol-visual glass-panel">
|
||||||
|
<img src="assets/websocket-handshake.svg" alt="HTTP Upgrade Handshake" />
|
||||||
|
</div>
|
||||||
|
<div class="protocol-points glass-panel">
|
||||||
|
<article><b>Mechanism</b><ul><li>HTTP Upgrade switches the protocol</li><li>Frames move both ways on one socket</li></ul></article>
|
||||||
|
<article><b>Cost</b><ul><li>Each client keeps a live connection</li><li>Scaling needs connection-aware design</li></ul></article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">This is the key WebSocket slide. The browser starts with HTTP, upgrades the connection, and then both client and server can send frames whenever needed.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide image-slide" data-title="OSI">
|
||||||
|
<h2>WebSocket in the OSI Model</h2>
|
||||||
|
<p class="subtitle">Application semantics at Layer 7, reliable byte delivery through TCP/IP.</p>
|
||||||
|
<div class="single-visual glass-panel">
|
||||||
|
<img src="assets/websocket-osi.svg" alt="WebSocket in OSI model" />
|
||||||
|
</div>
|
||||||
|
<aside class="notes">WebSocket is an application-layer protocol. It uses TCP for reliable transport and IP for routing. The lower layers do not know what a chat message or room is.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="HTTP Versions">
|
||||||
|
<h2>HTTP Versions in WebSocket</h2>
|
||||||
|
<p class="subtitle">Different HTTP versions start WebSocket connections differently.</p>
|
||||||
|
<div class="version-row point-cards detailed-version-row">
|
||||||
|
<article><span>HTTP/1.1</span><b>Upgrade + 101 Switching Protocols</b><ul><li>The browser sends a normal GET request with <code>Upgrade: websocket</code>.</li><li>The server replies <code>101 Switching Protocols</code> if it accepts.</li><li>After that, the same TCP connection carries WebSocket frames both ways.</li><li>This is the classic and easiest model to understand.</li></ul></article>
|
||||||
|
<article><span>HTTP/2</span><b>Extended CONNECT over a Stream</b><ul><li>HTTP/2 does not use the old connection-level Upgrade flow.</li><li>It uses Extended CONNECT, so WebSocket traffic lives inside one HTTP/2 stream.</li><li>Other HTTP/2 streams can share the same underlying connection.</li><li>Support depends more on servers, clients, and proxies.</li></ul></article>
|
||||||
|
<article><span>HTTP/3</span><b>CONNECT over QUIC</b><ul><li>HTTP/3 runs on QUIC over UDP instead of TCP.</li><li>WebSocket uses a CONNECT-based mapping similar in idea to HTTP/2.</li><li>QUIC streams reduce transport-level head-of-line blocking.</li><li>It is modern, but the demo is easier to explain with HTTP/1.1.</li></ul></article>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">The project is best explained with HTTP/1.1 Upgrade. HTTP/2 and HTTP/3 support WebSocket through different mechanisms.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide image-slide" data-title="FastAPI Architecture">
|
||||||
|
<h2>Project Architecture</h2>
|
||||||
|
<p class="subtitle">Browsers connect to one WebSocket endpoint; ConnectionManager groups sockets by room.</p>
|
||||||
|
<div class="single-visual glass-panel">
|
||||||
|
<img src="assets/chatroom-architecture.svg" alt="WebSocket chatroom architecture" />
|
||||||
|
</div>
|
||||||
|
<aside class="notes">Explain the code structure: FastAPI serves the frontend, the WebSocket endpoint accepts connections, and the manager stores active clients by room before broadcasting messages.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="Takeaways">
|
||||||
|
<h2>Keynotes and Takeaways</h2>
|
||||||
|
<p class="subtitle">The essential points to remember from the project.</p>
|
||||||
|
<div class="takeaway-grid point-cards">
|
||||||
|
<article><b>HTTP is request/response</b><ul><li>Simple</li><li>Stateless</li><li>Not ideal for live push</li></ul></article>
|
||||||
|
<article><b>Polling simulates real-time</b><ul><li>Easy</li><li>Wasteful</li><li>Interval-based latency</li></ul></article>
|
||||||
|
<article><b>WebSocket keeps a channel</b><ul><li>Persistent</li><li>Full-duplex</li><li>Low latency</li></ul></article>
|
||||||
|
<article><b>Server design matters</b><ul><li>Connection state</li><li>Disconnect handling</li><li>Scaling strategy</li></ul></article>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">End with the central idea: WebSocket is useful when real-time bidirectional communication matters, but it moves responsibility to the server to manage long-lived connections.</aside>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="controls" aria-hidden="true">
|
||||||
|
<button id="prev" title="Previous"><</button>
|
||||||
|
<span id="counter">1 / 10</span>
|
||||||
|
<button id="next" title="Next">></button>
|
||||||
|
</div>
|
||||||
|
<div class="hint">Left / Right to move - N for notes - F for fullscreen</div>
|
||||||
|
<div class="progress"><span id="progressBar"></span></div>
|
||||||
|
<div class="speaker-notes" id="speakerNotes"></div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
69
docs/slides/script.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
const slides = [...document.querySelectorAll('.slide')];
|
||||||
|
const counter = document.getElementById('counter');
|
||||||
|
const progressBar = document.getElementById('progressBar');
|
||||||
|
const notesBox = document.getElementById('speakerNotes');
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
function fitDeck() {
|
||||||
|
const vw = window.innerWidth;
|
||||||
|
const vh = window.innerHeight;
|
||||||
|
const root = document.documentElement;
|
||||||
|
|
||||||
|
// Mobile uses normal document flow, so text can shrink and content can scroll.
|
||||||
|
if (vw <= 900) {
|
||||||
|
const mobileScale = Math.max(0.64, Math.min(0.9, vw / 900));
|
||||||
|
root.style.setProperty('--slide-scale', mobileScale.toFixed(3));
|
||||||
|
root.style.removeProperty('--slide-w');
|
||||||
|
root.style.removeProperty('--slide-h');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desktop/tablet: keep a 16:9 slide and fill as much height as the viewport allows.
|
||||||
|
const pad = Math.max(24, Math.min(72, Math.min(vw, vh) * 0.065));
|
||||||
|
const availableW = vw - pad;
|
||||||
|
const availableH = vh - pad;
|
||||||
|
let slideW = Math.min(availableW, availableH * (16 / 9));
|
||||||
|
let slideH = slideW * (9 / 16);
|
||||||
|
|
||||||
|
// If the width-bound calculation is too tall, bind by height instead.
|
||||||
|
if (slideH > availableH) {
|
||||||
|
slideH = availableH;
|
||||||
|
slideW = slideH * (16 / 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scale = Math.max(0.78, Math.min(1.55, Math.min(slideW / 1200, slideH / 675)));
|
||||||
|
root.style.setProperty('--slide-w', `${Math.floor(slideW)}px`);
|
||||||
|
root.style.setProperty('--slide-h', `${Math.floor(slideH)}px`);
|
||||||
|
root.style.setProperty('--slide-scale', scale.toFixed(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
slides.forEach((slide, i) => slide.classList.toggle('active', i === index));
|
||||||
|
counter.textContent = `${index + 1} / ${slides.length}`;
|
||||||
|
progressBar.style.width = `${((index + 1) / slides.length) * 100}%`;
|
||||||
|
const note = slides[index].querySelector('.notes')?.textContent.trim() || '';
|
||||||
|
notesBox.textContent = note;
|
||||||
|
}
|
||||||
|
|
||||||
|
function go(delta) {
|
||||||
|
index = Math.min(slides.length - 1, Math.max(0, index + delta));
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('prev').addEventListener('click', () => go(-1));
|
||||||
|
document.getElementById('next').addEventListener('click', () => go(1));
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (['ArrowLeft', 'PageUp'].includes(e.key)) go(-1);
|
||||||
|
if (['ArrowRight', 'PageDown', ' '].includes(e.key)) go(1);
|
||||||
|
if (e.key.toLowerCase() === 'n') notesBox.classList.toggle('show');
|
||||||
|
if (e.key.toLowerCase() === 'f') {
|
||||||
|
if (!document.fullscreenElement) document.documentElement.requestFullscreen?.();
|
||||||
|
else document.exitFullscreen?.();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', fitDeck);
|
||||||
|
window.addEventListener('orientationchange', fitDeck);
|
||||||
|
fitDeck();
|
||||||
|
update();
|
||||||
1061
docs/slides/styles.css
Normal file
35
nginx/default.conf
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
|
||||||
|
map $http_x_forwarded_proto $forwarded_proto {
|
||||||
|
default $http_x_forwarded_proto;
|
||||||
|
'' $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream chatroom_app {
|
||||||
|
server chatroom-app:8000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://chatroom_app;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_send_timeout 3600s;
|
||||||
|
}
|
||||||
|
}
|
||||||