Към съдържанието
wannacode

Как стига страница до теб

Проблем

Между Enter и страницата

Пишеш в браузъра shop.example.bg, натискаш Enter и след половин секунда виждаш каталога с кафе и чай. Страницата е на компютър в друг град, а може би в друга държава. Как браузърът го намира? Как разбира, че става дума за каталога, а не за количката? Какво точно си казват двете програми?

В следващия урок ще напишеш сървър, който отговаря на браузъра. За да го направиш, трябва да знаеш езика, на който си говорят.

Идеята

Пакети, рутери и IP адреси

Интернет е мрежа от мрежи: домашният ти рутер, мрежата на доставчика, мрежите на държави и центрове за данни, свързани помежду си.

  • Данните не пътуват на едно парче. Делят се на пакети, малки части от по около 1 500 байта. Всеки пакет носи адреса, за който е.
  • Рутерът е устройство, което получава пакет и го праща към следващата мрежа по пътя. Пакетът минава през 10–20 рутера, всеки го доближава до целта.
  • IP адресът е номерът на устройство в мрежата, например 93.184.215.14. Твоят компютър винаги може да се нарече localhost (127.0.0.1).
  • Портът е номер от 0 до 65 535 за програма на този компютър. IP адресът е сградата, портът е апартаментът. Уеб сървърите обикновено слушат на 80 (HTTP) и 443 (HTTPS).

Правилата, по които се говори, се казват протоколи. IP доставя отделните пакети, но не обещава, че ще стигнат. TCP стъпва върху IP: прави връзка между две програми, номерира пакетите, подрежда ги и праща наново загубените. Затова двете заедно се казват TCP/IP.

По-точно, за любопитните
  • Адресите от вида 93.184.215.14 са IPv4: четири числа от 0 до 255, около 4 милиарда адреса. Те не стигат, затова има и IPv6: 2606:2800:21f:cb07:6820:80da:af6b:8b2c.
  • Освен TCP има и UDP: без връзка и без препращане на загубеното, но по-бърз. Ползва се за глас и видео на живо и за игри. Уебът е върху TCP.
  • Двата края на една TCP връзка се казват сокети: адрес и порт от всяка страна. Портът на браузъра се избира сам, от свободните.

Идеята

Име вместо номер: DNS

Хората помнят имена, компютрите търсят по IP адрес. Домейнът е човешкото име на сайта: shop.example.bg. DNS (Domain Name System) превръща името в адрес:

  1. Браузърът пита DNS сървъра на доставчика: „кой е адресът на shop.example.bg?“
  2. Ако той не го знае, пита по веригата: сървъра за .bg, после сървъра на example.bg.
  3. Отговорът е IP адрес. Браузърът го запомня за известно време и следващия път не пита.

Чак тогава браузърът се свързва по TCP с този адрес, на порт 443.

Целият път на една страница, с частта от адреса, която ползва всяка стъпка:

Uri address = new Uri("https://shop.example.bg/products?category=coffee");
Console.WriteLine("1. DNS: кой е адресът на " + address.Host + "?");
Console.WriteLine("2. TCP: връзка до порт " + address.Port);
Console.WriteLine("3. HTTP: GET " + address.PathAndQuery);
Стъпка по стъпка, с думи
  1. Адресът казва всичко нужно: по кой протокол, до кой компютър и кой ресурс.
  2. Първо името: браузърът пита DNS кой е IP адресът на shop.example.bg.
  3. DNS отговаря с IP адрес, например 93.184.215.14. Браузърът го запомня.
  4. TCP връзка с този адрес. Портът не е в адреса, затова е 443, този на https.
  5. По връзката отива HTTP заявка: метод GET и пътят /products?category=coffee.
  6. Сървърът отговаря: статус 200 OK и HTML. Браузърът рисува страницата.
  7. Три стъпки: име в адрес (DNS), връзка (TCP), заявка и отговор (HTTP).

Хостингът е компютърът, на който живее сайтът: сървър в център за данни, винаги включен и свързан. Домейнът сочи хостинга: в DNS пише „shop.example.bg е на този IP адрес“.

