فرض کن مرورگرت رو باز کردی و این آدرس رو نوشتی:

https://google.com


چند لحظه بعد، صفحه‌ی سایت جلوته. از دید تو اتفاق خاصی نیفتاده. یه آدرس نوشتی، Enter زدی و سایت باز شد.ولی همین Enter ساده شروع یه سفره. سفری که از چند سیستم مختلف رد می‌شه و خیلی وقت‌ها زودتر از اونی تموم می‌شه که بشه بهش فکر کرد.


پس بیایم یه سؤال ساده بپرسیم:

وقتی این آدرس رو وارد می‌کنیم و Enter می‌زنیم، دقیقاً چه اتفاقی می‌افته؟


برای جواب دادن، قرار نیست فهرستی از تعریف‌ها رو حفظ کنیم. فقط درخواستمون رو دنبال می‌کنیم؛ از همون جایی که شروع می‌شه.


۱. همه‌چیز از مرورگر شروع می‌شه

وقتی Enter می‌زنیم، مرورگر اول باید بفهمه دقیقاً چی ازش خواستیم. برای ما URL فقط یه آدرسه، ولی مرورگر اون رو به چند تکه‌ی جدا تقسیم می‌کنه:

https://google.com
│       │
│       └── host (domain name)
└── scheme


google.com اسم دامنه‌ایه که می‌خوایم بهش برسیم.


https هم دو چیز مهم به مرورگر می‌گه. اول اینکه پروتکل همون HTTP هست، ولی قراره روی یه ارتباط امن‌شده با TLS منتقل بشه. دوم اینکه چون پورتی ننوشتیم، باید از پورت پیش‌فرض HTTPS یعنی 443 استفاده کنه.


یه چیز دیگه رو هم ننوشتیم: مسیر. وقتی مسیری در کار نباشه، مرورگر صفحه‌ی ریشه یعنی / رو درخواست می‌کنه. پس اگه همه‌ی چیزهایی رو که ننوشتیم هم اضافه کنیم، به این آدرس می‌رسیم که از نظر مرورگر با آدرس اول هیچ فرقی نداره:

https://google.com:443/


حالا مرورگر می‌دونه با چه پروتکلی، روی چه پورتی و دنبال چه صفحه‌ای باید بره. ولی یه چیز اساسی رو هنوز نمی‌دونه و اونم اینه که این سایت کجاست؟


اسم دامنه برای ما آدم‌ها راحت و به‌یادموندنیه، ولی برای برقراری ارتباط توی شبکه، مرورگر به IP Address نیاز داره.


۲. خب... این سایت کجاست؟

اینجاست که DNS به کار میاد. DNS یا Domain Name System رو، در ساده‌ترین حالت، می‌شه دفترچه تلفن اینترنت در نظر گرفت: اسم رو می‌دیم و آدرس رو پس می‌گیریم.

ولی تشبیه دفترچه تلفن فقط تا همین‌جا جواب می‌ده. DNS یه دفترچه‌ی واحد نیست که یه جا نگه داشته بشه؛ یه سیستم توزیع‌شده‌ست که جواب هر دامنه رو از چند Server مختلف و در چند مرحله پیدا می‌کنه. اینکه این مراحل دقیقاً چی هستن، خودش یه داستان جداست.


یه نکته‌ی مهم دیگه هم هست: این جست‌وجو همیشه از صفر شروع نمی‌شه. هر جواب DNS یه مدت اعتبار داره که بهش TTL می‌گن، و در این مدت مرورگر، سیستم‌عامل یا DNS Resolver شبکه می‌تونن جواب قبلی رو Cache کنن. اگه تازگی به این سایت سر زده باشی، ممکنه جواب از یکی از همین Cacheها بیاد. در هر حال، نتیجه‌ی این مرحله یکیه: اسم دامنه به یک یا چند IP Address (IPv4، IPv6 یا هر دو) تبدیل می‌شه. حالا می‌دونیم کجا باید بریم. ولی داشتن آدرس یه خونه با در زدن فرق داره.


۳. آدرس رو داریم؛ حالا باید در بزنیم

