Съдържание на курса
Урок 0 — Как работи уебът
От Dorian Chávez · основател на Hábil и архитект на интеграции ·
Време: две сесии от около 90 минути. Разпределение, което работи: в първата — „Защо, преди как“, адресът (0.1) и разговорът (0.2), с отворен терминал, за да повториш всяко curl и всяко dig; във втората — кой какво прави (0.3) с раздела „Мрежа“, „Грешката, която ще видиш“ и упражненията. Това е урок за четене и наблюдаване, но се чете с терминала и браузъра до теб, не на един дъх.
Какво изграждаш: още нищо; този урок е за четене и наблюдаване. Изграждаш мисловната карта, която ще носи останалите единайсет.
Какво научаваш: URL, DNS, HTTP, заявка и отговор; какво прави браузърът и какво сървърът; разделът „Мрежа“ на инструментите на браузъра.
Страниците на този урок. Единствената примерна страница, fig00_01.html, е в programas/00-como-funciona-la-web/ на хранилището на курса. Днес не е нужно да я отваряш: достатъчно е да я прочетеш тук, а в Урок 1 ще се научиш да я сервираш от своя компютър.
След урока ще можеш да
- Разделяш един URL на схема, хост, порт, път, запитване и фрагмент и казваш коя от тези части решава към коя машина се обръщаш и коя решава какво ѝ искаш.
- Обясняваш какво прави DNS и отличаваш неговата работа от работата на HTTP.
- Четеш HTTP заявка и HTTP отговор —началния ред, заглавните части, тялото— и казваш какво означава кодът на състоянието на отговора.
- Обясняваш защо една-единствена страница са много заявки и какво решава браузърът и какво решава сървърът във всяка от тях.
- Казваш коя част от кода ти се изпълнява на машината на този, който посещава страницата, и какво следва от това за сигурността.
- Отваряш раздела „Мрежа“ на инструментите на браузъра, намираш основната заявка на една страница и четеш нейното състояние, тип, размер и време.
- При повреда казваш на кой от три слоя е станала —името, връзката или отговорът— преди да промениш каквото и да било.
Защо, преди как
Представи си човека, който поддържа услугите на една фирма. Седем сутринта е, отваря таблото на revisor —проектът, който ще изградиш по време на курса— и вижда ред, който казва „cargando…“ (зарежда се…) и не се променя. Какво е станало? Може интернет връзката му да е паднала. Може името на сървъра вече да не сочи никъде. Може сървърът да е включен, но този файл вече да не съществува. Може да съществува, но сървърът да се бави половин минута да го върне. Или може всичко да е пристигнало наред, а собственият ти код да не знае как да го начертае. Пет причини, един-единствен симптом, и човекът, който гледа екрана, не може да ги различи. Ти ще можеш, но само ако знаеш какво се случва между момента, в който някой напише адрес, и момента, в който види страницата.
Това е работата на този урок. Той не пише нито един ред от таблото и въпреки това е този, който най-много грешки ще ти спести. По-голямата част от нещата, които най-много объркват новака —404, съобщение, което говори за „CORS“, страница, която се вижда без стилове, модул, който не се зарежда, данни, които пристигат празни— не са грешки на езика: те са недоразумения за този разговор. Който няма картата, започва да сменя код наслуки; който я има, пита първо „на коя от стъпките се счупи?“ и почти винаги отговорът е пред очите му за по-малко от минута.
Картата се побира в едно изречение: браузърът пита, сървърът отговаря, а всичко останало е подробност как се казват нещата. Това е същата идея, с която се ражда уебът: предложението, което Тим Бърнърс-Лий написва през 1989 г., описва свързани документи, които една програма иска от друга, а първият уебсайт, който CERN пази, все още може да се отвори и да се прочете. Този урок развива тази подробност в три части и всяка носи своя истински пример, измерен на 7 октомври 2026 г. срещу публична страница, която съществува точно за това. Първо, адресът: какво е URL и как име като example.com се превръща в конкретна машина. Второ, разговорът: какво точно си казват браузърът и сървърът, дума по дума. Трето, разпределението на задачите: какво прави всеки, къде живее твоят код и как всичко това се наблюдава с раздела „Мрежа“ на браузъра.
Не е нужно да инсталираш нищо, за да я следваш, освен в упражненията, които ползват браузъра и терминала. Тъй като урокът ти показва команди за терминала още от първия раздел, ето минимумът, за да ги четеш; Урок 1 го развива спокойно:
- Терминалът е прозорец, в който вместо да щракаш, пишеш команди. В Linux Mint се отваря с
Ctrl+Alt+T. - Знакът
$не се пише. В блоковете на курса ред, който започва с$, е команда, която ти въвеждаш без този знак и завършваш с натискане наEnter. Редовете след нея, без$, са онова, с което терминалът отговаря: не се пишат, четат се. - Командата е програма, следвана от опции. В
curl -I https://example.comcurlе програмата (такава, която изтегля уеб адреси),-Iе опция („покажи ми само заглавните части“), а URL адресът е онова, което ѝ искаш. - Вертикалната черта
|свързва две команди: онова, което отпечатва първата, влиза във втората, вместо да излезе на екрана. Ще видиш| head -3, която пропуска само първите три реда. На латиноамериканската клавиатура|е на клавиша вляво от1. /dev/nullе специален файл на Linux, който изхвърля всичко, което получи. Когато видишcurl … -o /dev/null, опцията-o(„запиши съдържанието в…“) праща страницата в това кошче и на екрана остават само съобщенията за разговора, които са онова, което искаме да разгледаме.
Ако някоя команда ти отговори command not found, тази програма не е инсталирана: Урок 1 обяснява как се инсталира със sudo apt install и защо отпред се пише sudo. А ако сега не можеш да ползваш терминал, не спирай: чети показаните изходи, които са истински, и направи упражненията за терминала, когато завършиш Урок 1.
Една последна методическа бележка. В този урок ще видиш много суров текст: редове, които браузърът и сървърът си разменят, такива, каквито са. Не е за наизустяване. Целта е да загубиш страха, че уебът е черна кутия: не е, той е разговор в текст, който може да се прочете, и щом веднъж си го прочел, вече не забравяш, че може.
Понятията
Те са три и се опират едно на друго. Чети ги по ред.
0.1 Адресът: URL и DNS
URL е адрес с части
Когато пишеш нещо в адресната лента на браузъра или щракаш върху връзка, онова, което използваш, е URL (на английски Uniform Resource Locator, унифициран локатор на ресурси). Прилича на пощенски адрес: не е произволно име, има части, всяка част отговаря на различен въпрос и редът има значение. Нека вземем измислен, но пълен URL, от тези, които би могъл да ползва revisor:
https://example.com:8443/data/services.json?status=down&sort=name#row-3
Чети го отляво надясно и дай име на всяко парче:
| Част | В примера | На какво отговаря |
|---|---|---|
| Схема | https |
На какъв език говорим? (протоколът: HTTP, шифрован с TLS) |
| Хост (домакин) | example.com |
С коя машина говоря? |
| Порт | 8443 |
На коя от вратите на тази машина чукам? |
| Път | /data/services.json |
Кой ресурс искам? |
| Запитване (query) | ?status=down&sort=name |
При какви условия или с какви допълнителни данни? |
| Фрагмент | #row-3 |
Към коя част от вече получения документ ме води браузърът? |
Тези части са определени в стандарта за URL (URL Standard на WHATWG, организацията, която поддържа HTML и почти цялата среда на браузъра) и, по-стар и по-кратък, в RFC 3986 на IETF. Ако някога ти трябва нещо точно за тях —например кои знаци може да съдържа един път— отговорът е там; не е нужно да ги наизустяваш.
Има четири подробности, които си струва да запомниш отсега, защото всяка ще те спаси от една спънка:
Портът почти никога не се пише. Една машина може да слуша едновременно на хиляди номерирани „врати“; всяка услуга обслужва на една. По конвенция нешифрованият уеб ползва 80, а шифрованият уеб ползва 443 и браузърът ги подразбира, когато не напишеш нищо. Затова https://example.com и https://example.com:443 са едно и също. Когато в Урок 1 вдигнеш сървър на своя компютър, той ще ползва друга врата —8000— и тогава вече ще трябва да я напишеш: http://localhost:8000.
Фрагментът не пътува. Всичко отляво на # се изпраща на сървъра; фрагментът — не. Той остава в браузъра, който го ползва, за да те превърти към частта от страницата с този идентификатор. Това е едно от най-честите обърквания на новака: „пратих на сървъра #row-3 и той не го получи“. Правилно е, че не го е получил, и ще го провериш със собствените си очи в раздела „Мрежа“.
Пътят не е папка. /data/services.json прилича на път през папки и в един прост файлов сървър, какъвто ще ползваш, действително съвпада с такъв. Но това е решение на сървъра, не закон: има сървъри, където /users/42 не отговаря на никакъв файл и отговорът се изчислява в момента. За браузъра пътят е просто текст, който той предава на сървъра, за да реши какво да отговори. И една полезна конвенция: когато пътят завършва с /, повечето файлови сървъри връщат index.html на тази папка. Затова адресът на началната страница може да бъде просто https://example.com/.
Главните и малките букви не значат едно и също навсякъде. Хостът не прави разлика: Example.COM и example.com са едно и също. Пътят, напротив, обикновено прави разлика. Онова, което на твоя компютър с Windows или macOS работи като Logo.PNG, на Linux сървър —където почти винаги живее един сайт— може да даде „не е намерен“, ако файлът се казва logo.png. Правилото на курса идва оттук: имена на файлове с малки букви, без интервали и без ударения.
Като стана дума за интервали и ударения: един URL може да съдържа само ограничен набор от знаци. Останалите се записват с процентно кодиране: знакът се превръща в своите байтове (в UTF-8) и всеки байт се записва като % плюс две шестнайсетични цифри. Интервалът е %20; ñ са два байта, %C3%B1. Така файл, наречен mi página.html, би се поискал като mi%20p%C3%A1gina.html. Браузърът го прави вместо теб, но в деня, когато видиш тази „супа от проценти“ в адресна лента или в журнала на сървъра, ще знаеш за какво става дума и ще знаеш защо е по-добре да не слагаш интервали и ударения на файловете си.
Абсолютни и относителни URL
Дотук видяхме цял URL. Но в една страница почти никога не пишеш пълния адрес на всеки файл. Пишеш нещо като css/estilo.css и браузърът го допълва, като взима за основа адреса на страницата, в която се намира. Това е относителен URL и ще го ползваш във всички следващи уроци, така че си струва да видиш как се разрешава. Да приемем, че текущата страница е http://localhost:8000/panel/index.html:
| Което пишеш | Как се чете | Резултат |
|---|---|---|
estilo.css |
същата папка като страницата | http://localhost:8000/panel/estilo.css |
css/estilo.css |
подпапка на текущата папка | http://localhost:8000/panel/css/estilo.css |
../estilo.css |
една папка нагоре | http://localhost:8000/estilo.css |
/estilo.css |
от корена на сайта, където и да си | http://localhost:8000/estilo.css |
Разликата между css/estilo.css (без начална наклонена черта) и /css/estilo.css (с начална наклонена черта) е причина за много „при мен се вижда“. Първият зависи от това къде е страницата; вторият — не. В този курс ще предпочитаме относителните без начална черта за собствените файлове, защото така проектът може да се мести от папка на папка, без да се счупи.
DNS: указателят, който превръща имена в адреси
Вече умееш да четеш URL. Остава да разрешим един проблем, който може би не си бил забелязал: компютрите не се намират по име. Намират се по IP адрес, който е число. Един IPv4 адрес са четири числа между 0 и 255, разделени с точки (104.20.23.154); IPv6 е по-дълъг и се пише в шестнайсетична система, и съществува, защото IPv4 адресите свършиха. Човекът помни example.com; мрежата има нужда от числото. Между двете стои DNS (Domain Name System, система за имена на домейни), който е, силно опростено казано, разпределен телефонен указател: ти му даваш име и той ти връща един или няколко адреса.
„Разпределен“ е важната дума. Не съществува един-единствен централен указател, който някой трябва да поддържа: той би бил тясно място и единична точка на отказ. DNS е йерархия. Името example.com се чете отдясно наляво: крайната точка (която почти винаги се пропуска) е коренът; com е домейнът от най-високо ниво; example е домейн вътре в com. Всяко ниво се администрира от различен човек и всеки администратор знае само към кого да се обърне за нивото под него. Така работи пълното запитване, което на практика се прави вместо теб:
- Твоят компютър пита своя резолвър: DNS сървър, който му е зададен от мрежата ти (на доставчика ти, на фирмата ти) или който си избрал ти. Ако резолвърът вече има отговора запазен, отговаря веднага и свършва.
- Ако няма, резолвърът пита коренов сървър. Коренът не знае къде е
example.com, но знае кой администрираcomи му казва към кого да се обърне. - Резолвърът пита сървърите на
com. Те също не знаят крайния адрес, но знаят кой администрираexample.comи го казват. - Резолвърът пита авторитетните сървъри на
example.com: онези, които имат истинския отговор. Те отговарят с адресите. - Резолвърът ги предава на твоя компютър и ги запазва за известно време, в случай че някой пак ги поиска.
Това може да се види. С инструмента dig (в Mint се инсталира със sudo apt install bind9-dnsutils) питаш за записи от тип A, които свързват име с IPv4 адрес. Това е истинският изход, който получих на 7 октомври 2026 г.:
$ dig example.com +noall +answer
example.com. 240 IN A 172.66.147.243
example.com. 240 IN A 104.20.23.154
Всеки ред казва: името, TTL (time to live, време на живот) в секунди, класа (IN, от internet), типа на записа (A) и адреса. Две наблюдения. Първо: има два адреса за едно име; нормално е, големите услуги разпределят натоварването между няколко машини. Второ: TTL от 240 означава „можеш да пазиш този отговор четири минути, преди да питаш отново“. Тази памет прави DNS бърз и тя е също причината, поради която промяната на един адрес не се вижда едновременно по целия свят: някои резолвъри още пазят стария отговор. Когато в Урок 11 публикуваш сайта си, ще си спомниш този абзац.
Ако попиташ за сървърите на зоната com, отговорът показва йерархията в действие:
$ dig +short NS com | head -3
g.gtld-servers.net.
f.gtld-servers.net.
m.gtld-servers.net.
(Имената са тринайсет, от a.gtld-servers.net до m.gtld-servers.net; на практика всяко от тях всъщност е флот от машини, разпръснати по света.) Твоят компютър няма нужда да знае това; резолвърът — да.
Две уточнения, за да не се объркаш по-късно. DNS не е уебът: ползва го и електронната поща, и почти всичко, което ползва интернет. Прави само едно нещо —име към адрес— и не знае нищо за страници. И localhost е специално име: резервирано е да означава „този същият компютър“ и се разрешава, без да излиза от него, към адресите за локален цикъл (loopback): 127.0.0.1 в IPv4 и ::1 в IPv6. Стандартът, който го резервира, е RFC 6761. Това, че са два, има значение за една подробност в Урок 1: ако една програма слуша само на единия от тях, браузърът, който не получи отговор на другия, опитва и първия. Когато браузърът ти отвори http://localhost:8000, няма да има пътуване през интернет: ще говори с програма, която работи на твоята собствена машина.
Една бележка за поверителността, и нищо повече. По традиция DNS запитванията пътуват нешифровани, така че който е по пътя, може да научи кои имена питаш. Съществува шифриран вариант, DNS през HTTPS (RFC 8484), който някои браузъри включват. Той не променя нищо от това, което научаваш тук; просто е добре да знаеш, че поверителността на името и поверителността на страницата са две различни неща.
0.2 Разговорът: HTTP
От името до връзката
С IP адреса в ръка браузърът отваря връзка с тази машина на посочената врата. Прави го с TCP, протокол, чиято работа е два компютъра да се разберат да говорят и да гарантират, че онова, което единият изпраща, пристига цялостно и по ред. Започва с кратка размяна на три съобщения („тристранното ръкуване“) и оттам нататък има отворен канал.
Ако схемата е https, преди да се каже нещо интересно, се прави второ ръкуване, този път за да се шифрова каналът: браузърът и сървърът договарят таен ключ, а сървърът представя своето TLS удостоверение: цифров документ, подписан от орган, на който браузърът ти се доверява, който доказва, че той наистина е собственикът на името, за което се представя. Това е частта „S“ в HTTPS (secure, сигурен) и я изпълнява протокол, наречен TLS. Можеш да я видиш в изхода на curl -v, инструмент, който изтегля един URL и разказва какво прави:
$ curl -v https://example.com -o /dev/null
* Host example.com:443 was resolved.
* IPv4: 104.20.23.154, 172.66.147.243
* Trying 104.20.23.154:443...
* Connected to example.com (104.20.23.154) port 443
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* Server certificate:
* subject: CN=example.com
* SSL certificate verify ok.
Всеки ред разказва стъпка, която току-що научи: името беше разрешено (DNS), беше опитан първият адрес (връзка), беше договорен TLS 1.3 (шифроване) и удостоверението на сървъра беше проверено (verify ok). (Съкратих изхода, за да се побере; пълният носи и редовете на ръкуването и на заявката. Измерих го с curl 8.7.1 на macOS; curl на Linux Mint 22, версия 8.5.0, ползва друга библиотека за шифроване, така че някои редове —най-вече този, който назовава алгоритъма след TLSv1.3— са формулирани различно. Стъпките са същите.) Всичко това се случи преди страницата да бъде поискана.
Добре е да запомниш какво ти дава и какво не ти дава катинарчето на HTTPS. Дава ти три неща: поверителност (който е по пътя, не може да прочете нито онова, което искаш, нито онова, което получаваш), цялостност (никой не може да го промени, без да се забележи) и автентичност (говориш с притежателя на името, което е било проверено). Не ти дава никаква гаранция за намеренията на този притежател: една измамна страница може да има напълно валидно катинарче. И не скрива също с кого говориш: който е по пътя, може да знае, че си се свързал с example.com, макар да не знае коя страница си поискал нито какво си получил.
Заявката и отговорът
С отворения канал започва HTTP (HyperText Transfer Protocol, протокол за пренос на хипертекст), който е схема от въпрос и отговор: клиентът изпраща заявка, сървърът връща отговор и дотук свършва размяната. Сървърът никога не започва разговор по своя инициатива; само отговаря. Тази асиметрия е гръбнакът на уеба.
Удивителното е колко е просто. В своята версия 1.1 заявката е текст, който можеш да прочетеш. Ето тази, която curl изпрати на example.com, като наложих тази версия с --http1.1, за да може да се види в явен вид:
GET / HTTP/1.1
Host: example.com
User-Agent: curl/8.7.1
Accept: */*
А ето отговора, също измерен на 7 октомври 2026 г. (частта със заглавните части; тялото ще видим веднага след това):
HTTP/1.1 200 OK
Date: Wed, 07 Oct 2026 16:53:12 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: cloudflare
last-modified: Fri, 02 Oct 2026 16:11:02 GMT
allow: GET, HEAD
Accept-Ranges: bytes
Age: 2482
cf-cache-status: HIT
CF-RAY: a46e6bb0cd40cb67-DFW
alt-svc: h3=":443"; ma=86400
Двете съобщения имат една и съща форма от три части, която е формата на всяко съобщение в HTTP/1.1 (по-новите версии, които ще видиш по-нататък в урока, носят същите части, опаковани по друг начин):
- Началният ред. В заявката се нарича ред на заявката и съдържа три думи: метода (
GET, „дай ми“), целта (/, пътят, който видя в URL) и версията на протокола. В отговора се нарича ред на състоянието и съдържа версията, кода на състоянието (200) и кратка фраза (OK), която е само за да я прочете човек. - Заглавните части. Редове от вида
Име: стойност, по един на ред. Те са метаданни: информация за заявката или отговора, не самото съдържание. - Празен ред и след него тялото: съдържанието (в
GETзаявка почти никога няма; в отговора, тук, е HTML на страницата).
Този празен ред е онзи, който отделя заглавните части от тялото. Технически детайл, който обяснява защо заявката по-горе завършва с празен ред.
Сега заглавните части, които се появиха, за да не са непрозрачен текст. От заявката: Host казва към кое име е насочена заявката. Изглежда излишно —вече се свързахме с адреса— но една-единствена машина може да обслужва стотици сайтове и именно Host ѝ казва кой от тях искаш. User-Agent се представя (тук curl, с версията на машината, където измервах; твоят ще казва своята; браузърът ти слага дълъг низ с името и версията си). Accept казва какви типове съдържание се приемат (*/*: всякакви). От отговора: Date е часът на сървъра. Content-Type е най-важният за теб и ще се върнем към него по-долу. Server определя програмата, която е отговорила. last-modified е кога ресурсът е бил променен за последен път. allow изброява методите, които този ресурс приема. Age казва колко секунди това копие е съхранявано в междинен кеш, а alt-svc съобщава, че същият сървър говори и HTTP/3. Останалото (Transfer-Encoding, Connection, Accept-Ranges) са подробности за начина на предаване и още не ти трябват. Ще видиш и заглавни части с префикси на доставчика, като cf-cache-status: те са допълнения от инфраструктурата на този, който сервира страницата, не част от протокола.
А тялото на този отговор е просто един HTML файл:
<!-- fig00_01.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Página de ejemplo</title>
</head>
<body>
<main>
<h1>Página de ejemplo</h1>
<p>Este texto viaja del servidor al navegador dentro de una respuesta HTTP.</p>
</main>
</body>
</html>
Página de ejemplo
Este texto viaja del servidor al navegador dentro de una respuesta HTTP.
(Вторият блок е онова, което би видял на екрана при зареждане на страницата; значението на всеки таг ще видим в Урок 2. Засега е достатъчно да видиш, че е само текст.) Ако един сървър сервираше този файл, пълният отговор би бил този, с прекъсванията на редовете, които протоколът изисква:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 290
<!-- fig00_01.html -->
<!DOCTYPE html>
...
Content-Length казва колко байта е тялото, за да знае браузърът кога е свършило; 290 е колкото е този файл. Не е отговор, който сме уловили от истински сървър —а такъв, какъвто би създал един—, а в Урок 1 ще видиш автентичен, от твоя собствен сървър.
Едно пояснение, което ще ти спести объркване. Ако махнеш от curl опцията --http1.1 и поискаш само заглавните части (-I), отговорът започва различно. Това е, което даде на 7 октомври 2026 г. (съкратено до първите редове):
$ curl -I https://example.com
HTTP/2 200
date: Wed, 07 Oct 2026 20:42:08 GMT
content-type: text/html; charset=utf-8
server: cloudflare
По подразбиране curl и сървърът се разбраха за HTTP/2 200, не за HTTP/1.1 200 OK, а имената на заглавните части пристигат с малки букви. HTTP има версии. 1.1 (RFC 9112) е текстовата, която току-що прочете. 2 (RFC 9113) и 3 (RFC 9114, върху различен транспорт, наречен QUIC, RFC 9000) изпращат същото в двоичен формат —и със сгъстени заглавни части—, по-ефикасен, който вече не може да се чете с просто око. Променя се опаковката; значението —методи, кодове на състоянието, заглавни части— е същото и в трите и е определено само веднъж, в RFC 9110. Затова изучаването на текстовата форма не е упражнение по носталгия: то е изучаване на езика, с предимството, че се чете. Ще видиш, че сървърът на твоя компютър отговаря с HTTP/1.0 (RFC 1945), още по-стара версия: стига за учебен сървър и, пак, значението не се променя.
Методите: какво се иска от сървъра
Методът е глаголът на заявката. Тези, които имат значение в този курс, са малко:
| Метод | За какво | Променя ли нещо на сървъра? |
|---|---|---|
GET |
да поискаш ресурс | не |
POST |
да изпратиш данни, за да ги обработи сървърът (например да създаде нещо) | обикновено да |
PUT |
да замениш ресурс с този, който изпращаш | да |
DELETE |
да изтриеш ресурс | да |
HEAD |
същото като GET, но без тяло: само заглавните части |
не |
RFC 9110 им дава две свойства с точни имена. Един метод е безопасен, когато не цели да променя нищо на сървъра: GET и HEAD са такива и затова браузърът може да ги повтаря, да ги зарежда предварително или да ги съхранява без страх. Един метод е идемпотентен, когато повтарянето му дава същия резултат като еднократното изпълнение: PUT и DELETE са такива (да изтриеш две пъти едно и също оставя същия свят, както да го изтриеш веднъж), POST не е (изпращането два пъти на една поръчка може да създаде две поръчки). Тази разлика обяснява защо, ако презаредиш страница, получена от POST, браузърът те предупреждава, преди да изпрати наново. Засега всичко, което ти трябва, е: зареждането на страница е GET, а Урок 10 ще използва GET и POST от формите.
Кодовете на състоянието: кой има проблема
Кодовете на състоянието са три цифри и първата вече ти казва почти всичко: семейството.
| Семейство | Означава | Които ще видиш |
|---|---|---|
| 1xx | информационно, разговорът продължава | почти никога |
| 2xx | успех | 200 OK, 201 Created, 204 No Content |
| 3xx | пренасочване: онова, което търсиш, е другаде | 301 и 302 (друг адрес), 304 Not Modified (ползвай своето копие) |
| 4xx | грешка на клиента: заявката е невалидна или не може да бъде обслужена | 400, 401, 403, 404 Not Found, 429 Too Many Requests |
| 5xx | грешка на сървъра: заявката е била валидна, но обслужването ѝ е пропаднало | 500, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout |
Разграничението 4xx / 5xx е злато за дебъгване, защото показва откъде да започнеш да търсиш: 404 казва „не намерих онова, което поиска“ (или, понякога, „няма да ти кажа дали съществува“); 500 казва „натъкнах се на неочакван проблем от моя страна, докато те обслужвах“. Не винаги е толкова чисто —зле програмиран сървър може да отговори 500 на заявка, която наистина е била лоша—, но като първа следа почти никога не подвежда. Когато таблото revisor оцвети ред в червено, защото една услуга е отговорила 503, то ще превежда точно този речник, а колоната му за състоянието се ражда оттук. Пълният официален списък живее в регистър на IANA, която е органът, който раздава тези числа.
Две предупреждения, които по-късно се плащат скъпо. 404 е правилен и пълен отговор. Сървърът е отговорил, с тяло (обикновено страница, която казва „не е намерено“) и всичко; просто отговорът е „нямам това“. От гледна точка на браузъра разговорът е минал добре. Това ще има значение в Урок 8, защото функцията fetch на JavaScript третира 404 като разговор, който е минал добре, а не като грешка: ти трябва да погледнеш състоянието сам. Второ предупреждение: семейството 2xx казва, че сървърът е обслужил заявката, не че резултатът е този, който ти очакваш. Един 200 с тяло, което казва „грешка“ (съществува и е лоша практика), си е 200.
Content-Type: как браузърът решава какво да прави с полученото
Върни се към заглавната част Content-Type: text/html; charset=utf-8. Казва две неща: типа на съдържанието (text/html, MIME тип, съставен от тип и подтип) и кодирането на знаците (utf-8, което позволява да се пишат „ñ“ и „á“). Браузърът решава какво да прави с един файл преди всичко по тази заглавна част, не по разширението на името. Ако сървърът каже text/html, го тълкува като страница; ако каже text/plain, го показва като текст, без да го тълкува, дори да се казва index.html; ако каже text/css, го третира като стилове; ако каже text/javascript, като програма; ако каже application/json, като данни. Има един нюанс: когато заглавната част липсва или е съмнителна, браузърът понякога „души“ първите байтове, за да познае типа (стандартът, който го регулира, го нарича MIME sniffing). За онова, което най-много те интересува в курса, не познава: JavaScript модул или стилов лист с грешен тип просто не се използват.
Типовете, които ще видиш в този курс, са малко и си струва да ги разпознаваш:
| MIME тип | Какво е | Урок |
|---|---|---|
text/html |
HTML документ | 2 |
text/css |
стилов лист | 3 |
text/javascript |
програма (RFC 9239 определи това име като официално) | 6 |
application/json |
данни в JSON | 8 |
image/png, image/svg+xml |
изображения | 11 |
Ще видиш, че тази заглавна част е главният герой на една от най-обърквящите повреди в Урок 1: когато сървърът отговори „не е намерено“ с HTML страница вместо с JavaScript модула, който очакваш, браузърът се оплаква, че е получил грешния тип. Вече ще знаеш защо.
Паметта на уеба: без състояние, с кеш
HTTP няма памет. Всяка заявка е независима: сървърът не знае, сам по себе си, че си същият човек, който преди две секунди поиска нещо друго. Казва се, че е протокол без състояние. Когато един сайт има нужда да те запомни (например че си влязъл в профила си), го прави с трик върху HTTP: връчва ти малко парче информация, наречено бисквитка (cookie) (чрез заглавната част Set-Cookie, която MDN обяснява с примери), и браузърът ти я връща в следващите заявки към този сайт, докато е валидна (самата бисквитка носи правила, които ограничават към кой домейн и кои пътища я придружава). Нашият revisor не се нуждае от нея; споменаваме я, за да знаеш, че съществува и че „паметта“ не е в протокола, а е поставена върху него.
Онова, което наистина ще засяга всекидневната ти работа, е кешът. За да не тегли едно и също хиляди пъти, браузърът пази копия на отговорите и, според заглавните части, решава дали може да ги използва повторно, без да пита, или трябва да попита „променено ли е от последния път?“. Ако копието още е валидно, браузърът го ползва, без да пита нищо. Ако вече е изтекло, пита и ако файлът не е променен, сървърът отговаря 304 Not Modified без тяло и браузърът отново ползва своето (RFC 9111). Чудесна идея за посетителя и капан за програмиста: редактираш файл, презареждаш и виждаш версията отпреди десет минути. В раздела „Мрежа“ има квадратче, с което да го изключиш, докато работиш, и ще ти трябва от Урок 1.
0.3 Кой какво прави: браузърът, сървърът и твоят код
Сървърът: програма, която слуша
Сървърът не е специална машина с различен вид: той е програма, която слуша на една врата, чака заявки и знае как да им отговаря. Един уеб сървър може да прави два вида неща. В простия случай чете файл от диска и го изпраща такъв, какъвто е: искаш /data/services.json и той ти праща файла, който е на този път. Това се нарича сервиране на статично съдържание и е онова, което ще прави учебният сървър на твоя компютър, а също и сайтът, който ще публикуваш в Урок 11. В сложния случай изпълнява програма за всяка заявка —пита база данни, изчислява, сглобява отговора— и тогава съдържанието е динамично. Този курс се нуждае само от първото: таблото чете JSON файл на самия проект, така че няма какво да се програмира от страната на сървъра.
Запомни това: сървърът не знае нищо за твоя екран. Не знае колко е широк, дали има екранен четец, дали кодът ти е пропаднал. Само отговаря на заявки. И, в статичния случай, той също не изпълнява твоя JavaScript: връчва го.
Браузърът: прави много повече от това да показва
Браузърът е клиентът и техническото му име, потребителски агент (user agent), описва добре ролята му: действа от името на човека. Работата му, от момента, в който напишеш URL, до момента, в който видиш нещо, е тази верига (описана подробно в документацията на MDN и в класическата статия на web.dev за това как работят браузърите):
- Разрешава името и отваря връзката (онова, което видяхме в 0.1 и 0.2).
- Иска основния документ с
GET. Получава HTML. - Чете го и изгражда DOM, Document Object Model: представяне на страницата във вид на дърво, с един възел за всеки таг. Това е дървото, върху което ще работи твоят JavaScript (Урок 7).
- Открива какво друго му трябва. Докато чете HTML, се натъква на препратки към други файлове: стилов лист с
<link>, програма със<script>, изображение с<img>. Всяка е още една заявка, със свой собствен път и свой собственGET, които браузърът често пуска успоредно. - Прилага стиловете и изчислява геометрията: какво колко е голямо и къде отива.
- Изпълнява JavaScript, който може да променя дървото и да прави още повече заявки (когато в Урок 8 използваш
fetch, ще е точно това). - Рисува резултата на екрана.
Начертано като времева линия за минималната страница по-горе, плюс един стилов лист и една програма, изглежда така:
tiempo →
navegador: GET /index.html ─────────┐
servidor: └─ 200, HTML ──┐
navegador: ├─ GET /css/main.css ─┐
├─ GET /js/main.js ──┤
servidor: │ └─ 200, 200
navegador: construye, aplica, ejecuta y pinta
Практическото следствие е основно: една страница не е едно изтегляне, а няколко, и всяко може да мине добре или зле поотделно. Страница, която „се вижда без стилове“, не е мистерия на CSS; най-често е, че заявката за стиловия лист е върнала 404, защото пътят е написан погрешно. И ще го видиш със собствените си очи след малко.
Трите езика и чий е всеки
Сега се разбира защо уебът се изгражда с три езика, а не с един. HTML описва съдържанието и неговото значение: това е заглавие, това е таблица, това е бутон. CSS описва представянето: цветове, размери, как се подрежда. JavaScript описва поведението: какво се случва, когато някой щракне, как се искат и изчертават данни. Нормално е всеки да пътува като отделен файл, със свой MIME тип, и браузърът да ги събира (може да се пише CSS и JavaScript и вътре в самия HTML, с таговете <style> и <script>, и тогава те пътуват вътре в него). Редът, в който ще ги учиш —HTML, CSS, JavaScript— е и редът на зависимост: една страница трябва да има смисъл само с HTML, да се подобри с CSS и да стане интерактивна с JavaScript. Това е идеята за прогресивно подобряване, която книгата Resilient Web Design на Джеръми Кийт развива по-спокойно и която си струва като допълнително четиво.
Онова, което стига до браузъра, е на този, който го получава
Тук има една идея, която този курс ще повтаря настоятелно, така че по-добре да я разбереш още от самия ѝ произход. HTML, CSS и JavaScript пътуват до машината на човека, който посещава страницата, и там се изпълняват. Всичко, което предадеш на браузъра, може да бъде прочетено и променено от този, който го получава. Всеки може да отвори „Преглед на кода“, да прочете твоя JavaScript, да промени стойности от инструментите на браузъра или дори изобщо да не ползва браузър и да изпрати на ръка заявката, която поиска. От това следват две правила, които ще са навици през целия курс:
- Нищо, което трябва да остане тайно, не може да живее в кода, който пращаш на браузъра. Парола, служебен ключ, лични данни: ако е там, вече е публикувано.
- На нищо, което се връща от браузъра или идва отвън, не бива да се вярва. Валидацията, направена в браузъра, подобрява преживяването, но не защитава нищо: който иска да я заобиколи, я заобикаля. А текст, който идва отвън, като името на една услуга, винаги трябва да се третира като текст и никога като код. В Урок 7 ще видиш какво става, ако не (нарича се XSS) и защо свойството
textContentе защитата.
Сигурността на уеба не е тема, която се добавя накрая: идва от това разпределение на ролите. Който знае къде работи кодът му, знае и какво може да гарантира.
Произходът: кой какво може да чете от кого
Съществува правило, което браузърът прилага заради твоята сигурност и което повече от веднъж ще ти се стори досада: политиката за същия произход. Произходът е комбинацията от схема, хост и порт. Една страница може свободно да чете само отговори от същия произход като нейния; четенето на отговори от друг произход изисква изрично разрешение от другия сървър (това разрешение се нарича CORS и го изучаваме в Урок 9). Сравни:
| Адрес | Същият произход като https://example.com/? |
Защо |
|---|---|---|
https://example.com/otra/ruta |
да | схемата, хостът и портът съвпадат; пътят не се брои |
http://example.com/ |
не | сменя се схемата |
https://www.example.com/ |
не | сменя се хостът |
https://example.com:8443/ |
не | сменя се портът |
Има един особен случай, който ще те ухапе в Урок 1: страница, отворена директно от файл (file:///home/ana/revisor/index.html), няма използваем произход; браузърите ѝ придават „непрозрачен“ и затова ѝ забраняват неща, които на страница, сервирана по HTTP, са позволени. Решението е онова, което вече предусещаш: да сервираш проекта си с локален сървър, макар и минимален.
Разделът „Мрежа“: да видиш разговора
Всичко казано дотук може да се види, не само да се вярва. Инструментите на браузъра (на английски DevTools) се отварят с клавиша F12 или с Ctrl+Shift+I и носят няколко раздела. Този, който ни интересува днес, е Мрежа (Network), който записва всяка заявка, която прави страницата. Във Firefox, браузъра, който идва с Linux Mint, се отваря и с Ctrl+Shift+E; в браузърите на базата на Chromium се казва същото и е в същото меню.
Три правила за употреба, които спестяват първите спънки:
- Отвори го, преди да заредиш страницата. Разделът записва, докато е отворен; онова, което е станало преди, не се появява. Отвори инструментите, влез в раздела „Мрежа“ и тогава презареди с
F5. - Гледай колоните. Всяка заявка е ред. Които имат значение: Състояние (Status; кодът:
200,404…), Метод (GET), Домейн и Файл (към кого и какво е поискано), Тип (MIME типът,Content-Type, който видяхме), Пренесено и Размер (колко байта са пътували и колко тежи вече разархивирано) и Време (колко е отнело). - Щракни върху ред. Отваря се панел със заглавните части на заявката и на отговора, точно както ги видяхме написани по-горе, плюс тялото и разбивката на времената.
С това в ръка примерната страница https://example.com е добра първа лаборатория: мъничка е и съдържанието ѝ е замислено да бъде безобидно. Когато я писах, на 7 октомври 2026 г., тази страница искаше две неща: HTML документа и една малка програма (/s.js), която самата тя свързва; ако днес видиш нещо различно, задачата е същата. В реда на документа ще видиш GET, домейна, 200, тип html и, в панела със заглавни части, същите редове, които показа curl.
И тук се затваря нишката с проекта. Колоната Време е идеята, която revisor ще показва за всяка услуга: колко е минало от излизането на заявката до пристигането на отговора. А колоната Състояние е онази, която ще реши дали един ред на таблото казва „arriba“ (работи) или „caído“ (не работи). revisor е, в малък мащаб, този раздел „Мрежа“, превърнат в продукт: списък със заявки към услуги, всяка със своето състояние и време за отговор. Затова започваме оттук.
Грешката, която ще видиш
Този урок се учи, като чупиш неща нарочно, защото повредите в уеба имат три ясни форми и разпознаването им е основното умение. Предизвикай ги ти; никоя не крие риск.
Първа форма: името не се разрешава (повреда на DNS). Поискай адрес, който не съществува. Домейнът .invalid е резервиран точно за това (RFC 2606): никога няма да съществува.
$ curl -sS https://nombre-que-no-existe.invalid
curl: (6) Could not resolve host: nombre-que-no-existe.invalid
Браузърът показва нещо равностойно: „Сървърът не може да бъде намерен“ или „Hmm. We're having trouble finding that site“. Числото в скобите е изходният код на curl (6 означава „името не можа да бъде разрешено“). Какво означава: повредата е станала преди да се свържеш с когото и да било, при DNS запитването. Името е написано погрешно, или не съществува, или резолвърът ти не отговаря. Как се оправя: провери правописа на името, а ако името е правилно, провери интернет връзката си.
Втора форма: никой не обслужва на тази врата (повреда на връзката). Поискай нещо от собствения си компютър, където няма никакъв сървър.
$ curl -sS http://localhost:8000
curl: (7) Failed to connect to localhost port 8000 after 0 ms: Couldn't connect to server
Браузърът казва „Не може да се свърже“ или ERR_CONNECTION_REFUSED. Какво означава: името беше разрешено и машината съществува, но никоя програма не слуша на тази врата. Как се оправя: сървърът е изключен или слуша на друга врата; пусни го или поправи номера. Забележи разликата с предишния случай: тук имаше адрес, до който да се стигне.
Трета форма: имаше отговор и е лоша новина (повреда на HTTP). Поискай ресурс, който не съществува, от сървър, който съществува.
$ curl -sI https://example.com/nada.html | head -1
HTTP/2 404
Какво означава: целият път проработи —име, връзка, шифроване, заявка—, сървърът разбра и отговори, че няма този ресурс. Как се оправя: пътят е написан погрешно или файлът не е там, където казваш. В браузъра отвори https://example.com/nada.html и погледни раздела „Мрежа“: ще видиш ред със състояние 404 и, въпреки това, страница, начертана на екрана. Това е доказателството за казаното по-рано: 404 е отговор.
С тези три форми можеш да подредиш всеки симптом в карта от три слоя, която е най-полезният инструмент за дебъгване на този урок:
| До къде стигна разговорът? | Слой | Типичен симптом | Какво да провериш |
|---|---|---|---|
| Името не беше разрешено | Име (DNS) | Could not resolve host, „сървърът не е намерен“ |
правопис, връзка, резолвър |
| Беше разрешено, но никой не отговаря на вратата | Връзка (TCP/TLS) | Failed to connect, ERR_CONNECTION_REFUSED, невалидно или изтекло TLS удостоверение |
дали сървърът е включен, врата, валидност на TLS удостоверението |
| Имаше отговор с код 4xx или 5xx | Отговор (HTTP) | 404, 500, 503 |
път, права, състояние на сървъра |
Преди да промениш ред код, попитай се на кой ред си.
Какво се прави погрешно
Да казваш „сървърът е паднал“ при всяка повреда. 404, изтекъл времеви лимит, изтекло TLS удостоверение и погрешно написано име са четири различни проблема с четири различни поправки. Цена: часове търсене на грешното място. Поправка: погледни първо кода на състоянието и слоя (таблицата по-горе).
Да пишеш файловете с главни букви, интервали или ударения. Logo Principal.PNG работи на твоя компютър и се чупи на Linux сървъра, където ще публикуваш. Цена: 404, който се появява само при публикуване, най-лошият момент. Поправка: малки букви, тирета вместо интервали, без ударения: logo-principal.png.
Да вярваш на катинарчето като на печат за качество. HTTPS казва, че каналът е поверителен и че името е автентично, не че сайтът е честен или свободен от грешки. Цена: да вярваш на един сайт заради сигнал, който не е обещавал това. Поправка: HTTPS е необходим, никога достатъчен.
Да криеш нещо в кода на браузъра. „Замъглен“ ключ в JavaScript си остава на машината на всеки, който отвори страницата. Цена: теч, който не може да бъде оттеглен. Поправка: тайното живее на сървъра, никога в онова, което се изпраща на браузъра.
Да дебъгваш с презареждане. Да презаредиш десет пъти „да видим дали сега ще стане“, без да отвориш раздела „Мрежа“, е налучкване. Цена: време и изкушението да промениш код, който не е бил лош. Поправка: отвори раздела „Мрежа“, презареди и прочети коя заявка е пропаднала и с какво състояние.
Да не знаеш коя версия на файла гледаш. Кешът връща старото копие, докато ти редактираш новото. Цена: да дебъгваш проблем, който вече си поправил. Поправка: по време на разработка, с отворени инструменти, отметни „Деактивиране на кеша“ в раздела „Мрежа“.
Упражнения
Първите две ползват молив и терминала; третото — браузъра; четвъртото събира всичко.
Упражнение 1 — Раздели URL адреса
Запиши на лист частите на тези три URL адреса (схема, хост, порт —дори да не е написан—, път, запитване, фрагмент) и отговори: кои два споделят произход?
https://example.com/data/services.json?status=down#row-3http://localhost:8000/index.htmlhttps://example.com:443/css/main.css
Упражнение 2 — Прочети един разговор с curl
С инсталиран curl (ако не, sudo apt install curl) изпълни curl -I https://example.com и curl -v https://example.com -o /dev/null. Отговори писмено: (а) коя версия на HTTP и кой код на състоянието върна? (б) какъв е Content-Type? (в) коя версия на TLS беше договорена? (г) колко IP адреса разреши името?
Упражнение 3 — Преброй заявките на една страница
Отвори Firefox, отвори инструментите (F12), иди в раздела Мрежа и след това напиши https://example.com. (а) Колко реда се появяват? (б) За първия: метод, състояние, тип, размер. (в) Отметни „Деактивиране на кеша“, презареди с F5 и сравни колоната „Пренесено“ с предишното зареждане. (г) Сега посети https://example.com/nada.html: какво състояние има редът и защо се вижда страница въпреки това?
Упражнение 4 — Класифицирай симптомите
За всеки симптом кажи на кой слой е станал (име, връзка или отговор) и какво би проверил първо: (а) curl: (7) Failed to connect to localhost port 8000; (б) страницата се появява, но без никакви стилове, а в раздела „Мрежа“ стиловият лист има състояние 404; (в) curl: (6) Could not resolve host; (г) браузърът показва страница „502 Bad Gateway“; (д) отваряш таблото от файл и не се зарежда JavaScript модул (подсказка: помисли за произхода).
Решения
Упражнение 1 — Раздели URL адреса
- Схема
https; хостexample.com; порт 443 (който браузърът подразбира заhttps); път/data/services.json; запитване?status=down; фрагмент#row-3. - Схема
http; хостlocalhost; порт8000; път/index.html; без запитване и фрагмент. - Схема
https; хостexample.com; порт443(написан, макар да е този, който и без това се подразбираше); път/css/main.css; без запитване и фрагмент.
Първият и третият споделят произход: същата схема (https), същият хост (example.com) и същият порт (443 и в двата, написан или не). Това, че имат различни пътища, не се брои. Вторият сменя схемата, хоста и порта.
Упражнение 2 — Прочети един разговор с curl
Стойностите в този пример са тези, които получих на 7 октомври 2026 г.; твоите може да се различават в онова, което е променливо (дати, адреси), не във формата.
(а) HTTP/2 200: версия 2 на протокола, код 200 (успех). (б) text/html; charset=utf-8. (в) TLSv1.3, в реда SSL connection using TLSv1.3 …. (г) Два: 104.20.23.154 и 172.66.147.243 (редът IPv4: от изхода). Ако в твоя изход протоколът е HTTP/1.1, твоят curl е договорил предишната версия и това също е правилно: значението не се променя.
Упражнение 3 — Преброй заявките на една страница
(а) Две, когато пишех това (документът и програмата /s.js); ако днес има друго число, запиши това, което виждаш. (б) GET, 200, тип html, размер от няколкостотин байта. (в) При изключен кеш колоната „Пренесено“ показва реалния изтеглен размер при всяко презареждане; при включен кеш може да се появи „от кеша“ или по-малка или нулева стойност, защото браузърът е използвал повторно своето копие. (г) Състояние 404: сървърът е отговорил, че няма този ресурс, но отговорът му съдържа страница, която казва точно това, и браузърът я изчертава. Разговорът е минал добре; ресурсът не съществува.
Упражнение 4 — Класифицирай симптомите
(а) Връзка. Името беше разрешено и машината съществува, но никой не слуша на врата 8000: провери дали сървърът е включен и дали е точно тази врата. (б) Отговор, във второстепенна заявка: основният документ е минал добре, а стиловият лист е върнал 404; провери пътя на <link> и главните букви в името на файла. (в) Име. Не е имало дори връзка: провери правописа на хоста и връзката си. (г) Отговор: код 5xx. Отговорил е междинен сървър (шлюз или прокси), който е предал заявката ти на истинския сървър и е получил от него невалиден отговор (RFC 9110 §15.6.3). Ако истинският сървър просто не беше отговорил навреме, кодът щеше да е друг: 504 Gateway Timeout. Не е нещо, което поправяш в страницата си: съобщава се на този, който администрира услугата. (д) Произход: браузърът не позволява на страница от file:// да зарежда модули, защото произходът ѝ е непрозрачен; решението, което ще видиш в Урок 1, е да сервираш папката по HTTP.
Как разбирам, че съм успял
Този урок е за разбиране и това също може да се измери. Готов си за следващия, когато:
curl -I https://example.comти отпечатва на първия редHTTP/2 200(илиHTTP/1.1 200 OK) и знаеш да обясниш всяка дума на този ред.- Можеш да вземеш произволен URL и да посочиш неговата схема, хост, порт, път, запитване и фрагмент без помощ, и знаеш кой от тях не пътува към сървъра.
- Отворил си раздела „Мрежа“ преди да заредиш страница, намерил си основната заявка и си прочел нейното състояние, тип и време.
- Пред трите грешки от раздела „Грешката, която ще видиш“ казваш правилния слой, без да поглеждаш таблицата.
- Можеш със свои думи, в две изречения, да обясниш защо „зареждането на страница“ са няколко заявки и защо онова, което предаваш на браузъра, не може да бъде тайно.
Ако някое от петте не ти се получава, върни се към съответния подраздел; те са основите на единайсетте урока, които следват. И запиши в дневника онова, което ти е струвало усилие: това е частта от курса, която чете само ти.
За допълнително четене
- Как работи уебът, от MDN — същата история, разказана от тези, които поддържат документацията на платформата, с повече изображения.
- Преглед на HTTP, от MDN — следващата стъпка, ако искаш да видиш HTTP по-задълбочено, включително заглавните части, които тук само назовахме.
- RFC 9110 — HTTP Semantics — официалното определение на методите, кодовете и заглавните части; това е справочник: в него се правят справки, не се чете на един дъх.
- Resilient Web Design, от Джеръми Кийт — историята и философията на уеба и произходът на идеята да се строи на слоеве.
Предпочитате имейл? Пишете ни на hola@habil.mx