По-точно, за любопитните
  • Домейн се регистрира срещу годишна такса през регистратор: фирма, акредитирана за съответния край (.bg, .com, .eu). Регистрацията е за определен срок, обикновено година, и се подновява. Ако не я подновиш, името се освобождава и всеки може да го вземе. Добрият домейн е кратък, лесен за запомняне и казва за какво е сайтът.
  • Хостингът има няколко вида. Споделен: много сайтове на един сървър, евтин, но съседите ти влияят. VPS (виртуален частен сървър): твоя част от сървър с гарантирани ресурси. Облак: плащаш колкото ползваш и растеш при нужда. Собствен (dedicated) сървър: цяла машина само за теб. Изборът зависи от посещенията, от нужните ресурси и от бюджета.

Виж го

Адресът и частите му

Адресът на страница се казва URL (Uniform Resource Locator). Той казва всичко нужно, за да се стигне до нея. В документацията ще срещнеш и по-общото име URI (Uniform Resource Identifier), откъдето идва и името на класа. В C# частите му дава класът Uri:

Uri address = new Uri("https://shop.example.bg:8443/products?category=coffee&page=2");
Console.WriteLine("Схема: " + address.Scheme);
Console.WriteLine("Хост: " + address.Host);
Console.WriteLine("Порт: " + address.Port);
Console.WriteLine("Път: " + address.AbsolutePath);
Console.WriteLine("Заявка: " + address.Query);
Uri home = new Uri("https://shop.example.bg/");
Console.WriteLine("Порт по подразбиране: " + home.Port);
Изход
Схема: https
Хост: shop.example.bg
Порт: 8443
Път: /products
Заявка: ?category=coffee&page=2
Порт по подразбиране: 443
  • Схема (https): по кой протокол.
  • Хост (shop.example.bg): кой компютър. Името минава през DNS.
  • Порт (8443): коя програма на него. Ако го няма в адреса, е този по подразбиране за схемата: 443 за https, 80 за http.
  • Път (/products): кой ресурс на сървъра.
  • Заявка (query, ?category=coffee&page=2): допълнителни параметри като двойки име=стойност, разделени с &.

Какво ще отпечата програмата?

Uri cart = new Uri("http://shop.example.bg/cart?step=2");
Console.WriteLine(cart.Port + " " + cart.AbsolutePath);
Отговор
По-точно, за любопитните
  • В адреса може да има само латински букви, цифри и няколко знака. Всичко друго, кирилица и интервали, се записва като байтове в UTF-8 с % отпред. Браузърът ти показва „зелен чай“, но по мрежата отива кодираното:
Uri search = new Uri("https://shop.example.bg/search?q=зелен чай");
Console.WriteLine(search.AbsoluteUri);
Console.WriteLine(Uri.UnescapeDataString(search.Query));
Изход
https://shop.example.bg/search?q=%D0%B7%D0%B5%D0%BB%D0%B5%D0%BD%20%D1%87%D0%B0%D0%B9
?q=зелен чай
  • След пътя може да има и фрагмент: #reviews. Той не стига до сървъра: браузърът само превърта страницата до тази част.

Идеята

HTTP заявка

Браузърът се е свързал. Сега праща HTTP заявка (HyperText Transfer Protocol). Тя е текст:

Заявка
GET /products?category=coffee HTTP/1.1
Host: shop.example.bg
Accept: text/html
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Firefox/131.0
  • Първият ред е метод, път и версия. Методът казва какво искаш: GET е „дай ми“, POST е „ето ти данни“ (поръчка, форма). Има и PUT (замени), DELETE (изтрий) и други.
  • После идват заглавките (headers), по една на ред: име: стойност. Host казва кой сайт искаш, защото на един сървър може да има много сайтове. Accept казва какво разбира браузърът, User-Agent кой е той.
  • Празен ред завършва заглавките. След него може да има тяло: данните на заявката. GET няма тяло, POST обикновено има:
Заявка с тяло
POST /cart HTTP/1.1
Host: shop.example.bg
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
product=C-101&qty=2

Content-Length казва колко байта е тялото, за да знае сървърът кога е получил всичко.

Идеята

HTTP отговор

