Pojmy
- HTTP Klient / User-Agent -- prohlížeč
- HTTP Server -- zpracovává požadavky - parsuje danou URL a podle konfigurace vygeneruje odpověď - např. vrátí statický soubor nebo spustí nějaký kód
- URL -- např. https://www.fi.muni.cz/~kas/pv090/#http -- obsahuje popis, jaký resource chceme
- Resource -- obecné jméno pro odpověď, kterou HTTP server generuje -- např. HTML stránka, CSS soubor, JS soubor
Popis protokolu
Charakteristika
HTTP byl navržen pro přenos dokumentů (např. HTML stránek) po internetu. Od té doby se vyvinul, a jelikož většina middleboxů HTTP provoz propouští, používá se dnes na spoustu věcí (př. REST API nebo frontend pro konfiguraci všeho možného).
- Klient-server architektura -- klient se ptá serveru (HTTP request) a server mu odpoví (HTTP response).
-
Bezestavový -- server neudržuje žádné informace o uživateli mezi
dvěma požadavky.
Ale: cookies (server řekne klientovi, ať mu posílá tento kousek dat s každým dalším requestem)
- Textově založený -- ve zprávách (požadavky a odpovědi) se používá plain ASCII text, takže je lidsky čitelný.
Connection Management
Původně (HTTP 1.0): 1 HTTP request = 1 nové TCP spojení
Problém 1: TCP handshake něco trvá. TCP používá model AIMD (nebo podobný), zkrátka na začátku je rychlost přenosu pomalá a postupně roste.
Řešení v1 (HTTP 1.1):
budeme používat znovu TCP spojení po předchozím requestu. Hlavička
Connection: Keep-Alive
Problém 2: Po loadu stránky vím, že chci poslat 20 HTTP dotazů (CSS, JS, IMGs). Nechci otevírat 20 studených TCP spojení, chci použít to jedno teplé. Ale zároveň to chci paralelně.
Řešení 2: sypu všechny dotazy najednou do jednoho TCP spojení, a pak čekám na odpovědi. Velmi těžké na implementaci, nepoužívá se defaultně.
Problém 3 (head-of-line blocking): Server odpovídá postupně. První odpověď blokuje zbytek.
Revize protokolu HTTP
-
HTTP 2
Binární protokol, nemění význam HTTP 1.1, pouze způsob přenosu. Server může odpovědět více odpověďmi, než na co se klient ptal (např. potřebné CSS a JS soubory). Taky řeší třetí problém použitím pravého multiplexingu.
Více informací viz https://developer.mozilla.org/en-US/docs/Glossary/HTTP_2
-
HTTP 3
Používá QUIC (TLS + UDP) místo TCP. Toto nějak obchází head-of-line blocking na transportní (TCP/UDP) vrstvě sítě; detaily jsem nezjišťoval.
Přehled Komunikace
- Metody, cesty, verze HTTP na prvním řádku (GET /index.html HTTP/1.1)
- Seznamu hlaviček -- hlavička je key-value pár (Accept-Language: cs-CZ).
- Prázdného řádku pro oddělení -- používá se CRLF!
- Těla požadavku -- např. data z formuláře, nebo data pro vytvoření. Typické pro metody POST, PUT (pro REST API často v kombinaci s hlavičkou Content-Type: application/json).
- Verze HTTP, stavový kód a popis na prvním řádku (HTTP/1.1 200 OK).
- Seznamu hlaviček -- hlavička je key-value pár (Content-Type: text/html).
- Prázdného řádku pro oddělení
- Těla odpovědi -- např. HTML stránka, nebo data v JSONu. Typické pro metody GET. I při metodě POST se může vrátit např. nově založený záznam.
curl https://www.fi.muni.cz/~kas/pv090/referaty/2023-podzim/dns.html
GET /~kas/pv090/referaty/2023-podzim/dns.html HTTP/1.1 Host: www.fi.muni.cz User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: cs,sk;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br
HTTP/1.1 200 OK Date: Fri, 20 Oct 2023 11:48:41 GMT Server: Apache Last-Modified: Mon, 16 Oct 2023 08:14:24 GMT Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=utf-8 Content-Language: en ...tělo odpovědi
Metody: GET, PUT, PATCH (idempotentní), POST, OPTIONS, DELETE
Stavové kódy odpovědí -- viz https://http.cat/
Range request
Pokud server podporuje (na nějakém konkrétním souboru/stránce) range request, klient je schopný si vyžádat jen část dat. To se používá například v přehrávačích nebo při stahování souborů (možnost pozastavit stahování).
Ukázka:
Klient: curl --head http://i.imgur.com/z4d4kWk.jpg Server: HTTP/1.1 200 OK … Accept-Ranges: bytes Content-Length: 146515 Klient: GET /z4d4kWk.jpg HTTP/1.1 Host: i.imgur.com Range: bytes=0-1023 Server: HTTP/1.1 206 Partial Content Content-Range: bytes 0-1023/146515 Content-Length: 1024
Content Negotiation
Obecně HTTP definuje pouze obecný pojem "content-negotiation" a dává k dispozici nějaké hlavičky a stavové kódy, které lze použít. Tyto hlavičky obecně začínají Accept* a používají se pro vyjádření preferencí klienta.
- Accept -- MIME typ, co klient chce
- Accept-Encoding -- jakým kompresním algoritmům klient rozumí
- Accept-Language -- v jaké jazykové mutaci chceme obsah
- Accept-Charset -- v jaké znakové sadě (př. UTF8) chceme obsah
Konkrétní mapování těchto hlaviček v chování HTTP Serveru je specifické pro každou implementaci. Server by měl poslat "Vary" hlavičku, ve které popíše, které mechanismy opravdu podporuje -- aby potom cache věděla, že změna v nepoužívané hlavičce nevadí.
Pro informace ohledně Apache: https://httpd.apache.org/docs/current/en/content-negotiation.html
CGI skripty a pooling
Motivace: Potřebuji generovat dynamický content (třeba závisející na aktuálním čase). Je blbost dávat do webového serveru nějaký samostatný programovací jazyk. Místo toho webový server může na základě URL poznat, jaký skript má pustit, udělat nový proces a jeho standardní výstup použít jako odpověď pro klienta.
- Flexibilní -- skript v jakémkoliv programovacím jazyce.
- Rozšiřitelné -- skript může generovat cokoliv, třeba PDF nebo JPG.
- Pomalé -- vždy spouštím nový proces.
Pomalost se řeší klasickou technikou "bazénkování" (pooling, neplést s polling) -- stejně jako v kontextu backendového programování existuje pool otevřených spojení do databáze (ze kterého si vždy jen nějaké "vypůjčíme"), abychom nemuseli vždy otevírat a zavírat (což je drahé), i zde se vynalezly přístupy, jak "bazénkování procesů" dosáhnout.
Jednou z klasických možností je PHP, kdy při startu webového serveru instanciujeme bazének procesů (PHP interpreterů), a poté si vždy jen daný interpret "vypůjčíme" pro zpracování daného requestu.
Velmi podobná věc: ASP.NET (a skoro jakýkoliv jiný backendový HTTP framework) má pool worker threads, kterým se vždy konkrétní požadavek přiřadí, ale po zpracování se zařadí zpátky do bazénku.
Cachování
- In-Browser cache (under Private caches) -- Browser podle hlaviček vrací starou verzi dokumentu
- Proxy cache (under Shared caches) -- specializovaný server jako proxy zastupující ten hlavní HTTP server
HTTP-EQUIV
Prohlížeč posílá GET požadavky na danou URL. Lze se podívat v Network tabu. Umí posílat POST (`form` tag). Ostatní metody -- využít fetch API. Parsuje odpověď a zobrazí ji uživateli nějak vyrenderované. Pokud je to HTML, tak tam můžou být "meta" tagy s HTTP-EQUIV direktivami. Tyto simulují HTTP hlavičky odpovědi. Prohlížeče jim rozumí. Jenže jim např. nerozumí cachovací proxy servery (museli by parsovat HTML). Pokud můžete použít HTTP hlavičky, použijte ty.
Use-case, kdy použít HTTP-EQUIV:- Nemůžete konfigurovat HTTP server (light hosting)
- Až na klientovi z JS potřebujete zpětně přidat hlavičku
Caching Best Practices
Důležité! Nasadíte novou verzi Reactí aplikaci, ale uživatelé si stěžují, že styly jsou rozbité. Build systém doporučí, abychom nastavili hlavičky "Cache-Control: max-age=31536000" pro věci ve složce static, "Cache-Control: no-cache" pro ostatní, my jsme to ale neudělali, a tak se cachují i věci, které se cachovat nemají.
Když konfigurujeme webserver, je důležité se zamyslet, jakou strategii zvolíme pro cachování. Například aby prohlížeč nebo cache proxy věděli, jak dlouho si mají data uchovat. Aby se nestalo, že při nasazaní nové verze aplikace mám ještě staré zacachované CSS styly, ale už nový HTML dokument.
- Dlouhá životnost cache, nikdy neměním obsah souboru -- cache se prostě použije
- Měním obsah souboru, nulová životnost cache -- nechat cache konzultovat server, jestli je obsah OK
- Z HTTP serveru: Cache-Control, Expires, ETag, Last-Modified
- Z klienta: If-None-Match, If-Modified-Since
Autentizace
Jiné často používané možnosti jsou např. OAuth2 (OpenId Connect), kdy se klient prokazuje nějakému serveru třetí strany (Google / Microsoft / ...), který mu pak vydá token, a odkáže ho zpět na resource server, který token ověří. Kromě OpenId Connect se využívá např. SAML (security assertion markup language) založený na XML. Otázka: který přístup k implementaci SSO používá MUNI?
Přímo v HTTP protokolu
Různé schémata: Basic, Digest, Bearer, ...
Stejná struktura: Klient se zeptá, server řekne
401 Unauthorized
, klient zadá potřebné údaje a pošle
request znovu s Authorization
header
- Basic Authentication: base64 kódování
- Digest: používá hash
- Bearer: jiné RFC, stejný princip
Následující sekce už se typicky neautentizují přímo vůči webovému serveru, ale vůči nějakému CGI skriptu obsluhujícím daný endpoint (resource). O to více jsou ale pro praktické použití důležité, protože dnes se většina webových aplikací dělá v nějakém frameworku, který poskytuje nějaké tyto možnosti.
Pomocí cookies
Klient se přihlásí, server mu pošle cookie, klient ji při každém dalším requestu pošle zpátky. Server si cookie někde uloží a přiřadí ji k nějakému uživateli.
Problém: Klient může cookie upravit, nebo si je vymyslet. Řešení: podepsat cookie, aby klient nemohl její obsah upravit.
HttpOnly a Secure flags pro zvýšení bezpečnosti cookies
JWT
Je to pouze specifikace formátu, v jakém přenášet nějaké tokeny.
Server vygeneruje JSON token se sadou "claims" o uživateli a podepíše to (symetricky - (H)MAC - nebo asymetricky - digital signature)
Do claims můžu zahrnout i informace pro frontend -- jaké ID a role uživatel má (podmíněný rendering u client-side aplikací)
Klient může posílat v custom hlavičkách, nebo třeba jako Authorization: Bearer token.
Dostupný software
- Apache
- Kestrel (ASP.NET Core)
- Node.js (module http)
- Nginx
- IIS (Microsoft)
Konfigurace Apache
Základní konfigurace
-
hlavní soubor
/etc/httpd/conf/httpd.conf
(fedora, jinde zde) - soubory
.htaccess
přímo v adresářích s obsahem - "Direktiva": Tvrzení o konfiguraci, co má platit. Umístit podle chtěného scope.
-
DocumentRoot
direktiva - místo ve filesystému, kam bude apache mapovat URLs.For example, if DocumentRoot is set to /var/www/html and a request is made for http://www.example.com/work/, the file /var/www/html/work/index.html will be served to the client. https://httpd.apache.org/docs/2.4/getting-started.html
-
VirtualHost
direktiva umožňuje více doménových jmen namapovat na tento jeden fyzický stroj
Direktivy a jejich use-cases
VirtualHost direktiva
Použití: Chci, aby na jednom fyzickém serveru běželo více oddělených webů.
Například: Dotaz
http://one.example.com/test.html
mapovat jinam než
http://two.example.com/test.html
<VirtualHost *:80> ServerName one.example.com DocumentRoot /var/www/one </VirtualHost> <VirtualHost *:80> ServerName two.example.com DocumentRoot /var/www/two </VirtualHost>
Konfigurace CGI skriptů
-
Použití
LoadModule cgid_module modules/mod_cgid.so
v httpd.conf -
Povolení CGI skriptů v daném adresáři pomocí
<Directory "/var/www/cgi"> Options +ExecCGI </Directory>
-
Řeknu Apachovi, které všechny soubory má považovat za CGI
skripty (podle přípony)
AddHandler cgi-script .cgi .py
Multiviews, odpověď podle preferencí klienta
Problém: Podle hlavičky Accept-Language
chci vrátit
jiné soubory v dané jazykové mutaci
- Buď manuálně specifikovat type map
- Nebo nechat server automaticky "generovat" type map
Autentizace
- Konfirurace
Directory
, který má být chráněný
AuthType Basic AuthName "Restricted Files" # (Following line optional) AuthBasicProvider file AuthUserFile "/usr/local/apache/passwd/passwords" Require user rbowen
SSL (HTTPS)
- jaký port (443), doména...
- kde se nachází certifikát, kterým se webserver prokazuje
- kde se nachází klíč, který webserver má používat
Redirects
-
RewriteRule
-- podporuje regulární výrazy, server odpovídá rovnou 200 OK jakoby k redirectu nedošlo -
Redirect
-- jednodušší, server klientovi řekne "tady nic není, ale hledej tady na té URL"
Literatura
- Viz odkazy v textu