Python ja CGI-ohjelmointi
CGI eli Common Gateway Interface on yksinkertaisin rajapinta WWW-sovelluksien tekemiseen
HTML-dokumentti Python-kielellä ja CGI-rajapinnan kautta tuotettuna:
#!/opt/rh/rh-python38/root/usr/bin/python3.8
# -*- coding: utf-8 -*-
import cgitb
cgitb.enable()
print("""Content-type: text/html; charset=UTF-8
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Malli</title>
</head>
<body>
<h1>Tulostetaan Pythonilla:</h1>
<p>
""")
print("Hello World! Osaatkönä skändejä?")
print("""
</p>
</body>
</html>
""")
- #!/opt/rh/rh-python38/root/usr/bin/python3.8 kertoo mistä www-palvelimen pitää etsiä käytettävää komentotulkkia eli tässä tapauksessa pythonia. Yleensä
#!/usr/bin/python
tai#!/usr/bin/python3
- # -*- coding: utf-8 -*- varmistaa, että lähdekoodi käsitellään UTF-8-merkistönä eikä vahingossakaan esim. ISO-8859-1-muotoisena
- print("""Content-type: text/html; charset=UTF-8 Tulostetaan useampi rivi tekstiä jolloin pythonissa käytetään """-merkintää osoittamaan
tulostuksen alku.
Ensimmäiselle riville pitää tulostaa www-selaimen tarvitsema tieto dokumentin mediatyypistä HTTP-otsakkeena (text/html). HTTP-otsakkeen yhteydessä voidaan myös asettaa käytetty merkistö.
Muitakin HTTP-protokollan otsakkeita voitaisiin tulostaa tässä vaiheessa. Varsinainen HTML-dokumentti alkaa ensimmäisen tyhjän rivin jälkeen.
- """ Monen rivin tuloste loppuu
- print "Hello World!" Tavallinen yhden rivin tulostus
Virheilmoitukset ja asetukset
Pythonilla tehtyjen CGI-ohjelmien debuggaamista helpottaa cgitb-moduulin käyttöönotto:
import cgitb
cgitb.enable()
cgitb aiheuttaa virheraporttien näyttämisen www-selaimessa tai niiden tallentamisen tekstitiedostoon.
Oletuksena cgitb-kirjasto tulostaa virheilmoitukset HTML-muodossa. Jos haluat tekstimuotoisen tulostuksen niin käytä kutsua:
cgitb.enable(format="text")
Lisätietoa: HOWTO Use Python in the web
CGI-ohjelmat users.jyu.fi-palvelimella
Viimeisimmät tiedot löytyvät aina digipalvelujen ohjeesta: CGI/SSI-tekniikoiden käyttäminen www-palveluissa (users.jyu.fi/groups.jyu.fi)
- CGI-ohjelmien on oltava W:\cgi-bin\-kansiossa tai sen alikansiossa. Mikään muu kansio ei kelpaa
- CGI-ohjelmien tiedostopääte on oltava .cgi
- Hakemiston (jossa ajettava tiedosto on) omistajan/ryhmän pitää olla samat kuin itse käyttäjän (uid/gid). Nämä tiedot saa selville id-komennolla.
- Suoritettavan tiedoston omistajan ja ryhmän pitää olla samat kuin käyttäjän, jona tiedosto suoritetaan.
- Suoritettavaan tiedostoon tai hakemistoon ei saa olla kirjoitusoikeutta muilla kuin itse käyttäjällä.
- Harvinaisissa tapauksessa SELinux voi aiheuttaa ongelmia. Toimi silloin digipalvelujen ohjeen mukaan.
Python ja merkistöt
- Sinun on aina tiedettävä mikä merkistö on merkkijonoissa käytössä. Tämä koskee kaikkia ohjelmointikieliä
- Kirjoita ohjelmakoodi ja sen merkkijonot UTF-8-merkistössä
# -*- coding: utf-8 -*-
- html-dokumentin merkistön on myös oltava UTF-8 (content-type, mahdollinen xml-deklaraatio. mahdollinen meta-elementti, formin accept-charset)
HTTP-otsakkeessa oikea merkistö:
Content-type: text/html; charset=UTF-8
Pelkässä tekstimuodossa:
Content-type: text/plain; charset=UTF-8
Edelläolevan pitäisi riittää mutta jos merkistö on mainittu jossain muuallakin niin myös siellä UTF-8:
<!doctype html> <html> <head> <meta charset="UTF-8" /> <title>Malli</title> </head> <body> <form action="" method="post" accept-encoding="UTF-8"> </form> </body> </html>
- Jos joudut käyttämään muuta merkistöä kuin UTF-8, muista encode
Querystring
- Sivun osoitteeseen voidaan lisätä parametreja seuraavalla tavalla:
http://osoite.example?parametri=arvo¶metri2=arvo1¶metri2=arvo2 http://osoite.example?nimi=M%C3%A4lli%20Henkil%C3%B6&arvosana=5&arvosana=3&arvosana=2&kurssi=Helppo+kurssi&kurssi=Vaikea+kurssi&kurssi=%C3%84ll%C3%B6+kurssi
Osoitteen perään lisätään ?-merkki. ?-merkin jälkeen luetellaan avaimia ja arvoja &-merkillä eroteltuina
Osoitteessa esiintyvät erikoismerkit on koodattava Percent-encoding-tavalla. Pythonissa tämän voi tehdä urllib.quote_plus-funktiolla.
Jos query_stringin sisältävän osoitteen sijoittaa html-dokumenttiin on muistettava koodata myös &-merkit cgi.escape-funktiolla.
- Querystringissä annettuihin parametreihin pääsee käsiksi.
Pythonin cgi-kirjaston avulla. WWW-sivuilla
olevien lomakkeiden lähettämät tiedot käsitellään samalla tavalla. Tähän palataan seuraavien viikkojen tehtävissä.
import cgi fields = cgi.FieldStorage() # alustaa lomake-olion #pyytää ensimmäisen url-nimisen lomakekentän arvon. Jos sitä ei ole annettu käyttää #oletusmerkkijonoa "1". #Tiedot ovat aina merkkijonoja paitsi oletusarvoksi voi antaa myös muita tyyppejä nimi = fields.getfirst("nimi", "Tuntematon") # Jos samalla parametrilla on useampia arvoja niin ne voidaan pyytää listana # Lista on tyhjä, jos pyydettyä parametria ei löydy arvosanat = fields.getlist("lkm")
Flask ja users.jyu.fi
Lopullinen sovellus pitää yleensä saada toimimaan muualla kuin omalla koneella ja toiminnallisuudessa voi olla eroja esim. tiedostopolut voivat olla erilaisia. Debuggaus on kuitenkin huomattavasti helpompaa omalla koneella. Jos on W:-asema verkkolevynä niin voi suoraan sieltä ajaa sovellustaan oman koneen Flaskilla ja testailla samaan aikaan myös users.jyu.fin kautta.
Flaskia aloitellessa muistakaa users.jyu.fi:ssä kaikki temput mitä vaadittiin, että cgi-ohjelma lähtee toimimaan:
- oltava cgi-bin-kansiossa
- ryhmän oltava users
- suoritusoikeus ja lukuoikeus ryhmällä ja muilla
- cgi-ohjelmaan ei saa olla kirjoitusoikeutta ryhmällä ja muilla
- Myöskään samaan kansioon ei saa olla kirjoitusoikeutta ryhmällä ja muilla
- Flask hoitaa itse http-otsakkeet
- Mitään ei saa printata flask-funktiosta vaan vain ja ainoastaan voidaan palauttaa return-lauseessa yksi merkkijono
users.jyu.fi
- Ota ssh-yhteys jalava- tai halava-palvelimeen
- Luo W:-asemalle kansio
cgi-bin/ties4080/. Kansion koko polku halava/jalava-koneessa on
/wwwhome/home/oma_tunnus/public_html/cgi-bin/ties4080/
Kansion täytyy ehdottomasti olla w:\cgi-bin eikä mikään muu. kts. CGI/SSI-tekniikat users.jyu.fi-palvelimella - Siirry cgi-bin/ties4080/-kansioon
- Luo virtuaaliympäristö python3.8:lla
/opt/rh/rh-python38/root/usr/bin/python3.8 -m venv venv
- Aktivoi virtuaaliympäristö komennolla (Bash-shellissä):
Jos käytätkin esim. csh/tcsh, niin aktivointikomento on:. venv/bin/activate
source venv/bin/activate.csh
- Aktivoitumisen merkiksi komentokehotteesi alkuun ilmestyy merkintä (venv)
- Päivitä ensimmäiseksi pip uusimpaan versioon:
pip install --upgrade pip
- Asenna seuraavaksi Flask- ja Flask-WTF-kirjastot:
pip install Flask Flask-WTF
- Virtuaaliympäristösi on nyt käyttövalmis. Voit suorittaa Python-ohjelmia tässä ympäristössä, kun
asetat shebang-rivin osoittamaan
virtuaaliympäristössä olevaan python-tulkkiin. users.jyu.fi (karahka2)-palvelimessa suoritettavalla ohjelmalle polku on muotoa:
Polku ei ole sama kuin halava/jalava-koneissa. Näissä koneissa polku on:#!/home/oma_tunnus/public_html/cgi-bin/ties4080/venv/bin/python
#!/wwwhome/home/oma_tunnus/public_html/cgi-bin/ties4080/venv/bin/python
- Virtuaaliympäristö suljetaan deactivate-komennolla, mutta älä sulje sitä vielä vaan kokeile ensin seuraavan hello-ohjelman toimintaa
- Luo haluamallasi editorilla hello.cgi-tiedosto ja tallenna se w:\cgi-bin\ties4080\-kansioon.
- Kopioi ohjelmasi pohjaksi seuraava koodi:
#!/home/oma_tunnus/public_html/cgi-bin/ties4080/venv/bin/python # -*- coding: utf-8 -*- print("""Content-type: text/plain; charset=UTF-8 Hello world! """)
-
Toimiakseen ohjelma vaatii suoritusoikeuden (execute) kaikille käyttäjille. Lisäksi toimiakseen CGI-ohjelmana ohjelman ryhmän (group) täytyy olla users
[tjlahton@halava cgi-bin]$ chmod a+x hello.cgi [tjlahton@halava cgi-bin]$ chgrp users hello.cgi [tjlahton@halava cgi-bin]$ ls -al hello.cgi -rwxr-xr-x. 1 tjlahton users 3777 Feb 5 09:59 hello.cgi
Cgi-ohjelmaan sekä samaan kansioon EI SAA olla kirjoitusoikeutta muilla kuin käyttäjällä itsellään.
Jos edellämainitut asiat ovat kunnossa voit kokeilla suorittaa ohjelmasi.
[tjlahton@jalava cgi-bin]$ python hello.cgi
Seuraava virhe tarkoittaa, että rivinvaihdot ovat DOS-tyyppisiä vaikka pitäisi olla Unix-tyyppisiä:
usr/bin/python^M: bad interpreter: No such file or directory
Varmista, että editorisi käyttää unix-tyylisiä rivinvaihtoja. Visual Studio Codessa rivinvaihto näkyy alalaidan statusbarissa joko muodossa CRLF tai LF. Oikea muoto on LF.
Varmista myös, että tiedostosi alussa ei ole UTF-8:aan liittyvää BOM-merkintää. Seuraava virhe yleensä vihjaa, että tiedoston alussa on BOM:
Exec format error. Binary file not executable.
less-komennolla on helppo tarkistaa onko tiedoston alussa BOM:
Saatuasi ohjelman toimimaan komentoriviltä voit kokeilla sitä myös WWW-selaimella osoitteesta:
http://users.jyu.fi/~omatunnus/cgi-bin/ties4080/hello.cgi
Jos ohjelma antaa selaimessa internal server error-virheilmoituksen, tarkista vielä kaikki edellämainitut asiat.
-
Valitettavasti users.jyu.fi-palvelimella ei voida suoraan ajaa Flaskia
vaan sen suorittaminen on kikkailtava CGI-ohjelmana.
Saatuasi hello-ohjelman toimimaan selaimessa voit jatkaa seuraavalla.
Luo kansioon uusi tiedosto flask.cgi ja kirjoita siihen seuraava ohjelmakoodi. Muista muuttaa ensimmäiselle riville oma käyttäjätunnuksesi.
#!/home/oma_tunnus/public_html/cgi-bin/ties4080/venv/bin/python # -*- coding: utf-8 -*- # suorittaa Flask-sovellukset CGI-ohjelmina users.jyu.fi-palvelimella import sys from wsgiref.handlers import CGIHandler from werkzeug.debug import DebuggedApplication try: #oma täytyy tässä kohdassa olla sama kuin oman flask-sovelluksen sisältävän #python-tiedoston nimi esim. oma.py from oma import app as application if __name__ == '__main__': handler = CGIHandler() application.debug = True handler.run(DebuggedApplication(application)) except: #koska tänne päädyttäessä ei werkzeug toimi täytyy itse tulostaa http-protokollan #edellyttämä otsake. STDOUT menee tässä tapauksessa suoraan selaimelle print("Content-Type: text/plain;charset=UTF-8\n") print("Syntaksivirhe:\n") for err in sys.exc_info(): print(err)
Tämä koodi vastaa sitä mitä PythonAnywhere loi sinulle WSGI-konfigurointitiedostoon. Oleellisin ero on, että tässä suoritetaan Flask-sovellus CGI-rajapinnan kautta.
- Luo samaan kansioon myös oma.py-tiedosto. Tiedoston nimen on oltava
tämä. Vrt. edellisessä koodissa olevaan riviin
from oma import app as application
. Kirjoita tiedostoon seuraava ohjelmakoodi:#!/wwwhome/home/oma_tunnus/public_html/cgi-bin/ties4080//venv/bin/python # edellisellä shebang-rivillä ei ole merkitystä nyt muuten kuin komentorivillä # suoritettaessa eikä oikeastaan silloinkaan, koska Flask-sovellusta ei voi kunnolla # suorittaa komentoriviltä halava/jalava-koneissa # -*- coding: utf-8 -*- from flask import Flask, request, Response import os app = Flask(__name__) # @app.route määrää mille osoitteille tämä funktio suoritetaan @app.route('/') def hello_world(): return Response("Hello World", content_type="text/plain; charset=UTF-8")
- Varmista, että tiedostojen ja kansioiden oikeudet ovat samat kuin aiemmin tehdyissä cgi-ohjelmissa.
- Kokeile sovellustasi osoitteessa http://users.jyu.fi/~omatunnus/cgi-bin/ties4080/flask.cgi/. Viimeinen /-merkki on olennainen!
- Jos sovelluksesi ei toimi, lue ohjetta eteenpäin ja kokeile omalle koneelle asennetulla Flaskilla
- Yleensä syntaksivirheet löytyvät, kun yrität suorittaa sovelluksesi komentoriviltä:
taipython flask.cgi
python oma.py
Sinun pitää suorittaa ohjelmasi virtualenvillä asentamallasi python-tulkilla. Sinun pitää siis ensin aktivoida virtuaaliympäristö ja sen jälkeen kokeilla ohjelmasi suorittamista.
Komentoriviltä suoritettuna flask-ohjelma ei voi toimia, koska se vaatii www-ympäristön. Komentoriviltä ajettuna saat kuitenkin kiinni koko sovelluksen kaatavat syntaksivirheet. Varsinaista Flask-palvelinta ei voi käynnistää halava/jalava-koneissa.
- Yleensä syntaksivirheet löytyvät, kun yrität suorittaa sovelluksesi komentoriviltä:
- WWW-palvelin suorittaa Flask-kehyksen ja oma.py-tiedostosta löytyy hello_world-funktio, joka on määrätty suoritettavaksi,
kun pyydetään sovelluksen juurikansiota (/). HTTP-protokollaan liittyviä asioita ei tarvitse itse tehdä vaan Flask hoitaa ne. Riittää, että
funktio palauttaa merkkijonon, jossa on sivulle haluttu sisältö.
- WWW-palvelin (users.jyu.fi) suorittaa flask.cgi-sovelluksen tiedoston ensimmäisellä rivillä kerrotulla ohjelmalla eli virtuaaliympäristön python-tulkilla #!/home/oma_tunnus/public_html/cgi-bin/ties4080/venv/bin/python
- Sovellus yrittää ladata oma.py-tiedoston (kirjaston) sisällön
(
from oma import app as application
). Jos ei onnistu eli kyseisessä tiedostossa on esim. syntaksivirheitä niin näytetään virhe sivulla - Käynnistetään
handler = CGIHandler()
. Suoritetaan CGIHandlerilla edellä ladattu oma sovellushandler.run(DebuggedApplication(application))
- oma.py-tiedostossa käynnistetään Flask.
app = Flask(__name__)
- Flask ottaa ohjat käsiinsä ja suorittaa
@app.route('/')
-merkinnän määräämän funktion elihello_world()
hello_world()
asettaa halutun mediatyypin ja palauttaa tekstin "Hello World"
- Jos haluat sovelluksesi tallentavan users.jyu.fi-palvelimella oleviin tiedostoihin, on sinun annettava kyseiseen tiedostoon kaikille käyttäjille kirjoitusoikeus. Users.jyu.fi:ssä on myös lisärajoitteita CGI-ohjelmille.
Flaskin debuggaus users.jyu.fi:ssä
Joskus täytyy itse logata virheet. Seuraava tekee lokitiedoston kansioon '../hidden/'. Korjaa polku oikeaksi:
import logging
logging.basicConfig(filename=os.path.abspath('../hidden/flask.log'),level=logging.DEBUG)
logging.debug("Tänne mitä haluaa lokiin kirjoittaa")
Älä kirjoita lokia cgi-bin-kansioon tai sen alikansioihin!. Tämä ei toimi.
Jotta lokista olisi iloa on käytettävä try..exceptiä. Nappaa exceptillä kiinni mahdolliset virheet ja dumppaa virheilmoitus lokiin:
try:
# sqlite haluaa absoluuttisen polun
con = sqlite3.connect(os.path.abspath('../hidden/resepti'))
except Exception as e:
logging.debug("Kanta ei aukea")
# sqliten antama virheilmoitus:
logging.debug(str(e))
Lokin sisältöä vo seurailla helpoiten ottamalla pääteyhteyden halavaan ja käyttämällä tail-komentoa:
tail -f flask.log
Tämä kyttää koko ajan tiedoston sisältöä ja näyttää heti sinne tulevat uudet rivit.
Järkevintä on debugata omalla koneella olevalla Flaskilla, koska siellä näkyy suoraan palvelimen virheloki.
Esimerkkejä
Python ja CGI-ohjelmointiluento (youtube, 2016)
Vanha Python ja CGI-ohjelmointiluento (youtube, 2014)
JSON, omat luokat, lomakkeen korvaaminen linkillä (Lähdekoodi, Template )
ostoskori.cgi (lähdekoodi, template)
Käyttäjien kommentit