Hypertext Transfer Protocol (HTTP-protokolla)

Käydään läpi HTTP-protokollan tärkeimmät osat webbisovellusten ja sovelluskehityksen kannalta. Tähän liittyen käsitellään myös www-lomakkeiden toiminta.

HTTP-pyyntö ja -vastaus

HTTP on protokolla, jonka avulla selain ja www-palvelin keskustelevat. Keskustelu koostuu pyynnöistä ja vastauksista: selain pyytää yhtä resurssia kerrallaan, ja palvelin vastaa kuhunkin pyyntöön erikseen.

Sekä pyyntö että vastaus koostuvat kolmesta osasta: aloitusrivistä, otsakkeista ja mahdollisesta rungosta. Otsakkeet ja rungon erottaa tyhjä rivi. Tämä on syytä painaa mieleen, koska CGI-ohjelmassa juuri tuo tyhjä rivi on tulostettava itse.

Selaimen ja palvelimen välistä liikennettä on helpointa seurata selaimen kehittäjätyökalujen (F12 tai SHIFT+CTRL+I) verkkovälilehdellä.

Yhden sivun avaaminen ei ole yksi pyyntö vaan monta. Selain hakee ensin dokumentin, jäsentää sen ja hakee vasta sitten kaiken, mihin dokumentti viittaa: tyylitiedostot, skriptit, kuvat ja fontit. Jokainen niistä on oma HTTP-pyyntönsä omine otsakkeineen ja statuskoodeineen.

Sekvenssikaavio: selain lähettää palvelimelle GET-pyynnön              html-dokumentista ja saa vastaukseksi 200 OK. Selain jäsentää dokumentin ja              lähettää sen jälkeen erilliset GET-pyynnöt tyylitiedostosta, skriptistä ja              kuvasta, joihin palvelin vastaa kuhunkin omalla vastauksellaan.
Sivun latautuminen: yksi dokumentti, monta pyyntöä.

Selaimen lähettämä pyyntö

GET /ties4080/luennot/http/ HTTP/1.1
Host: appro.mit.jyu.fi
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: fi,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Referer: https://appro.mit.jyu.fi/ties4080/luennot/
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Selaimen lähettämät otsakkeet selityksineen
RiviMerkitys
GET /ties4080/luennot/http/ HTTP/1.1 Pyydetään resurssia (GET), jonka polku palvelimella on /ties4080/luennot/http/, protokollaversiolla HTTP/1.1.
Host: appro.mit.jyu.fi Palvelimen nimi. Pakollinen HTTP/1.1:ssä, koska samassa IP-osoitteessa voi olla useita sivustoja.
User-Agent: … Selaimen tiedot. Älä rakenna sovelluslogiikkaa tämän varaan; arvo on helppo väärentää ja se valehtelee historiallisista syistä muutenkin.
Accept: … Selaimen hyväksymät mediatyypit ja niiden painotus q-arvoilla. Palvelin voi valita tämän perusteella, minkä muodon se palauttaa (sisältöneuvottelu).
Accept-Language: fi,en-US;q=0.7,en;q=0.3 Halutut kielet paremmuusjärjestyksessä.
Accept-Encoding: gzip, deflate, br Tuetut pakkaustavat. Palvelin saa pakata vastauksen näillä.
Referer: … Osoite, josta ollaan tulossa. Kirjoitusvirhe otsakkeen nimessä on alkuperäisestä speksistä eikä sitä ole koskaan korjattu.
Connection: keep-alive Jäänne HTTP/1.0-ajalta. HTTP/1.1:ssä yhteys on oletuksena pysyvä, joten otsaketta ei tarvittaisi.
Huomaa

Accept-Charset-otsake on poistettu käytöstä eivätkä selaimet enää lähetä sitä. Merkistö kerrotaan nykyisin vain vastauksen Content-Type-otsakkeessa.

Palvelimen vastaus