Сървърът отговаря със същия вид текст. Първият ред носи статус: число и кратко обяснение. Първата цифра казва вида:

Статус Вид Пример
2xx успех 200 OK: ето каквото поиска
3xx пренасочване 301 Moved Permanently: вече е на друг адрес
4xx грешка в заявката 404 Not Found: няма такова нещо
5xx грешка на сървъра 500 Internal Server Error: програмата на сървъра се провали

После са заглавките на отговора и тялото: HTML на страницата, JSON с данни, картинка.

Отговор
HTTP/1.1 404 Not Found
Content-Type: text/html; charset=utf-8
Content-Length: 43
<h1>Няма такъв продукт</h1>

Content-Type казва какво е тялото и в каква кодировка. При пренасочване заглавката Location казва новия адрес, а браузърът сам праща нова заявка натам:

Пренасочване
HTTP/1.1 301 Moved Permanently
Location: https://shop.example.bg/products
По-точно, за любопитните
  • HTTP няма памет (stateless): всяка заявка е отделна и сървърът не помни предишните. Затова, за да те „помни“ в количката, сайтът ти дава бисквитка (cookie): заглавка, която браузърът праща обратно с всяка следваща заявка.
  • Същата липса на памет се използва при DDoS атака през HTTP: хиляди заразени компютри пращат огромен брой заявки към един сървър. Всяка изглежда като обикновена и сървърът трябва да я обработи, докато не спре да смогва и за истинските клиенти. Защитата е пред сървъра: услуги, които филтрират съмнителния трафик и ограничават броя заявки от един адрес.

Виж го

Отговорът, разглобен с код

Отговорът е просто текст, затова можеш да го разглобиш с методите на текста. Краят на всеки ред в HTTP е \r\n, а празният ред между заглавките и тялото е \r\n\r\n:

string response = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html; charset=utf-8\r\n" +
"Content-Length: 27\r\n" +
"\r\n" +
"<h1>Кафе и чай</h1>";
string[] parts = response.Split("\r\n\r\n");
string[] lines = parts[0].Split("\r\n");
string[] status = lines[0].Split(' ', 3);
Console.WriteLine("Версия: " + status[0]);
Console.WriteLine("Статус: " + status[1] + " (" + status[2] + ")");
Dictionary<string, string> headers = new Dictionary<string, string>();
for (int i = 1; i < lines.Length; i++)
{
string[] header = lines[i].Split(": ", 2);
headers[header[0]] = header[1];
}
Console.WriteLine("Тип: " + headers["Content-Type"]);
Console.WriteLine("Дължина: " + headers["Content-Length"] + " байта");
Console.WriteLine("Тяло: " + parts[1]);
Изход
Версия: HTTP/1.1
Статус: 200 (OK)
Тип: text/html; charset=utf-8
Дължина: 27 байта
Тяло: <h1>Кафе и чай</h1>
  • Split("\r\n\r\n") дели на две: заглавна част и тяло.
  • Заглавната част се дели на редове. Първият е статусът. Split(' ', 3) дава най-много 3 части: така при 404 Not Found обяснението остава цяло.
  • Заглавките влизат в речник: име → стойност. Split(": ", 2) дели само на първото : .

Така работи всяка програма, която говори HTTP. В следващия урок това ще прави HttpClient вместо теб.

По-точно, за любопитните
  • Content-Length е 27 байта, а текстът е 19 знака. По мрежата отиват байтове в UTF-8, а там всяка кирилска буква е 2 байта:
string body = "<h1>Кафе и чай</h1>";
Console.WriteLine(body.Length + " знака");
Console.WriteLine(System.Text.Encoding.UTF8.GetByteCount(body) + " байта");
Изход
19 знака
27 байта
  • Имената на заглавките не зависят от главни и малки букви: content-type и Content-Type са едно и също. Речник, който сравнява така, се прави с new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase).
  • HTTP/1.1 е текст. По-новите HTTP/2 и HTTP/3 пращат същите методи, заглавки и статуси, но в двоичен вид и с много заявки по една връзка. За програмата ти смисълът е същият.

Пробвай

Заглавка с двоеточие