الان هر دو چیزی که برای شروع ارتباط لازمه دستمونه: IP رو DNS بهمون داده و پورت رو خود URL.

185.xxx.xxx.xxx:443


برای اینکه داستان رو ساده نگه داریم، فرض کنیم این ارتباط با TCP برقرار می‌شه. TCP کمک می‌کنه دو طرف یه ارتباط قابل‌ اعتماد داشته باشن: داده‌ها به همون ترتیبی که فرستاده شدن تحویل داده می‌شن و اگه چیزی در مسیر گم بشه، دوباره فرستاده می‌شه.


ولی قبل از اینکه هر داده‌ای رد و بدل بشه، خود اتصال باید برقرار بشه. این کار با یه تبادل سه‌مرحله‌ای انجام می‌شه که بهش Three-Way Handshake می‌گن:



مرورگر می‌گه «می‌خوام باهات ارتباط برقرار کنم»، سرور جواب می‌ده «باشه، آماده‌ام»، و مرورگر تأیید می‌کنه که «پس شروع کنیم».


حالا اتصال برقراره.


ولی هر چیزی که روی این اتصال بفرستیم، هنوز رمزنگاری نشده. اگه قرار باشه رمز عبور یا Cookie از این مسیر رد بشه، نمی‌خوایم هر کسی که ترافیک مسیر رو می‌بینه بتونه اون رو بخونه.

و یادتون هست که URL ما با چی شروع می‌شد؟


۴. قبل از هر حرفی، ارتباط رو امن می‌کنیم

همون https اول URL یعنی قبل از اینکه درخواست HTTP خودمون رو بفرستیم، باید یه ارتباط امن ساخته بشه. این کار TLS هست. قرار نیست اینجا وارد جزئیات رمزنگاری بشیم. فقط سه چیزی که TLS برامون فراهم می‌کنه مهمه:


  • محرمانگی: داده‌ای که بین مرورگر و سرور رد و بدل می‌شه رمزنگاری می‌شه و بقیه نمی‌تونن محتواش رو بخونن.

  • احراز هویت: مرورگر می‌تونه مطمئن بشه واقعاً به سروری وصل شده که مجاز به پاسخ دادن برای این دامنه‌ست.

  • یکپارچگی: اگه کسی وسط راه داده‌ها رو دستکاری کنه، طرفین متوجه می‌شن.


برای احراز هویت، سرور یه Certificate ارائه می‌ده و مرورگر بررسی می‌کنه که معتبر باشه و واقعاً برای همین دامنه صادر شده باشه. همین وسط یه توافق کوچیک ولی مهم هم انجام می‌شه. مرورگر و سرور در همین Handshake تصمیم می‌گیرن با کدوم نسخه‌ی HTTP با هم حرف بزنن؛ مثلاً HTTP/2 یا HTTP/1.1. اسم این مکانیزم ALPN هست و چند قدم بعد به کارمون میاد.

Browser  <──── encrypted (TLS) ────>  Server

حالا یه ارتباط امن داریم.

ولی تا اینجا فقط مسیر رو آماده کردیم. هنوز به سرور نگفتیم چی می‌خوایم.


۵. بالاخره می‌گیم چی می‌خوایم

حالا مرورگر درخواستش رو می‌فرسته. اگه نسخه‌ی توافق‌شده HTTP/1.1 باشه، ساده‌ترین شکل درخواست ما این شکلیه:

GET / HTTP/1.1
Host: google.com


همین دو خط کلی اطلاعات دارن.


GET به سرور می‌گه «می‌خوام یه چیزی رو ازت بگیرم».

/ همون مسیریه که مرورگر خودش اضافه کرد؛ یعنی صفحه‌ی اصلی.

Host هم مشخص می‌کنه درخواست برای کدوم دامنه‌ست. شاید عجیب باشه؛ ما که به IP سرور وصل شدیم، دیگه اسم دامنه برای چیه؟ ولی روی یه IP می‌شه ده‌ها سایت مختلف رو میزبانی کرد و سرور باید بدونه این درخواست مال کدومشونه. برای همین در HTTP/1.1 فرستادن Host اجباریه. درخواست واقعی مرورگر معمولاً طولانی‌تره. یه نمونه‌ی ساده‌شده از HTTP/1.1 می‌تونه چیزی شبیه این باشه:

GET / HTTP/1.1
Host: google.com
Connection: keep-alive
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml
Accept-Language: fa-IR,fa;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate, br
Cookie: session_id=abc123


این فقط یه نمونه‌ی آموزشیه و هر مرورگری در هر شرایطی دقیقاً همین Headerها رو نمی‌فرسته. ولی ساختار کلی رو خوب نشون می‌ده:

Request Line
Headers
Empty Line
Body (optional)


برای یه GET معمولاً Body نداریم. Body وقتی لازم می‌شه که بخوایم داده‌ای برای سرور بفرستیم؛ مثلاً وقتی یه فرم رو ارسال می‌کنیم.


یه نکته‌ی مهم: این شکل متنی مخصوص HTTP/1.1 هست. اگه در مرحله‌ی TLS روی HTTP/2 توافق شده باشه، که برای سایت‌های HTTPS امروزی خیلی رایجه، همین اطلاعات به شکل Frameهای باینری فرستاده می‌شن و مثلاً به‌جای Header Host یه فیلد به اسم :authority داریم. شکل ارسال فرق می‌کنه، ولی معنی درخواست همونه: متد، مسیر، دامنه و Headerها. تفاوت نسخه‌ها رو می‌تونی در صفحه‌ی HTTP ببینی.


درخواست فرستاده شد. ولی اون طرف خط، دقیقاً کی منتظرشه؟


۶. Request به کجا می‌رسه؟

ممکنه فکر کنی Request مستقیم به برنامه‌ای که سایت رو اجرا می‌کنه می‌رسه. در یه معماری ساده ممکنه همین‌طور باشه. ولی در خیلی از معماری‌های واقعی، جلوی برنامه یه لایه‌ی دیگه قرار داره: یه Reverse Proxy مثل Nginx.



Reverse Proxy بر اساس اطلاعات خود Request تصمیم می‌گیره اون رو به کجا بفرسته. مثلاً:

example.com/api     →  API service
example.com         →  Web app
admin.example.com   →  Admin panel


کارهای دیگه‌ای هم ازش برمیاد: Load Balancing بین چند نسخه از Application، محدود کردن دسترسی، سرو کردن فایل‌های Static و Terminate کردن TLS. همین آخری یه لحظه‌ی جالب توی داستان ما می‌سازه. یادتون هست در مرحله‌ی قبل با «سرور» یه ارتباط امن برقرار کردیم؟ در چنین معماری‌ای، اون سرور به احتمال زیاد همین Nginx بوده.


یعنی Certificate رو Nginx ارائه داده، رمز رو Nginx باز می‌کنه، و بعد Request رو معمولاً روی شبکه‌ی داخلی و اغلب با HTTP ساده به Application می‌ده. ممکنه Application ما اصلاً هیچ‌وقت مستقیماً با TLS سروکار نداشته باشه.


(اگه جلوی سایت یه CDN هم باشه، ماجرا یه لایه‌ی دیگه پیدا می‌کنه؛ اون‌وقت اولین جایی که TLS باهاش برقرار شده ممکنه خود CDN باشه.)


در هر حال، کار این لایه برای داستان ما اینه: Request رو به جایی می‌رسونه که کد برنامه بتونه پردازشش کنه. حالا وقتشه ببینیم Request دقیقاً وارد کد کجا می‌شه.


۷. Application از کجا می‌فهمه باید چه کار کنه؟

اینجا دیگه وارد بخش اصلی برنامه می‌شیم. فرض کنید Web Application ما با Django نوشته شده. با FastAPI یا Go هم ایده همینه؛ فقط شکل کد فرق می‌کنه. Request وارد Application می‌شه و Router بررسی می‌کنه که این Request مربوط به کدوم قسمت برنامه‌ست:

GET  /              →  home
GET  /articles/42   →  article_detail
POST /login         →  login