HTTP/1.1 200 OK
Date: Tue, 08 Sep 2026 10:53:18 GMT
Server: Apache
Content-Type: application/xhtml+xml; charset=UTF-8
Content-Length: 286
Last-Modified: Mon, 07 Sep 2026 12:44:13 GMT
ETag: "11e-63f0a1c2e4b80"
Accept-Ranges: bytes
Vary: Accept-Encoding

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="fi" xml:lang="fi">
  <head>
    <meta charset="UTF-8" />
    <title>Testisivu</title>
    <link rel="stylesheet" href="testi.css" />
  </head>
  <body>
    <h1>Testiotsikko</h1>
    <p>Testitekstiä</p>
  </body>
</html>
Vastauksen otsakkeet selityksineen
RiviMerkitys
HTTP/1.1 200 OK Statuskoodi. Kaikki kunnossa.
Date: … Vastauksen lähetysaika. Aina GMT-ajassa.
Server: Apache Palvelinohjelmisto. Versiotiedot kannattaa piilottaa, koska ne helpottavat tunnettujen haavoittuvuuksien etsimistä.
Content-Type: application/xhtml+xml; charset=UTF-8 Mediatyyppi ja merkistö. Tämä on tärkein otsake, jonka oma sovellus asettaa: selain tulkitsee sisällön sen perusteella.
Content-Length: 286 Rungon koko tavuina — ei merkkeinä. Ääkköset vievät UTF-8:ssa kaksi tavua.
Last-Modified: … Milloin resurssia on viimeksi muutettu.
ETag: "11e-63f0a1c2e4b80" Sisällön tunniste, joka muuttuu dokumentin muuttuessa. Selain voi kysyä If-None-Match-otsakkeella, onko sisältö muuttunut, ja saada vastaukseksi 304 Not Modified.
Accept-Ranges: bytes Palvelin osaa palauttaa myös osan resurssista (esim. videon kelaus tai keskeytyneen latauksen jatkaminen).
Vary: Accept-Encoding Kertoo välimuisteille, minkä pyyntöotsakkeen mukaan vastaus vaihtelee. Ilman tätä välimuisti voi tarjoilla väärän version.

Accept, Accept-Language ja Accept-Encoding

Nämä kolme otsaketta ovat toiveita, eivät käskyjä. Selain kertoo niillä, mitä se osaa ottaa vastaan ja mitä se mieluiten saisi, ja palvelin valitsee. Ilmiötä kutsutaan sisältöneuvotteluksi: sama osoite voi palauttaa eri sisällön eri pyytäjälle.

Kaikki kolme käyttävät samaa syntaksia: vaihtoehdot pilkuilla eroteltuna ja kunkin painotus q-arvolla väliltä 0–1. Puuttuva q tarkoittaa arvoa 1, ja q=0 tarkoittaa "tämä ei kelpaa lainkaan". Suurempi arvo on parempi, ja tarkempi määrittely voittaa yleisemmän: text/html on tarkempi kuin text/*, joka on tarkempi kuin */*.

Kolme neuvoteltavaa asiaa
PyyntöotsakeMistä neuvotellaan VastauksessaVälimuistille
Acceptmediatyyppi Content-TypeVary: Accept
Accept-Languagekieli Content-Language Vary: Accept-Language
Accept-Encodingpakkaus siirtoa varten Content-Encoding Vary: Accept-Encoding
Muista Vary

Jos vastaus vaihtelee jonkin pyyntöotsakkeen mukaan, se on kerrottava Vary-otsakkeessa. Muuten välimuisti — selaimen oma, yliopiston välityspalvelin tai CDN — tarjoilee ensimmäisenä pyytäneelle valitun version kaikille muillekin. Klassinen oire: sivusto jää englanniksi, koska joku haki sen ensin englanniksi.

Accept

Accept luettelee mediatyypit, joita pyytäjä osaa käsitellä. Selaimen dokumenttipyynnössä arvo on nykyisin käytännössä vakio:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8

