HTTP
forespørgslen og svaret som webben bygger på
Hvad det er
HTTP, Hypertext Transfer Protocol, er de regler, en browser, en app eller et
script følger, når den beder en server om noget og får et svar tilbage. Kører
samtalen inde i en krypteret TLS-forbindelse, hedder den samme protokol HTTPS.
En udveksling består altid af to dele. Klienten sender en forespørgsel med en
metode som GET eller POST, det mål, den vil have fat i, en række
headerfelter og nogle gange en krop. Serveren svarer med en statuskode på tre
cifre, sine egne headerfelter og som regel en krop. Når en side indlæses, et
billede hentes eller ét program kalder et andet, er det den samme udveksling,
der gentages.
Specifikationen kalder protokollen tilstandsløs. Hver forespørgsel skal indeholde alt, hvad serveren behøver for at forstå den, og protokollen husker intet fra den ene udveksling til den næste. Login, indkøbskurve og sessioner bygges ovenpå, typisk med cookies: et headerfelt, serveren sætter én gang, og som klienten sender med på alle senere forespørgsler.
Ordforrådet er lille, men præcist. GET og HEAD er sikre metoder; de beder om
en repræsentation og forventes ikke at ændre noget. PUT og DELETE er
idempotente, så den samme forespørgsel sendt to gange efterlader serveren i
samme tilstand som én gang. POST lover ingen af delene. Statuskoderne er
grupperet efter første ciffer: 2xx betyder succes, 3xx omdirigering, 4xx en fejl
i forespørgslen og 5xx en fejl på serveren. En klient, der møder en kode, den
ikke kender, ved derfor alligevel nogenlunde, hvad der skete.
Betydningen har ligget fast, mens formatet på ledningen er skiftet ud to gange. HTTP/1.1 skriver beskederne som tekstlinjer over en TCP-forbindelse, og hver forbindelse leverer sine svar ét ad gangen og i rækkefølge. HTTP/2 beholder metoder og felter, men pakker dem i binære rammer og fletter mange forespørgsler sammen på én forbindelse. HTTP/3 sender de samme rammer over QUIC, en transport bygget på UDP, så en tabt pakke kun forsinker den strøm, den hørte til. Siden 2022 har IETF udgivet semantikken i ét dokument og hver versions trådformat i et andet.
HTTP eller HTTPS er altså ikke et valg mellem to protokoller. HTTPS er HTTP med TLS nedenunder, som krypterer udvekslingen og beviser serverens navn. WebSocket begynder som en almindelig HTTP-forespørgsel og opgraderer derefter forbindelsen til en kanal, hvor begge parter kan sende, og gRPC er en konvention for fjernkald, der kører oven på HTTP/2.
Hvorfor det betyder noget
Stort set alle systemer, der taler med andre over internettet, bruger allerede HTTP. Det interessante valg er derfor, om man bruger det, protokollen definerer, eller behandler den som et rør og bygger resten selv.
Der er god grund til at bruge den fuldt ud, for en lang kæde af software forstår
allerede ordforrådet. Et klientbibliotek må gerne prøve en GET igen, når den
løber tør for tid, men skal lade en POST være, og det samme gælder en proxy
undervejs. En cache må gemme et svar mærket max-age=600 i ti minutter og
give det videre til den næste læser uden at spørge serveren. Overvågning tæller
5xx-svar som fejl uden at få det at vide. Et API, der sender hvert kald som en
POST til én adresse og melder fejl inde i et 200-svar, giver afkald på alt
det, og hver klient må lære netop det API’s private regler.
Modellen har sine grænser. Forespørgsel og svar passer til en klient, der ved, hvad den vil have, og langt dårligere til en server, der har nyt at fortælle. Derfor går live-opdateringer som regel over WebSocket, server-sent events eller gentagne forespørgsler.
Ukrypteret HTTP på det åbne internet er et kortere spørgsmål. Browsere markerer det som usikkert, alle på vejen kan læse og ændre indholdet, og certifikater er gratis, så offentlig trafik hører hjemme på HTTPS. Valget mellem HTTP/1.1, HTTP/2 og HTTP/3 er mest en indstilling på serveren eller kanten foran den. Applikationens kode forbliver den samme, fordi hver forespørgsel betyder det samme i alle tre.
Sådan ser det ud i praksis
Det tydeligste eksempel her på sitet er et API, der blev flyttet over på et framework, som er bygget om HTTP’s egen form. HTTP-API’et flyttede fra Fiber til Huma v2, der kører oven på Fiber. Hvert endpoint erklæres med structs for input og output, og valideringen kommer fra typerne i stedet for fra håndskrevne tjek. Ud af de samme erklæringer kommer en OpenAPI-beskrivelse, og et tjek i CI sammenligner de ruter, serveren registrerer, med dem, beskrivelsen annoncerer. Ved 649 paths og 760 operationer er pariteten 100 %, og den holdes, fordi en rute uden beskrivelse får buildet til at fejle. Præcis to endpoints blev på rå Fiber, nemlig WebSocket-endpointene, hvor Humas model med forespørgsel og svar ikke passer.
Sitet bruger selv indholdsforhandling. Hver side udgives både som HTML og som
Markdown, og én måde at få Markdown-udgaven på er at
sende Accept: text/markdown og lade serveren vælge. Samme side
forklarer, hvorfor onion-adressen med vilje er almindelig http://: adressen
er tjenestens egen offentlige nøgle, så kredsløbet er godkendt og krypteret,
før der overhovedet bliver talt HTTP.
HTTP dukker også op som værktøj i infrastrukturkode. Da en ekstra server blev sat op, blev begge udbyderes firewalls skrevet direkte mod deres REST-API’er gennem en generisk HTTP-opgave i stedet for en leverandørsamling. Det fjernede en afhængighed og dens versionsdrift mod at skrive hver forespørgsels form i hånden. Siden nævner også en anden pris: den generiske HTTP-opgave erklærer ingen understøttelse af check-tilstand, så en prøvekørsel af det play, der opretter maskinen, springer hver opgave i det over. Prøven køres derfor mod firewall-playet.
Hvor det går galt
De fleste HTTP-fejl opstår, når et ord i ordforrådet bruges til at betyde noget andet end det, resten af kæden hører.
En fejl, der ligner en succes
Et API svarer 200 og lægger "error": "card declined" i kroppen. En cache kan
gemme svaret, dashboards tæller det som en succes, og klientens logik for nye
forsøg går aldrig i gang. Send den statuskode, der passer, 4xx eller 5xx, og
beskriv problemet i kroppen. RFC 9457 definerer en JSON-form til netop det,
leveret som application/problem+json.
Et link, der sletter
En GET ændrer tilstand, for eksempel et afmeldings- eller slettelink i en
mail. Crawlere, linkforhåndsvisninger i chatapps, sikkerhedsscannere og
browserens forhåndsindlæsning følger alle links på egen hånd, og hver af dem
kan udløse handlingen. Det, der ændrer data, hører hjemme bag POST, PUT
eller DELETE, eventuelt med en bekræftelsesside, hvis linket skal findes.
En betaling, der trækkes to gange
Klienten sender en POST, forbindelsen ryger, før svaret kommer, og klienten
prøver igen. Serveren havde allerede behandlet den første. HTTP giver klienten
ingen måde at skelne de to tilfælde på, så API’et må gøre det: tag imod en
idempotensnøgle i et headerfelt og returnér det gemte resultat, når nøglen
kommer igen, eller lad klienten vælge den nye ressources id og oprette den med
PUT.
En formular, der bliver til en GET
Serveren svarer på en POST med 301 eller 302 for at sende den videre. Af
historiske grunde tillader specifikationen, at brugeragenten skifter metoden til
GET ved netop de to koder, og mange gør det, så kroppen forsvinder undervejs.
Skal metoden overleve omdirigeringen, er 307 og 308 de rigtige koder.
Den forkerte udgave fra en cache
Én URL leverer HTML eller Markdown, dansk eller engelsk, afhængigt af
forespørgslens headerfelter. En cache, der intet har fået at vide, gemmer den
udgave, den så først, og giver den videre til den næste læser. Svaret skal
nævne de felter, det afhænger af, for eksempel Vary: Accept, så cachen
gemmer én kopi pr. udgave frem for én pr. URL.
Læs mere
- RFC 9110: HTTP Semantics — metoder, statuskoder og headerfelter, defineret én gang for alle versioner.
- RFC 9111: HTTP Caching — hvornår
et gemt svar må genbruges, og hvad
Cache-Controlfortæller en cache. - RFC 9112: HTTP/1.1 — tekstformatet på ledningen, forbindelser og hvordan en besked afgrænses.
- RFC 9113: HTTP/2 — binære rammer og mange strømme over én forbindelse.
- RFC 9114: HTTP/3 — den samme semantik lagt oven på QUIC-strømme.
- RFC 9457: Problem Details for HTTP APIs — en fast JSON-krop til at beskrive, hvad der gik galt.