Request ما GET / بود، پس به Handler صفحه‌ی اصلی می‌رسه.


حالا فرض کنیم صفحه‌ی اصلی لیست آخرین مقاله‌ها رو نشون می‌ده. Handler ما می‌تونه چیزی شبیه این باشه:

# views.py
from django.shortcuts import render

from .models import Article


def home(request):
    # Ten most recent articles for the homepage
    articles = Article.objects.order_by("-created_at")[:10]
    return render(request, "home.html", {"articles": articles})


همین چند خط، نقطه‌ی اتصال کل سفر به کد ماست. Requestی که از مرورگر راه افتاد و از DNS و TCP و TLS و Reverse Proxy رد شد، حالا به شکل یه شیء request تحویل این تابع شده.


یه نکته‌ی ظریف هم اینجا هست: خط Article.objects... هنوز سراغ Database نمی‌ره. QuerySetهای Django تنبل (lazy) هستن و Query واقعی وقتی QuerySet evaluate بشه اجرا می‌شه؛ مثلاً وقتی Template بخواد روی مقاله‌ها حلقه بزنه.


ولی دیر یا زود، اون Query اجرا می‌شه. و این بار Application برای تکمیل Request خودش یه درخواست می‌فرسته؛ به Database.


۸. یه سفر کوچیک‌تر، درون همین سفر

وقتی QuerySet واقعاً evaluate می‌شه، Django ORM از اون QuerySet یه Query SQL می‌سازه و اجراش می‌کنه. چیزی شبیه این:

SELECT id, title, created_at
FROM blog_article
ORDER BY created_at DESC
LIMIT 10;


اون [:10] در پایتون، همون LIMIT 10 در SQL شده. Database این Query رو اجرا می‌کنه و نتیجه رو برمی‌گردونه:

id   title                                            created_at
----------------------------------------------------------------
42   What Actually Happens When You Open a Website?   2026-09-10
41   How Does DNS Actually Work?                      2026-09-08
40   What Is a Reverse Proxy?                         2026-09-05


Application حالا داده‌ها رو داره و می‌تونه روشون کار کنه: فیلترشون کنه، با اطلاعات دیگه ترکیبشون کنه و در نهایت تصمیم بگیره چه Responseای بسازه. در مثال ما، Django این داده‌ها رو داخل Template home.html می‌ذاره و یه HTML می‌سازه.


ولی Response همیشه HTML نیست. یه API ممکنه JSON برگردونه و یه Endpoint دیگه ممکنه یه فایل. Application همیشه هم سراغ Database نمی‌ره. اگه همین لیست مقاله‌ها چند ثانیه پیش ساخته و Cache شده باشه، ممکنه اصلاً Queryای اجرا نشه.


پس این معماری:

Browser
   ↓
Reverse Proxy
   ↓
Web Application
   ↓
Database


یه الگوی رایجه، نه تنها معماری ممکن. ممکنه Application به‌جای Database از Cache استفاده کنه، به یه سرویس دیگه Request بفرسته، یا حتی برای بعضی Requestها اصلاً نیاز به هیچ داده‌ی خارجی نداشته باشه. برای داستان ما، مهم اینه که Application حالا همه‌ی چیزی رو که برای جواب دادن لازم داره، در اختیار داره.


۹. جواب راه می‌افته

Application در نهایت یه HTTP Response می‌سازه که از همون مسیری که Request اومده بود برمی‌گرده. باز هم با شکل HTTP/1.1 (و با حذف چند Header برای ساده موندن مثال):

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
    <meta charset="utf-8">
    <title>Google</title>
    <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
    <link rel="stylesheet" href="/styles.css">
    <script src="/app.js" defer></script>
</head>
<body>
    <img src="/images/logo.png" alt="Logo">
    <h1>Latest articles</h1>
    <ul>
        <li><a href="/articles/42">What Actually Happens When You Open a Website?</a></li>
        <li><a href="/articles/41">How Does DNS Actually Work?</a></li>
    </ul>
</body>
</html>


خط اول مهم‌ترین خبر رو می‌ده:

