Kapittel 2 · Del 1

Web og HTTP

Fra grunnleggende prinsipper for nettverksapplikasjoner og klient–server-paradigmet, via HTTP-protokollens innside, til cookies, caching og HTTP/2.

01 · Hvor apper bor

Programmer i hver sin ende av nettet

Før vi snakker om HTTP eller BitTorrent, må vi forstå én enkel ting: hvor kjører egentlig en nettverksapp?

En nettleser kjører på din maskin. En webserver kjører på en annen. Mellom dem ligger Internett — millioner av rutere og lenker som flytter pakker rundt. Den helt sentrale ideen i applikasjonslaget er denne: du skriver kode bare for endesystemene. Ruterne i kjernen kjører ingen brukerapplikasjoner; de bare videresender pakker. Dette er grunnen til at en student kan finne på en helt ny nettjeneste i kveld og rulle den ut over hele kloden i morgen, uten å måtte oppdatere noe som helst i nettverkets infrastruktur.

Å skrive en nettverksapp betyr altså å skrive to (eller flere) programmer som kjører på forskjellige maskiner og som utveksler meldinger over nettet. Et webserverprogram snakker med et nettleserprogram. To Skype-klienter snakker med hverandre. En BitTorrent-klient snakker med dusinvis av andre klienter samtidig. Mønsteret er likt overalt: prosesser på endesystemer som sender meldinger gjennom nettverket.

Prosesser, sockets og adresser

Inne i operativsystemet er en kjørende app en prosess. To prosesser på samme maskin snakker sammen via mekanismer operativsystemet tilbyr (delt minne, pipes, etc.). To prosesser på forskjellige maskiner snakker sammen ved å sende meldinger gjennom nettet — og porten ut av prosessen kalles en socket.

Bildet du bør ha i hodet: socket-en er en dør. Avsenderprosessen dytter en melding ut gjennom døra og stoler på at transport-infrastrukturen på andre siden leverer den til en annen socket hos mottaker-prosessen. Det er to sockets involvert i hver kommunikasjon — én i hver ende.

VERT A prosess (nettleser) socket transport (TCP/UDP) network · link · physical Internett VERT B prosess (webserver) socket transport (TCP/UDP) network · link · physical styres av app-utvikler resten styres av OS
To prosesser kommuniserer gjennom hver sin socket. Alt over socket-en er apputviklerens domene; alt under er operativsystemets.

For at meldingen skal komme frem, må mottakerprosessen ha en identifikator. IP-adressen til verten er ikke nok — én maskin kjører gjerne titalls prosesser samtidig. Identifikatoren består derfor av to deler: IP-adresse (hvilken maskin) og portnummer (hvilken prosess på den maskinen). En HTTP-server lytter typisk på port 80, en mailserver på port 25.

Tenk på IP som gateadressen til en bygård, og portnummeret som leilighetsnummeret. Begge må stå på konvolutten for at brevet skal komme frem til riktig person.

Hva en applikasjonsprotokoll faktisk er

Når to programmer skal snakke sammen, må de bli enige om reglene. En applikasjonsprotokoll definerer fire ting:

Meldingstyper — for eksempel forespørsel og svar. Meldingssyntaks — hvilke felt finnes, og hvordan er de avgrenset? Meldingssemantikk — hva betyr verdiene i feltene? Regler — når og hvordan sendes meldinger, og hvordan svarer man?

Noen protokoller er åpne: definert i RFC-er, fritt tilgjengelig, slik at hvem som helst kan implementere dem. HTTP og SMTP er klassiske eksempler — det er nettopp fordi de er åpne at en hvilken som helst nettleser kan snakke med en hvilken som helst webserver. Andre protokoller er proprietære — Skype er et eksempel — og fungerer bare innenfor én leverandørs økosystem.

02 · To måter å organisere det på

Klient–server, eller alle mot alle

Når to maskiner skal samarbeide, hvem tar initiativet? Hvem er alltid på? Svaret deler hele applikasjonsverdenen i to.

Den klassiske modellen er klient–server. En tjenermaskin står alltid på, har en fast IP-adresse, og venter på at klienter skal koble seg til. Klientene — din nettleser, din e-postapp, din FTP-klient — kobler seg til når de har behov, sender en forespørsel, får svar, og kobler ned igjen. Klientene snakker aldri direkte med hverandre; all trafikk går via tjeneren. Eksempler: HTTP, IMAP, FTP. Modellen er enkel å resonnere om, og fordi tjeneren har én kjent adresse er det ingen tvil om hvem som skal kontaktes.