Luettelo tarkoittaa: mieluiten text/html tai application/xhtml+xml (molemmat painolla 1), sitten application/xml (0,9), ja viimeisenä mikä tahansa muu (0,8). Selain siis hyväksyy XHTML:n täysin samanarvoisena kuin HTML:n, mikä on kurssin tehtävien kannalta olennaista.

Sama selain lähettää eri arvon riippuen siitä, mitä se on hakemassa. Kuvaa haettaessa arvo on esimerkiksi image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8 ja tyylitiedostoa haettaessa text/css,*/*;q=0.1. Palvelin voi tämän perusteella palauttaa samasta osoitteesta modernin AVIF-kuvan uusille selaimille ja JPEG-kuvan vanhoille.

Rajapinnoissa otsaketta käytetään muodon valitsemiseen. Sama osoite voi palauttaa koneelle JSON-muotoa ja selaimelle sivun:

from flask import request, jsonify, render_template, Response

@app.route("/lauta")
def lauta():
    paras = request.accept_mimetypes.best_match(
        ["application/json", "application/xhtml+xml"])

    if paras == "application/json":
        vastaus = jsonify(tilanne())
    else:
        vastaus = Response(render_template("lauta.html", tilanne=tilanne()),
                           content_type="application/xhtml+xml; charset=UTF-8")

    # kerrotaan välimuisteille, että vastaus riippuu Accept-otsakkeesta
    vastaus.headers["Vary"] = "Accept"
    return vastaus

Kokeile eroa komentoriviltä:

curl -H "Accept: application/json" https://palvelin.example/lauta
curl -H "Accept: application/xhtml+xml" https://palvelin.example/lauta

Jos palvelin ei osaa tuottaa mitään pyydetyistä muodoista, se saa vastata 406 Not Acceptable. Käytännössä useimmat palvelimet palauttavat mieluummin oletusmuodon kuin virheen, koska rikkinäinen Accept-otsake on yleisempi vika kuin aito yhteensopimattomuus.

Accept-Language

Accept-Language kertoo käyttäjän kieliasetukset paremmuusjärjestyksessä. Kielitunnisteet ovat BCP 47 -muotoisia: pelkkä kieli (fi) tai kieli ja alue (en-US, sv-FI).

Accept-Language: fi,en-US;q=0.7,en;q=0.3

Tämä tarkoittaa: suomeksi jos suinkin mahdollista, muuten amerikanenglanniksi, ja viimeisenä mikä tahansa englanti. Palvelin valitsee parhaan tarjolla olevan version, kertoo valintansa Content-Language-otsakkeessa ja lisää Vary: Accept-Language.

kieli = request.accept_languages.best_match(["fi", "en", "sv"]) or "fi"
vastaus = Response(render_template(f"etusivu.{kieli}.html"),
                   content_type="application/xhtml+xml; charset=UTF-8")
vastaus.headers["Content-Language"] = kieli
vastaus.headers["Vary"] = "Accept-Language"

Muista myös merkitä dokumentin kieli html-elementin lang- ja xml:lang-attribuuteilla. Ne kertovat kielen ruudunlukijalle ja hakukoneelle; HTTP-otsake taas kertoo, miksi juuri tämä versio lähetettiin.

Otsakkeen vaikutusta on helpointa kokeilla curlilla. Valitsin -D - tulostaa vastauksen otsakkeet ja -o /dev/null heittää itse sisällön menemään:

curl -s -D - -o /dev/null -H "Accept-Language: fi" https://hazor.iki.fi/
curl -s -D - -o /dev/null -H "Accept-Language: en" https://hazor.iki.fi/

Vertaa vastauksista Content-Language- ja Vary-otsakkeita sekä Content-Length-arvoa. Jos ne ovat molemmilla kutsuilla samat, palvelin ei neuvottele kielestä lainkaan vaan palauttaa aina saman dokumentin — mikä on täysin kelvollista, kunhan Vary: Accept-Language ei silloin lupaa muuta.

Todellisessa selainpyynnössä kieliä on useita painotuksineen. Näin saat curlilla saman pyynnön, jonka suomenkielinen selain lähettäisi, ja voit samalla katsoa millaisen dokumentin kielen palvelin ilmoittaa:

curl -s -D - -o /dev/null -H "Accept-Language: fi,en;q=0.5" https://hazor.iki.fi/
curl -s -D - -o /dev/null -H "Accept-Language: en-US,en;q=0.9,fi;q=0.3" https://hazor.iki.fi/

# pelkkä yksi otsake näkyviin monen sijasta
curl -s -D - -o /dev/null -H "Accept-Language: en" https://hazor.iki.fi/ | grep -i content-language
Kolme sudenkuoppaa

Accept-Encoding

Accept-Encoding ei koske sisältöä vaan sen pakkaamista siirtoa varten. Selain kertoo, mitä pakkaustapoja se osaa purkaa:

Accept-Encoding: gzip, deflate, br, zstd

Palvelin valitsee yhden, pakkaa vastauksen rungon ja kertoo valinnastaan:

HTTP/1.1 200 OK
Content-Type: application/xhtml+xml; charset=UTF-8
Content-Encoding: br
Content-Length: 1284
Vary: Accept-Encoding

Selain purkaa vastauksen automaattisesti, joten sivun tekijä ei näe eroa — paitsi siirretyn datan määrässä. Tekstimuotoinen sisältö kutistuu tyypillisesti neljäsosaan tai viidesosaan. Kehittäjätyökalujen verkkovälilehti näyttää sekä siirretyn että purettuun kokoon; niiden ero on juuri tämä.

Kokeile itse. Pyydä sama sivu ensin gzipillä ja sitten Brotlilla ja vertaa, mitä palvelin vastaa Content-Encoding-otsakkeessa:

curl -s -D - -o /dev/null -H "Accept-Encoding: gzip" https://hazor.iki.fi/
curl -s -D - -o /dev/null -H "Accept-Encoding: br" https://hazor.iki.fi/

Siirretyn datan määrän näkee -w-valitsimella. Vertaa pakkaamatonta vastausta molempiin pakkaustapoihin:

curl -s -o /dev/null -w "identity: %{size_download} tavua\n" \
     -H "Accept-Encoding: identity" https://hazor.iki.fi/

curl -s -o /dev/null -w "gzip:     %{size_download} tavua\n" \
     -H "Accept-Encoding: gzip" https://hazor.iki.fi/

curl -s -o /dev/null -w "br:       %{size_download} tavua\n" \
     -H "Accept-Encoding: br" https://hazor.iki.fi/

Huomaa, että curl ei pura pakkausta itse, ellei sitä pyydetä. Ilman --compressed-valitsinta pakattu vastaus tulostuu ruudulle roskana, ja juuri siksi edellisissä esimerkeissä sisältö ohjattiin /dev/null-laitteeseen. Kun haluat sekä pakatun siirron että luettavan lopputuloksen, käytä --compressed-valitsinta — se lisää Accept-Encoding-otsakkeen automaattisesti ja purkaa vastauksen:

curl -s --compressed https://hazor.iki.fi/ | head

# vertaa: sama pyyntö ilman purkamista tulostaa binääriroskaa
curl -s -H "Accept-Encoding: gzip" https://hazor.iki.fi/ | head

Jos palvelin ei tue pyydettyä pakkaustapaa, se vastaa yksinkertaisesti pakkaamattomana ilman Content-Encoding-otsaketta. Sitä ei siis tarvitse erikseen varautua käsittelemään.

Älä sekoita näitä kahta

Content-Encoding kertoo pakkauksen ja Content-Type-otsakkeen charset-parametri merkistön. Ne ovat eri asioita, vaikka molempia kutsutaan arkikielessä koodaukseksi. Merkistö kertoo, miten kirjaimet on esitetty tavuina; pakkaus taas tiivistää nuo tavut siirtoa varten ja puretaan ennen kuin merkistöä edes katsotaan.

Pakkaus on www-palvelimen työtä, ei sovelluksen. Apachella siitä huolehtii mod_deflate ja Cloud Runissa palvelun edessä oleva kuormantasaaja, joten CGI- tai Flask-sovelluksen ei tarvitse tehdä asialle mitään. Jo valmiiksi pakattuja tiedostoja (JPEG, PNG, videot, zip) ei kannata pakata uudelleen; niistä ei ole enää mitään puristettavaa.

HTTPS

HTTPS ei ole eri protokolla vaan tavallista HTTP:tä TLS-salauksen sisällä. Se antaa kolme asiaa: liikenne on salattua, sen muuttuminen matkalla havaitaan, ja palvelimen henkilöllisyys on varmennettu varmenteella.

Salaus koskee koko pyyntöä ja vastausta: polkua, querystringiä, otsakkeita, evästeitä ja runkoa. Ulkopuoliselle jää näkyviin vain palvelimen nimi ja liikenteen määrä. Selaimet vaativat HTTPS:ää nykyisin lähes kaikelta, ja moni rajapinta (esim. sijaintitieto ja service workerit) toimii vain salatulla yhteydellä. Käytä sovelluksissasi aina https://-osoitteita.

HTTP/1.1, HTTP/2 ja HTTP/3

Edellä esitetty tekstimuotoinen pyyntö on HTTP/1.1:tä. Uudemmat versiot säilyttävät saman semantiikan — samat metodit, otsakkeet ja statuskoodit — mutta siirtävät ne eri tavalla:

Sovelluskehittäjän kannalta versio ei useinkaan näy: palvelin ja selain sopivat siitä keskenään, ja oma koodi käsittelee samoja metodeja ja otsakkeita versiosta riippumatta.

Metodit

Metodi kertoo, mitä resurssille halutaan tehdä. Kaksi käsitettä kannattaa tuntea:

HTTP-metodit
MetodiKäyttöTurvallinenIdempotentti
GET Hae resurssikylläkyllä
HEAD Kuten GET, mutta vain otsakkeetkylläkyllä
POST Lähetä tietoa käsiteltäväksi; luo uusieiei
PUT Korvaa resurssi kokonaaneikyllä
PATCH Muuta osaa resurssistaeiei
DELETE Poista resurssieikyllä
OPTIONS Kysy, mitä resurssille voi tehdäkylläkyllä

HTML-lomake osaa vain GET- ja POST-metodit. Muut ovat käytössä JavaScriptin fetch-kutsuissa ja REST-rajapinnoissa.

Statuskoodit

Palvelin palauttaa jokaiseen pyyntöön statuskoodin, joka kertoo lyhyesti miten pyyntö onnistui. Ensimmäinen numero kertoo luokan:

Web-sovelluskehityksessä tarvittavat koodit
KoodiNimiMilloin oma sovellus palauttaa
200OK Normaali onnistunut vastaus.
201Created Uusi resurssi luotiin. Osoite kerrotaan Location-otsakkeessa.
204No Content Onnistui, mutta vastauksessa ei ole runkoa.
301Moved Permanently Osoite vaihtui pysyvästi. Selaimet ja hakukoneet muistavat tämän pitkään, joten älä käytä kokeiluun.
302Found Väliaikainen ohjaus. Selaimet vaihtavat metodin GET:ksi, mikä ei ollut alkuperäinen tarkoitus — käytä mieluummin 303:a tai 307:ää.
303See Other Lomakkeen käsittelyn jälkeen. Kertoo selaimelle, että tulos haetaan GET-pyynnöllä toisesta osoitteesta.
304Not Modified Sisältö ei ole muuttunut; selain käyttää välimuistiaan.
307 / 308Temporary / Permanent Redirect Kuten 302 ja 301, mutta metodi ja runko säilyvät.
400Bad Request Pyyntö on virheellinen tai puutteellinen.
401Unauthorized Kirjautuminen puuttuu. Nimestään huolimatta kyse on tunnistautumisesta, ei käyttöoikeudesta.
403Forbidden Tunnistautuminen ei auta; oikeudet eivät riitä. Tämän saat myös, jos CGI-ohjelman tiedosto-oikeudet ovat väärin.
404Not Found Resurssia ei ole.
405Method Not Allowed Esim. POST osoitteeseen, joka hyväksyy vain GET-pyyntöjä. Flask palauttaa tämän itse, jos methods-luettelo ei täsmää.
422Unprocessable Content Pyyntö on muodollisesti kelvollinen, mutta sisältö ei kelpaa (validointivirhe).
429Too Many Requests Pyyntöjä on tullut liikaa.
500Internal Server Error Sovellus kaatui. Tuttu näky, kun CGI-ohjelman rivinvaihdot tai oikeudet ovat väärin.
502 / 503 / 504Bad Gateway / Service Unavailable / Gateway Timeout Taustapalvelin ei vastaa, on ruuhkautunut tai hidas.

Otsakkeet, jotka sovellus itse asettaa

Suurimman osan otsakkeista hoitaa www-palvelin. Nämä ovat niitä, jotka oma sovellus asettaa:

OtsakeKäyttö
Content-Type Mediatyyppi ja merkistö. Ilman tätä selain arvaa, ja arvaa usein väärin.
Location Uudelleenohjauksen kohde. Kulkee aina 3xx-statuskoodin kanssa.
Set-Cookie / Cookie Evästeen asettaminen ja palauttaminen. Käytä HttpOnly- ja Secure-määreitä.
Cache-Control Kuinka kauan vastausta saa säilyttää välimuistissa. Henkilökohtaiselle sisällölle private tai no-store.
Content-Disposition Näytetäänkö sisältö selaimessa vai tarjotaanko sitä tallennettavaksi, ja millä tiedostonimellä.
from flask import Response, redirect, url_for

# mediatyyppi ja merkistö
vastaus = Response(sisalto, content_type="application/xhtml+xml; charset=UTF-8")

# oma otsake
vastaus.headers["Cache-Control"] = "no-store"

# uudelleenohjaus: asettaa Location-otsakkeen ja statuskoodin
return redirect(url_for("lauta"), code=303)

Lomake

form-elementti määrittelee lomakkeen alkamis- ja loppumiskohdan. Sen sisään sijoitetaan lomake-elementit, ja sen attribuutit kertovat, minne ja miten tiedot lähetetään.

<form action="https://palvelin.example/sovellus" method="post">
  <p>
    <label for="nimi">Nimi:</label>
    <input id="nimi" type="text" name="nimi" />
  </p>
  <p>
    <label for="email">Sähköposti:</label>
    <input id="email" type="email" name="email" />
  </p>
  <p>
    <label for="kommentti">Kommentti:</label>
    <textarea id="kommentti" name="kommentti" rows="4" cols="40"></textarea>
  </p>
  <p>
    <input type="submit" name="laheta" value="Lähetä kommenttisi" />
  </p>
</form>

Jokaisella kentällä on oltava name, koska sovellus lukee arvot juuri sillä nimellä. label-elementin for-attribuutti viittaa kentän id-attribuuttiin — se ei ole sama asia kuin name. Älä käytä value-attribuuttia ohjetekstinä; siihen on placeholder, ja varsinainen selitys kuuluu labeliin.

action-attribuutti

action kertoo osoitteen, jossa lomakkeen käsittelevä ohjelma sijaitsee. Tyhjä arvo (action="") lähettää lomakkeen samaan osoitteeseen, jossa lomake itse on — usein juuri se, mitä halutaan.

method-attribuutti

method kertoo, miten tiedot toimitetaan. HTML-lomakkeen mahdolliset arvot ovat get ja post, ja ne viittaavat suoraan HTTP-metodeihin.

GET siirtää tiedot osoitteen querystringissä:

https://palvelin.example/sovellus?nimi=Etunimi+Sukunimi&email=maija%40example.com&kommentti=t%C3%A4h%C3%A4n+kommentti&laheta=L%C3%A4het%C3%A4+kommenttisi

Ohjelman osoite ja tiedot erottaa kysymysmerkki, kentät erottaa &-merkki ja kentän nimen sen arvosta yhtäsuuruusmerkki. Erikoismerkit on koodattu, ja välilyönnin paikalla on + tai %20.

POST siirtää tiedot pyynnön rungossa, jolloin ne eivät näy osoitteessa:

POST /sovellus HTTP/1.1
Host: palvelin.example
Content-Type: application/x-www-form-urlencoded
Content-Length: 96

nimi=Etunimi+Sukunimi&email=maija%40example.com&kommentti=t%C3%A4h%C3%A4n+kommentti&laheta=L%C3%A4het%C3%A4
Yleinen väärinkäsitys

POST ei ole turvallisuusominaisuus. Tiedot eivät näy osoiterivillä, selainhistoriassa, kirjanmerkeissä eivätkä palvelinlokeissa, mutta ne näkyvät sellaisenaan kehittäjätyökalujen verkkovälilehdellä ja kenelle tahansa, joka pääsee liikennettä katsomaan. Ainoa asia, joka salaa tiedot matkalla, on HTTPS.

Kumpi siis milloinkin?

enctype-attribuutti

enctype kertoo, missä muodossa POST-lomakkeen runko koodataan. GET-lomakkeeseen se ei vaikuta.

Merkistöt ja percent-encoding

Osoitteessa saa esiintyä vain rajattu joukko merkkejä. Kaikki muut — ääkköset, välilyönnit, lainausmerkit, hakasulkeet — koodataan percent-encoding-tavalla prosenttimerkillä ja merkin tavujen heksadesimaaliesityksellä.

Koodauksen tulos riippuu siitä, millä merkistöllä teksti ensin muutetaan tavuiksi. UTF-8:ssa ä on kaksi tavua, ISO-8859-1:ssä yksi:

from urllib.parse import quote_plus, unquote_plus

quote_plus("Sähköpostiosoite")
# 'S%C3%A4hk%C3%B6postiosoite'          UTF-8, nykyinen ja oikea muoto

quote_plus("Sähköpostiosoite", encoding="latin-1")
# 'S%E4hk%F6postiosoite'                ISO-8859-1, vanha muoto

unquote_plus("S%E4hk%F6postiosoite")
# 'S\ufffdhk\ufffdpostiosoite'          UTF-8:na luettuna tavut ovat kelvottomia
Vanha esimerkki

Vanhoissa materiaaleissa (myös tämän kurssin aiemmissa versioissa) näkee osoitteita seuraavaan tapaan:

http://palvelin.example/sovellus?Nimi=Etunimi+Sukunimi&email=Sahk%F6postiosoite&kommentti=t%E4h%E4n+kommentti&laheta=L%E4het%E4+kommenttisi

Tässä %F6 on ISO-8859-1:n ö ja %E4 sen ä. Nykyinen selain lähettäisi samat merkit muodossa %C3%B6 ja %C3%A4. Esimerkki kannattaa tunnistaa, koska vastaavia osoitteita on yhä liikkeellä, mutta älä käytä sitä mallina: jos sovellus purkaa tavut väärällä merkistöllä, tuloksena on joko korvausmerkkejä (S�hk�posti) tai mojibakea (Sähköposti).

Käytännössä koodaus menee oikein, kun kaikki lenkit ketjussa ovat UTF-8:aa: dokumentin Content-Type, meta charset ja tarvittaessa lomakkeen accept-charset-attribuutti. Selain koodaa lomakkeen sillä merkistöllä, jolla lomakesivu on tarjoiltu.

Osoitteita ei kannata rakentaa käsin merkkijonoja liimaamalla, vaan koodausfunktioilla:

from urllib.parse import urlencode
from flask import url_for
import html

kysely = urlencode({"nimi": "Mälli Henkilö", "kurssi": "Ällö kurssi"})
osoite = url_for("lauta", l=8, p1="Mälli Henkilö")

# html-dokumenttiin sijoitettaessa myös &-merkit on koodattava
linkki = '<a href="' + html.escape(osoite) + '">Linkki</a>'

Lomakkeen käsittely

Sekvenssikaavio: selain hakee lomakesivun GET-pyynnöllä, käyttäjä              täyttää lomakkeen ja selain lähettää sen POST-pyynnöllä. Sovellus tarkistaa              syötteet. Jos syöte ei kelpaa, palvelin palauttaa saman lomakkeen              virheilmoituksineen. Jos syöte kelpaa, muutos tallennetaan ja palvelin              vastaa statuskoodilla 303, jonka jälkeen selain hakee tulossivun              GET-pyynnöllä.
Lomakkeen käsittely POST/Redirect/GET-kuvion mukaisesti.
  1. Selain pyytää palvelimelta lomakesivun.
  2. Palvelin vastaa otsakkeilla ja lomakkeen sisältävällä dokumentilla.
  3. Käyttäjä täyttää lomakkeen ja lähettää sen. Selain kokoaa kenttien nimet ja arvot ja lähettää ne action-osoitteeseen method-attribuutin kertomalla metodilla.
  4. Palvelimella suoritettava ohjelma käsittelee tiedot.
  5. Ohjelma palauttaa tuloksen: virheilmoitukset ja korjattavaksi palautetun lomakkeen, tulossivun, uudelleenohjauksen tai vaikka kuvan.

Se, miten ohjelma saa tiedot käyttöönsä, riippuu rajapinnasta. Suoraan CGI-rajapintaa käytettäessä pyynnön tiedot tulevat ympäristömuuttujissa (REQUEST_METHOD, QUERY_STRING, CONTENT_LENGTH, HTTP_*) ja POST-runko standardisyötteestä luettuna. Flask tarjoaa saman tiedon valmiiksi jäsennettynä:

from flask import request

request.method                    # "GET" tai "POST"
request.args.get("nimi")          # osoitteen parametrit
request.form.get("nimi")          # POST-lomakkeen kentät
request.values.get("nimi")        # molemmat yhdessä
request.files.get("liite")        # multipart/form-data -lähetykset
request.headers.get("User-Agent") # pyynnön otsakkeet
request.environ                   # koko ympäristö, vrt. CGI-muuttujat

POST, uudelleenohjaus ja GET

Jos POST-pyyntöön vastataan suoraan sivulla, selaimen osoitteessa on yhä käsittelijän osoite ja pyyntönä POST. Kun käyttäjä painaa päivitystä tai selaa taaksepäin, selain kysyy lomakkeen lähettämistä uudelleen — ja sama lisäys tai poisto tehdään toistamiseen.

Ratkaisu on POST/Redirect/GET: käsittelijä tekee muutoksen ja vastaa uudelleenohjauksella tulossivulle, jonka selain hakee GET-pyynnöllä. Silloin osoiterivillä on tulossivun osoite, sivun voi päivittää vapaasti ja siitä voi tehdä kirjanmerkin.

@app.route("/kommentti", methods=["POST"])
def tallenna():
    tallenna_kommentti(request.form.get("kommentti", ""))
    # 303 kertoo, että tulos haetaan GET-pyynnöllä toisesta osoitteesta
    return redirect(url_for("kiitos"), code=303)

HTTP on tilaton

HTTP on oletuksena tilaton. Palvelin ei tiedä pyyntöjen välillä mitään edellisistä pyynnöistä: jokainen pyyntö on itsenäinen. Tila on siis kuljetettava jotenkin mukana:

Palvelinprosessin muistiin tilaa ei voi jättää: CGI-ohjelmassa tulkki käynnistyy joka pyynnöllä uudelleen, ja pilvipalvelussa samasta sovelluksesta voi olla käynnissä useita instansseja.

Esimerkit

Lisätietoa

Käyttäjien kommentit

Kommentoi Lisää kommentti