HTTP/1.1 200 OK


200 یعنی درخواست با موفقیت انجام شده. OK فقط یه توضیح متنی برای آدم‌هاست؛ مرورگر به عدد نگاه می‌کنه، و در HTTP/2 و HTTP/3 این توضیح متنی اصلاً وجود نداره.


اگه صفحه وجود نداشت، 404 Not Found می‌گرفتیم و اگه سمت سرور مشکلی پیش اومده بود، چیزی مثل 500 Internal Server Error.


بعد Headerها میان. Content-Type به مرورگر می‌گه محتوایی که داره می‌گیره HTML هست و با چه Encodingی باید خونده بشه. Headerهای دیگه‌ای هم برای Cache، Cookie، فشرده‌سازی و امنیت وجود دارن که اینجا حذفشون کردیم.


بعد از یه خط خالی، خود Body میاد؛ همون HTML که Template ساخته.

رفت و برگشت کامل شد:

Browser ──request──>  Reverse Proxy ──> Web Application ──> Database
Browser <─response──  Reverse Proxy <── Web Application <── Database


به نظر میاد کار تموم شد. درخواست رفت، جواب برگشت، HTML رسید. ولی اگه دقیق‌تر به همین HTML نگاه کنی، می‌بینی تازه اول ماجراست.


۱۰. یه Enter، ده‌ها Request

مرورگر همین‌طور که HTML رو دریافت و پردازش می‌کنه، منابعی مثل CSS، JavaScript، تصویر و فونت رو پیدا می‌کنه و برای دریافت اون‌ها Requestهای جدید می‌فرسته. مرورگرها حتی می‌تونن از سازوکارهایی مثل preload scanner استفاده کنن تا بعضی منابع رو زودتر پیدا و دریافت کنن. یادتون هست توی HTML این خط‌ها بودن؟

<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/styles.css">
<script src="/app.js" defer></script>
<img src="/images/logo.png" alt="Logo">


هر کدوم می‌تونه یه Request جدید ایجاد کنه:

GET /fonts/main.woff2
GET /styles.css
GET /app.js
GET /images/logo.png


پس یه بار Enter زدن لزوماً یعنی یه Request نیست. یه صفحه‌ی معمولی می‌تونه ده‌ها Request بسازه. شاید این سؤال پیش بیاد: یعنی برای هر کدوم از این فایل‌ها، کل مسیر DNS و TCP و TLS از اول تکرار می‌شه؟


خوشبختانه نه. برای Requestهای بعدی به همون مقصد، مرورگر معمولاً می‌تونه از Connectionهای موجود دوباره استفاده کنه؛ بنابراین لازم نیست برای هر فایل همه‌ی مراحل از اول انجام بشن. در HTTP/1.1 مرورگر می‌تونه چند Connection هم‌زمان به یک مقصد داشته باشه و Requestها رو بین اون‌ها تقسیم کنه. در HTTP/2 هم چند Request می‌تونن هم‌زمان روی یک Connection جلو برن.


البته این فقط وقتی صدق می‌کنه که Request به همون مقصد بره. اگه بعضی فایل‌ها از یه دامنه‌ی دیگه بیان، مثلاً از یه CDN، ممکنه برای اون دامنه نیاز به DNS و Connection و TLS جداگانه باشه. هر کدوم از این Requestها هم ممکنه سرنوشت متفاوتی داشته باشه:

                ┌── styles.css   → browser cache
                │
                ├── app.js       → CDN
HTML ───────────┤
                ├── logo.png     → Nginx (static file)
                │
                └── main.woff2   → browser cache


و حتی بعد از باز شدن صفحه، JavaScript می‌تونه خودش Requestهای جدید بسازه. مثلاً app.js اطلاعات پروفایل کاربر رو از یه API بگیره:

app.js
   │
   └── GET /api/profile
             ↓
       Web Application
             ↓
          Database


پس چیزی که ما بهش می‌گیم «سایت رو باز کردم»، از دید مرورگر مجموعه‌ای از Request و Responseهای مختلفه. ولی هنوز یه سؤال مونده: از این همه فایل، تصویری که روی صفحه می‌بینیم چطور ساخته می‌شه؟