Den åpenbare ulempen er at tjeneren fort blir en flaskehals. Skal du distribuere en stor fil til en million klienter samtidig, må én maskin laste opp den filen en million ganger. Det skalerer dårlig — og det skal vi snart se på matematisk.

Den alternative modellen er peer-to-peer. Her finnes det ingen alltid-på-tjener. Vilkårlige endesystemer — peers — snakker direkte med hverandre. En peer ber om en tjeneste fra en annen peer, og leverer selv tjenester tilbake til andre. Den fascinerende egenskapen er selv-skalering: hver nye peer som kommer inn i systemet bringer ikke bare nye krav, men også ny kapasitet. Jo flere som vil ha filen, jo flere er det som kan dele filen videre.

Prisen man betaler er kompleksitet. Peers er kun til og fra koblet, IP-adressene deres skifter, og det finnes ingen sentral oversikt. Klassiske eksempler er BitTorrent (fildeling), KanKan (strømming) og Skype (VoIP).

Hybridvirkeligheten

I praksis er nesten alle moderne systemer hybrider. Skype bruker peer-to-peer for selve samtalen, men trenger sentraliserte tjenere for å finne ut hvor motparten er. BitTorrent bruker P2P for filoverføringen, men ofte en sentralisert «tracker» eller distribuert hashtabell for å oppdage andre peers. Den rene formen er sjeldne; det viktige er å forstå begge modellene fordi systemer låner trekk fra begge.

Sjekk forståelsen
Hvorfor skalerer P2P-fildistribusjon prinsipielt bedre enn klient–server?
Riktig — dette er nøkkelen til selv-skalering. I klient–server er total opplastningskapasitet konstant (= us). I P2P vokser den med antall peers, fordi hver peer både laster ned og laster opp.
03 · Hva trenger appen fra laget under?

Bestillingen til transportlaget

En app er kresen. Den vil ha noen ting fra transportlaget — men ikke alle apper vil ha det samme.

Når du designer en applikasjonsprotokoll, må du velge hvilken transporttjeneste du skal sitte på. Det finnes to hovedvarianter på Internett — TCP og UDP — og de gir deg veldig forskjellige garantier. For å velge fornuftig må du vite hva appen din faktisk trenger.

Det er fire dimensjoner som spiller inn. Datapålitelighet: tåler appen at noen pakker forsvinner? Filoverføring krever 100 % pålitelighet — én tapt byte og hele filen er korrupt. Lyd og video kan derimot tåle litt tap; en glipp her og der gir litt knatring, ikke katastrofe. Tidskrav: må data komme frem innen en viss tid? Internettelefoni og spill trenger lav forsinkelse for å føles brukbart. En filnedlasting bryr seg ikke om ett sekund hit eller dit. Gjennomstrømning: trenger appen en minimumshastighet? Streaming-video gjør det. E-post gjør det ikke. Sikkerhet: skal data krypteres, integritetssjekkes, autentiseres?

TCP vs. UDP, kort fortalt

TCP gir deg pålitelig overføring (alle byter kommer frem, i riktig rekkefølge), flytkontroll (avsender overstyrer ikke en treg mottaker) og overbelastningskontroll (avsender bremser når nettet er stappet). I tillegg er TCP forbindelsesorientert — det må settes opp en forbindelse mellom partene før data kan flyte. TCP gir ikke tidsgarantier eller minimum gjennomstrømning, og det gir heller ikke sikkerhet i seg selv.

UDP er det motsatte: ingen pålitelighet, ingen flytkontroll, ingen overbelastningskontroll, ingen forbindelsesoppsett. Du sender en pakke, den kommer kanskje frem, kanskje ikke. Hvorfor finnes UDP da? Fordi det er enkelt og raskt, og fordi noen apper foretrekker å ta hånd om pålitelighet selv — eller faktisk ikke vil ha den. Sanntids stemmesamtaler vil heller miste noen pakker enn å vente på retransmisjoner.