Програмата трябва да отпечата името и стойността на заглавката, но вместо адреса печата само https. На кой ред е грешката?

Избери реда с грешката.

Идеята

HTTPS: същото, но заключено

HTTP е обикновен текст. Всеки рутер по пътя може да прочете паролата ти или да подмени цената. HTTPS е HTTP през TLS (по-старото име е SSL):

  1. Ръкостискане (handshake). Браузърът и сървърът се договарят как да криптират и си разменят ключове.
  2. Сертификат. Сървърът показва сертификат, подписан от организация, на която браузърът вярва. Така знаеш, че говориш с истинския shop.example.bg, а не с някой по средата.
  3. Криптиране. Оттам нататък заявките и отговорите пътуват криптирани. По пътя се вижда до кой сървър отиваш, но не и какво си казвате.

Методите, заглавките и статусите са същите като при HTTP. Катинарчето в адресната лента значи, че връзката е по HTTPS.

Идеята

Какво прави браузърът

Първият отговор е HTML: текстът и структурата на страницата. Браузърът го чете и в него намира още ресурси: стилове (CSS), програми (JavaScript), картинки, шрифтове. За всеки праща нова заявка. Обикновена страница прави десетки заявки, преди да е готова. Затова сайт с много тежки картинки се зарежда бавно, дори HTML-ът да е малък.

После браузърът подрежда всичко и го рисува на екрана. JavaScript може да промени страницата и след това, без да я зарежда наново.

По-точно, за любопитните
  • Браузърът има няколко части. Потребителският интерфейс: адресната лента, бутоните, отметките, всичко освен самата страница. Двигателят на браузъра свързва интерфейса с двигателя за изобразяване, който разбира HTML и CSS и рисува. Мрежовата част прави HTTP заявките. Слой за рисуване на основни контроли (падащи списъци, прозорци), общ за всички операционни системи. Интерпретаторът на JavaScript изпълнява скриптовете. Хранилището пази бисквитки и данни на сайтовете (localStorage, IndexedDB).
  • Как се рисува: от HTML се строи дърво на елементите (DOM), от CSS дърво на стиловете (CSSOM). Двете заедно дават дървото за изобразяване (render tree). После се смята къде е всеки елемент и колко е голям (layout), и накрая се рисува (paint). JavaScript променя DOM и браузърът изобразява наново само засегнатото.
  • За повече: Как работи интернет, Какво е HTTP, Как работят браузърите и DDoS атака с HTTP заявки (на английски).

Упражнение

Адреси, статуси и заявки

Реши задачите в редактора си.

1. Части на адрес. За https://shop.example.bg/search?q=espresso&max=30&page=2 отпечатай хоста, порта и пътя. После раздели заявката на двойки в речник (TrimStart('?') маха въпросителния знак, после Split('&') и Split('=')), отпечатай ги и вземи номера на страницата като число:

Изход
Хост: shop.example.bg, порт 443
Път: /search
q = espresso
max = 30
page = 2
Страница: 2

2. Какво значи статусът. Метод Meaning(int code) със switch връща смисъла на известните кодове 200, 201, 301, 404 и 500. За другите казва вида по първата цифра (code / 100), а за непознат вид връща „непознат код“:

Изход
200: успех
201: създадено
301: преместено завинаги
404: няма такъв ресурс
418: грешка в заявката
500: грешка на сървъра
503: друга грешка на сървъра

3. Сглоби заявка. Метод BuildGet(Uri address) връща текста на GET заявка с Host и Accept: text/html, с \r\n в края на всеки ред и празен ред накрая. address.PathAndQuery дава пътя заедно със заявката. Отпечатай я, като замениш всяко \r\n с видимия текст \r\n и нов ред:

Изход
GET /products?category=tea HTTP/1.1\r\n
Host: shop.example.bg\r\n
Accept: text/html\r\n
\r\n

Тест

Провери се

1. Какво прави DNS?
2. В адреса https://shop.example.bg/products?category=tea кой е портът?
3. Отговорът е със статус 404. Какво означава?
4. Как клиентът разбира, че в отговора свършват заглавките и започва тялото?
5. Какво добавя HTTPS към HTTP?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·