۱۱. از کد تا پیکسل

HTML، CSS، JavaScript، تصویر، فونت... هیچ‌کدوم به‌تنهایی اون چیزی نیست که روی صفحه می‌بینیم. مرورگر باید همه‌شون رو کنار هم بذاره. اول HTML رو به یه ساختار درختی به اسم DOM تبدیل می‌کنه و CSS رو به CSSOM. بعد این دو رو ترکیب می‌کنه تا بفهمه چه چیزهایی باید دیده بشن و هر کدوم چه استایلی دارن. در مرحله‌ی Layout حساب می‌کنه هر عنصر کجا قرار بگیره و چه اندازه‌ای داشته باشه. و در نهایت با Paint و Compositing، پیکسل‌ها رو روی صفحه می‌کشه. JavaScript هم می‌تونه وسط این مسیر DOM یا استایل‌ها رو تغییر بده و بخشی از این کار رو دوباره راه بندازه.


به زبان ساده:

HTML  ──>  DOM   ──┐
                   ├──>  Layout  ──>  Paint  ──>  Pixels
CSS   ──>  CSSOM ──┘


یه نکته‌ی جالب هم اینجاست: CSSهای لازم برای رندر اولیه می‌تونن جلوی نمایش نهایی صفحه رو بگیرن؛ برای همین سرعت رسیدن همون styles.css مستقیماً روی سرعت دیده شدن صفحه اثر داره. جزئیات این مراحل خودش یه دنیای جداست که صفحه‌ی Browser Rendering سراغش می‌ره. ولی برای داستان ما همین تصویر کافیه:


مرورگر فقط HTML رو نمایش نمی‌ده؛ همه‌ی منابع رو پردازش می‌کنه و از روشون صفحه رو می‌سازه.


و این همون لحظه‌ایه که URLای که اول تایپ کردیم، بالاخره تبدیل می‌شه به پیکسل‌هایی که روی نمایشگر می‌بینیم.


۱۲. حالا کل سفر رو یک‌جا ببینیم



این مسیر یه مدل ساده‌شده از یه درخواست وب بود و در دنیای واقعی همیشه دقیقاً همین اجزا و همین ترتیب وجود ندارن. ممکنه Response از Cache مرورگر یا یه CDN بیاد و Request اصلاً به Web Application نرسه. Web Application ممکنه خودش از چند Service مختلف تشکیل شده باشه که با هم حرف می‌زنن.


و ممکنه ارتباط اصلاً روی TCP نباشه. در HTTP/3 به‌جای TCP از QUIC استفاده می‌شه که روی UDP کار می‌کنه و TLS رو هم در دل خودش داره. مرورگر ممکنه از قبل بفهمه که سرور HTTP/3 رو پشتیبانی می‌کنه؛ مثلاً چون سرور در یه Response قبلی با Header Alt-Svc این رو اعلام کرده، یا از طریق یه رکورد DNS از نوع HTTPS. اگه هم QUIC به هر دلیلی برقرار نشه، مثلاً چون شبکه UDP رو بسته، مرورگر ممکنه برگرده سراغ همون TCP.


ولی ایده‌ی اصلی در همه‌ی این حالت‌ها یکیه: یه درخواست مقصدش رو پیدا می‌کنه، ارتباطی امن می‌سازه، از چند لایه رد می‌شه، جایی پردازش می‌شه و جوابش برمی‌گرده.


و دونستن این مسیر فقط جواب یه سؤال کنجکاوانه نیست. دفعه‌ی بعد که یه صفحه کند باز شد، می‌دونی کجاها رو باید نگاه کنی: شاید DNS، شاید Handshakeها، شاید یه Query کند در Database، شاید ده‌ها فایلی که صفحه منتظرشون بود. چون حالا دیگه وقتی یه URL رو وارد می‌کنی و Enter می‌زنی، فقط یه صفحه‌ی وب نمی‌بینی.


یه Request رو می‌بینی که داره سفر می‌کنه.