ApplikasjonDatatapGjennomstrømningTidssensitiv?Transport
Filoverføring / nedlastingIngen tapElastiskNeiTCP
E-postIngen tapElastiskNeiTCP
WebdokumenterIngen tapElastiskNeiTCP
Sanntid lyd/videoTolererer tap5 kbps – 5 MbpsJa, 10-talls msTCP eller UDP
Strømmet lyd/videoTolererer tap10 kbps – 5 MbpsJa, få sekunderTCP
Interaktive spillTolererer tapFå kbps og oppJa, 10-talls msUDP / TCP

Sikre TCP med TLS

Vanlige TCP- og UDP-sockets er ukrypterte. Sender du et passord gjennom en vanlig TCP-socket, vandrer det i klartekst over Internett — og hvem som helst på veien kan lese det. Løsningen heter Transport Layer Security (TLS), som ligger som et lag oppå TCP og gir kryptering, dataintegritet og endepunktsautentisering. TLS er implementert som biblioteker i applikasjonslaget — apper kaller TLS-funksjoner, som i sin tur snakker med TCP. Klartekst sendt inn i en TLS-socket kommer ut igjen i den andre enden, men over nettet har det vandret kryptert.

04 · Web og HTTP

Protokollen som lager en webside

Når du skriver en URL i nettleseren, hva skjer egentlig? Svaret er HTTP — Webens applikasjonsprotokoll, og kanskje den mest brukte protokollen i verden.

En webside er sjelden én ting. Den er en HTML-fil som refererer til andre objekter — bilder, stylesheets, skript, fonter, videoer — og hvert av disse objektene kan hente fra forskjellige tjenere på Internett. Når nettleseren skal vise en side, må den først hente HTML-en, deretter parse den for å finne ut hvilke andre objekter som trengs, og så hente alle disse også. Dette er en koreografert ballett av forespørsler og svar, og det er HTTP som dirigerer alt sammen.

HTTP står for HyperText Transfer Protocol. Den følger en klient–server-modell: nettleseren er klienten som sender forespørsler, webserveren er tjeneren som svarer. HTTP bruker TCP under panseret — fordi vi trenger pålitelig overføring for at HTML-en ikke skal ankomme korrupt. Klienten initierer en TCP-forbindelse til tjeneren på port 80 (eller 443 for HTTPS), tjeneren aksepterer, så utveksles HTTP-meldingene, og til slutt lukkes forbindelsen.

En kritisk egenskap: HTTP er tilstandsløst. Tjeneren husker ingenting om tidligere forespørsler fra samme klient. Hver forespørsel er en blank tavle. Det høres ut som en svakhet — og vi skal se at det skaper utfordringer som må løses med cookies — men det er også en enorm styrke. Tilstandsløse protokoller er enkle å implementere, enkle å skalere, og krasjer en tjener kan en ny ta over uten å trenge å rekonstruere noen «sesjonstilstand».

To smaker av HTTP-forbindelser

I tidlig HTTP-tid (HTTP/1.0) brukte man ikke-persistente forbindelser: én TCP-forbindelse per objekt. Du åpner forbindelsen, henter HTML-en, lukker. Åpner ny forbindelse, henter første bilde, lukker. Åpner ny forbindelse, henter andre bilde, lukker. For en side med ti bilder betyr det elleve TCP-oppsett. Hvert oppsett koster én RTT bare for å komme i gang, pluss OS-overhead.

HTTP/1.1 introduserte persistente forbindelser: åpne én TCP-forbindelse, send mange forespørsler over den, lukk når du er ferdig. Forskjellen kan virke liten, men den er stor — særlig over forbindelser med høy RTT.

Responstid: hvorfor RTT betyr alt

La oss regne. RTT (round-trip time) er tiden det tar for en liten pakke å reise fra klient til tjener og tilbake. For en ikke-persistent HTTP-forespørsel om ett objekt:

KLIENT TJENER åpner TCP RTT ber om fil RTT fil mottatt overføring av fil total = 2·RTT + overføringstid
Ikke-persistent HTTP: én RTT for TCP-oppsett, én RTT for forespørsel og start av svar, pluss tiden det tar å overføre selve filen.

Persistent HTTP fjerner den første RTT-en for hvert ekstra objekt — du betaler bare TCP-oppsettet én gang, så kan du sende den ene forespørselen etter den andre over samme forbindelse. Klienten kan til og med pipeline forespørslene: send forespørsel for objekt 2 før svaret på objekt 1 er kommet i havn. For en side med mange referanser kan dette halvere lastetiden.

Sjekk forståelsen
En webside inneholder en HTML-fil og 8 bilder, alle på samme tjener. Med ikke-persistent HTTP (ingen parallelle forbindelser) — hvor mange TCP-forbindelser må opprettes, og hva er minimum antall RTT-er for å laste hele siden?
Med ikke-persistent HTTP trenger hvert objekt sin egen TCP-forbindelse, altså 9 stykker (1 HTML + 8 bilder). Hver forbindelse koster 2 RTT-er (1 for TCP-oppsett + 1 for forespørsel/svar), totalt 18 RTT-er pluss selve overføringstiden. Med persistent HTTP ville du klart deg med 1 forbindelse og 10 RTT-er (1 for TCP-oppsett + 1 per objekt) — eller enda mindre med pipelining.
05 · HTTP-meldingen, brettet ut

Hva en forespørsel og et svar faktisk inneholder

HTTP-meldinger er bemerkelsesverdig lesbare. Du kan i praksis snakke med en webserver fra et tastatur via Telnet.

Det finnes to typer HTTP-meldinger: forespørsler (sendt fra klient til tjener) og svar (tjener til klient). Begge er rent ASCII — ingen binær obscurité — og består av tre deler: en startlinje, et antall hodelinjer, og en valgfri kropp.

Forespørsel
GET /index.html HTTP/1.1
Host: www.cs.umass.edu
User-Agent: Firefox/3.6
Accept: text/html
Accept-Language: en-us,en
Accept-Encoding: gzip,deflate
Connection: keep-alive

(tom linje markerer slutt)
Svar
HTTP/1.1 200 OK
Date: Sun, 26 Sep 2010 20:09:20 GMT
Server: Apache/2.0.52
Last-Modified: Tue, 30 Oct 2007
Content-Length: 2652
Content-Type: text/html

<html>...</html>

Forespørselens første linje — kalt request line — har formen METODE URL VERSJON. Hodelinjene følger som navn: verdi. En tom linje (CRLF) markerer slutten på hodet. Etter hodet kan det komme en kropp, for eksempel data fra et utfylt skjema.

Metodene

HTTP definerer flere metoder, og hver har sin tydelige rolle. GET ber om et objekt; eventuelle parametre legges i URL-en etter et spørsmålstegn. POST sender data til tjeneren — typisk fra et webskjema — i forespørselens kropp. HEAD ber om hodet til et objekt uten selve innholdet; nyttig for debugging og for cacher som vil sjekke om noe er endret. PUT laster opp en ny fil til en angitt URL og erstatter eventuelt eksisterende innhold der.

Statuskoder

Svarets første linje inneholder en statuskode på tre sifre. Du kjenner sikkert noen av dem allerede:

200 OKForespørselen lyktes; objektet følger med i svaret.
301 Moved PermanentlyObjektet er flyttet; ny adresse oppgis i Location-hodet.
400 Bad RequestTjeneren forstod ikke forespørselen.
404 Not FoundDet forespurte dokumentet finnes ikke på tjeneren.
505 HTTP Version Not SupportedTjeneren støtter ikke den HTTP-versjonen klienten brukte.

Prøv det selv

Du kan snakke direkte med en webserver fra terminalen. Skriv telnet gaia.cs.umass.edu 80, så har du en TCP-forbindelse på port 80. Skriv en GET-forespørsel manuelt (avslutt med to enter), og se rå HTTP-respons komme tilbake. Det er én av de hurtigste måtene å avmystifisere webens innerste virkemåte på.

06 · Tilstand i en tilstandsløs verden

Cookies, eller hvorfor nettleseren din vet hvem du er

HTTP er tilstandsløs — men handlekurven din i nettbutikken vet jo hva du har lagt i den. Hvordan henger det sammen?

Tilstandsløsheten i HTTP er en velsignelse for skalerbarhet og enkelhet, men en forbannelse hvis du faktisk vil bygge en applikasjon der brukeren skal kunne logge inn, fylle en handlekurv, eller fortsette der hen slapp i går. Hvordan kan en tjener kjenne igjen den samme brukeren mellom uavhengige forespørsler? Svaret er en av Webens viktigste oppfinnelser: cookies.

Idéen er enkel. Første gang en bruker besøker et nettsted, sender tjeneren en unik identifikator tilbake i et spesielt hodefelt — Set-Cookie: 1678. Nettleseren lagrer denne i en cookie-fil, knyttet til domenet. Neste gang brukeren ber om noe fra samme nettsted, legger nettleseren automatisk ved cookien i forespørselen — Cookie: 1678. Nå kan tjeneren slå opp identifikatoren i sin egen database og finne ut: «Aha, det er den brukeren som la to bøker i handlekurven for ti minutter siden.»

Det er fire bevegelige deler i systemet: et Set-Cookie-hode i et HTTP-svar, et Cookie-hode i påfølgende HTTP-forespørsler, en cookie-fil på brukerens maskin (administrert av nettleseren), og en database på tjenersiden der cookien knyttes til faktisk informasjon.

Cookies brukes til mye nyttig: autorisasjon (hvem er du innlogget som?), handlekurver, anbefalinger, sesjonstilstand for webmail. Men de er også en av hovedmekanismene bak sporing på nettet. Tredjeparts persistente cookies — cookies satt av domener som ikke er det du ser i adressefeltet — gjør at samme identifikator kan følge deg på tvers av mange forskjellige nettsteder, og bygge en svært detaljert profil av aktiviteten din.

Norske regler fra 2025

Stortinget har vedtatt en ny ekomlov som trådte i kraft 1. januar 2025. Den krever at samtykke til informasjonskapsler oppfyller GDPR-kravene: frivillig, spesifikt, informert, utvetydig, gitt gjennom en aktiv handling, dokumenterbart, og mulig å trekke tilbake like lett som det ble gitt. Dette er grunnen til at nesten alle nettsteder nå viser deg en cookie-banner ved første besøk.

HTTP er tilstandsløst, men cookies gir tilstand. Hvorfor valgte man ikke bare å gjøre HTTP tilstandsbevart fra starten, i stedet for å legge på cookies etterpå?

Tilstandsløshet er en bevisst designbeslutning som gir store fordeler: tjenere trenger ikke huske noe mellom forespørsler, noe som gjør det enkelt å skalere (legg til flere tjenere bak en lastbalanser — en hvilken som helst av dem kan svare på en hvilken som helst forespørsel). Hvis tjeneren krasjer, trenger den ikke å rekonstruere noen sesjonstilstand. Tilstandsløse protokoller er også enklere å implementere og enklere å feilsøke. Cookies lar deg legge til tilstand der du trenger det, uten å påtvinge det på hele protokollen. Det er et «opt-in»-system: bare de applikasjonene som faktisk trenger tilstand, betaler kostnaden.

07 · Web-cache og betinget GET

Ikke hent det du allerede har

Den raskeste forespørselen er den du aldri sender.

En web-cache — eller proxy-tjener — er en mellommann som husker tidligere svar. Brukerens nettleser konfigureres til å sende alle HTTP-forespørsler gjennom cachen istedenfor direkte til opprinnelses-tjeneren. Når en forespørsel kommer inn, sjekker cachen først om den allerede har objektet. Hvis ja, leverer den det umiddelbart. Hvis nei, henter den objektet fra opprinnelses-tjeneren, lagrer en kopi, og leverer det videre til klienten.

Cachen fungerer altså samtidig som tjener (overfor klienten) og som klient (overfor opprinnelses-tjeneren). Dette gir to viktige fordeler: kortere responstid for brukeren, fordi cachen typisk er nærmere enn opprinnelses-tjeneren, og mindre trafikk på institusjonens aksesslinje, fordi populært innhold bare må lastes ned én gang. For en universitets-ISP eller et kontornettverk kan dette spare enorme mengder båndbredde.

Problemet: hvordan vet cachen at den har en gyldig kopi?

Hva om innholdet på opprinnelses-tjeneren er endret siden cachen lagret kopien sin? Da serverer cachen utdatert innhold til brukeren — pinlig. Løsningen er betinget GET. Cachen sender en vanlig GET-forespørsel, men legger til et hodefelt: If-Modified-Since: <dato>.

Tjeneren sjekker om objektet er endret etter den datoen. Hvis ikke, svarer den med 304 Not Modified — en liten melding helt uten innhold. Cachen vet da at dens versjon fortsatt er gyldig og kan trygt levere den. Hvis objektet er endret, svarer tjeneren med 200 OK og det nye innholdet, akkurat som en vanlig GET. Dette er en elegant optimalisering: i det vanlige tilfellet — der innholdet ikke har endret seg — slipper man å overføre objektet i det hele tatt, bare 304-meldingen.

CACHE TJENER GET med If-Modified-Since: 30. okt 304 Not Modified (tomt svar) GET med If-Modified-Since: 30. okt 200 OK + nytt innhold uendret endret
Betinget GET. Hvis objektet er uendret, slipper man hele overføringen og får bare en bitteliten 304-melding tilbake.
Sjekk forståelsen
En web-cache har en lagret kopi av et objekt. Hva sender den til opprinnelses-tjeneren for å sjekke om kopien fortsatt er gyldig?
Betinget GET er en vanlig GET-forespørsel med et ekstra hodefelt — If-Modified-Since — som inneholder datoen cachen sist hentet objektet. Tjeneren svarer med 304 Not Modified (ingen kropp) hvis objektet er uendret, eller 200 OK med det nye innholdet hvis det er endret. Dette sparer båndbredde i det vanlige tilfellet der innholdet ikke har endret seg.
08 · HTTP/2 og hode-blokkeringen

Når én stor fil blokkerer alt det andre

HTTP/1.1 var bra, men det hadde ett irriterende problem som HTTP/2 ble laget for å fikse.

HTTP/1.1 lot deg pipelinje forespørsler over én TCP-forbindelse, men tjeneren svarte i samme rekkefølge som forespørslene kom (FCFS — first-come-first-served). Forestill deg en webside som inneholder én stor video og tre små bilder. Klienten ber om alle fire i rekkefølge: O₁ (videoen), O₂, O₃, O₄. Tjeneren begynner å sende O₁, og fordi den er stor, må de tre små bildene vente bak i køen. Dette kalles head-of-line-blokkering — det fremste objektet sperrer for alt det andre.

Resultatet er at brukeren ser en blank side mens videoen overføres, selv om de tre små bildene kunne vært overført på et øyeblikk. Nettleserens hovedtriks for å omgå dette i HTTP/1.1 var å åpne flere parallelle TCP-forbindelser til samme tjener, slik at de små objektene kunne hentes parallelt med det store. Det fungerer, men det er tungvint og svekker overbelastningskontrollen.

HTTP/2 (RFC 7540, 2015) løser dette ved å dele objekter opp i mindre frames og blande sammen rammene fra forskjellige objekter på samme forbindelse. Tjeneren kan også prioritere rammer ut fra hva klienten har bedt om — ikke nødvendigvis FCFS. Tre små objekter får da overført rammene sine mellom rammer fra det store objektet, og kommer raskt frem mens det store fortsatt overføres.

HTTP/2 introduserte også server push: tjeneren kan sende objekter til klienten før klienten har bedt om dem. Når tjeneren mottar en forespørsel om en HTML-side, vet den at klienten snart vil trenge CSS-filen og noen bilder — og kan proaktivt dytte disse ut i stedet for å vente på at klienten parser HTML-en og sender nye forespørsler. Dette eliminerer én ekstra RTT for hvert objekt som pushes.

HTTP/1.1 HTTP/2 O₁ (stor) O₂ O₃ O₄ O₂, O₃, O₄ må vente små objekter blandet inn — alle leveres raskt tid → LEVERINGSTIDSPUNKT HTTP/1.1: O₁ O₂ O₃ O₄ HTTP/2: O₂ O₃ O₄ O₁ (litt forsinket)
HTTP/1.1 leverer i bestilt rekkefølge — store objekter blokkerer små. HTTP/2 fletter rammer fra forskjellige objekter, slik at små objekter ikke må vente.

HTTP/2 har likevel et gjenværende problem: det ligger fortsatt på én TCP-forbindelse, og hvis en TCP-pakke går tapt, må alle objektene vente på retransmisjonen. HTTP/3 løser dette ved å bytte ut TCP med en ny transportprotokoll kalt QUIC, bygd på UDP. QUIC gir hver strøm sin egen uavhengige feilhåndtering, slik at et pakketap i én strøm ikke blokkerer de andre. Mer om dette i kapittelet om transportlaget.