1/1

Python jako CGI

Tento text je mírně zastaralý. Takto se web dnes už nedělá. (viz článek: Jak napíšu webovou aplikaci v Pythonu?) Ale někdy se tento způsob tvorby webu může přece jen hodit. Můžete si tak vyzkoušet staré dobré CGI, které funguje téměř všude.

Obsah:

  1. Web server
  2. Python
  3. Formuláře
  4. GET a POST
  5. Python a CGI
  6. Malý příklad -- anketa

CGI je protokol pro propojení externích aplikací s webovým serverem. To serveru umožňuje delegovat požadavek od klienta na externí aplikaci, která dle požadavku vrátí výstup.

Prostě a jednoduše: Klient (prohlížeč) zašle web-serveru požadavek. Server spustí nějaký program nebo skript a jeho výstup zašle zpět klientovy.

Web server

Aby vše fungovalo je nutné nainstalovat a nastavit webový server. Nejčastěji používaným je Apache.

aptitude install apache2-mpm-prefork

Konfigurace se provádí v souboru /etc/apache2/sites-enabled/000-default.

aptitude install mc
mcedit /etc/apache2/sites-enabled/000-default

Pokud chcete využít pohodlí práce v GUI můžete udělat něco jako:

aptitude install gedit

a poté se přihlásíte přes ssh s parametrem -X

ssh -X jmeno@muj.stroj.tld gedit /etc/apache2/sites-enabled/000-default 

Grafická aplikace se vám poté zobrazí na lokálním display.

Provedeme konfiguraci podle návodu.

<Directory /var/www/>
    Options -Indexes FollowSymLinks MultiViews +ExecCGI
    AddHandler cgi-script .cgi
    AllowOverride None
    Order allow,deny
    allow from all
</Directory>

`--> stáhnout

Důležité je +ExecCGI a AddHandler cgi-script .cgi. Všechny soubory s příponou .cgi se budou spouštět a jejich výstup se odešle do webového prohlížeče.

Po změně konfigurace je třeba znovu přesvědčit webový server aby si konfiguraci znovu načetl.

service apache2 reload

Python

Přejdeme do adresáře /var/www:

cd /var/www

vytvoříme soubor index.cgi.

mcedit index.cgi

Základem každého skriptu je http hlavička. Minimální hlavička může vypadat např. takto:

Content-Type: text/html;
<prázdný řádek>

Prázdný řádek na konci je důležitý. Více o http můžete najít v článku List of HTTP header fields nebo Hypertext Transfer Protocol. Pokud se chcete podívat jak vypadá některá konkrétní http hlavička konkrétní srkánky poslouží vám dobře program [[!wkcz Telnet]] nebo web-sniffer.

Náš zdrojový kód potom může vypadat asi takto

#!/usr/bin/python
# -*- coding: utf8 -*-

import datetime

print "Content-Type: text/html; Charset=utf-8;\n"

print "<h1>Funguje mi to!!! Huráááá!!!</h1>"

td=datetime.datetime.today()

print "<p>Aktuální datum: {}. {}. {}</p>".format(td.day, td.month, td.year)
print "<p>Aktuální čas: {}:{}:{}</p>".format(td.hour, td.minute, td.second)

`--> stáhnout

Nakonec je ještě potřeba nastavit souboru práva pro spouštění.

chmod a+x index.cgi

Formuláře

Data lze stránce předat pomocí html formulářů. Jde tedy o html značky <form> a <input>.

Ano nebo ne

<form>
 <p>
    <input type="text" name="vstup" value="hodnota" />
    <input type="checkbox" name="anone" /> Ano nebo ne
 </p><p>
     <input type="submit" name="tlac" value="Ano"/>
     <input type="submit" name="tlac" value="Ne"/>
 </p>   
</form>

`--> stáhnout

GET a POST

GET a POST jsou dvě základní metody protokolu http. Metoda GET ukládá data to URL. Proto je následující heslo po stisknutí entru viditelné v adresním řádku prohlížeče.

<form method="get">
    <input type="password" name="vstup" value="" />
</form>

`--> stáhnout

Z toho důvodu data, která chceme skrýt nebo data, která jsou příliš objemná zasíláme pomocí metody POST.

<form method="post">
    <input type="password" name="vstup" value="" />
</form>

`--> stáhnout

Python a CGI

Python disponuje modulem CGI, pomocí kterého lze jednoduše přebírat parametry zaslané skriptu, bez ohledu na to, zda byla použita metoda GET nebo POST. Základem je objekt FieldStorage.

import cgi

print "Content-Type: text/html; Charset=utf-8;\n"

print "<h1>Funguje mi to!!! Huráááá!!!</h1>"

print """
<h1>Formulář, co ho odesílám</h1>
<form method="get">
 <p>
    <input type="text" name="vstup" value="hodnota" />
    <input type="checkbox" name="anone" /> Ano nebo ne
 </p><p>
    <input type="submit" name="odeslat" />
 </p>   
</form>

<h1>Data, která jsem dostal</h1>
"""

# načtu data odeslané pomocí formuláře
data = cgi.FieldStorage()

try:
    print "<p>vstup:",data["vstup"].value ,"</p>"
except:
    print """<p style="color:red;">Nedostal jsem data.</p>"""

# tento způsob je trošku bezpečnější...
print "<p>vstup:",data.getvalue('vstup') ,"</p>"

`--> stáhnout

Ladění

Ladění je trochu problematické, protože skript nespouštím já ale webový server. Když udělám ve skriptu syntaktickou chybu, v prohlížeči se dozvím jen, že jsem udělal chybu, ale už nevím kde.

Chybu bych měl odhalit poté, co spustím skript v příkazové řádce:

user@troj:~$ /var/www/index.cgi
  File "./index.cgi", line 7
    print "<h1>Můj data</h1>"
    ^
IndentationError: unexpected indent

Tento způsob nefunguje vždy dobře a někdy je potřeba se podívat do logu webového serveru. Zde dobře poslouží příkaz tail.

user@troj:/var/www$ tail /var/log/apache2/error.log 
[Tue Feb 12 09:57:25 2013] [error]  
[Tue Feb 12 09:57:25 2013] [error]      
[Tue Feb 12 09:57:25 2013] [error]  print "<h1>M\xc5\xafj data</h1>"
[Tue Feb 12 09:57:25 2013] [error]      
[Tue Feb 12 09:57:25 2013] [error]  ^
[Tue Feb 12 09:57:25 2013] [error]  IndentationError
[Tue Feb 12 09:57:25 2013] [error]  : 
[Tue Feb 12 09:57:25 2013] [error]  unexpected indent
[Tue Feb 12 09:57:25 2013] [error]  
[Tue Feb 12 09:57:25 2013] [error]  Premature end of script headers: index.cgi

Protože je toto všechno velmi složité přináší Python modul cgitb. Stačí vložit na začátek skriptu něco jako:

   1 #!/usr/bin/python
   2 # -*- coding: utf8 -*-
   3 import cgitb
   4 cgitb.enable()

`--> stáhnout

a naprostou většinu chyb by nemělo být problém vidět přímo ve webovém prohlížeči.

| navigace |

Malý příklad -- anketa

Vše shrneme do malého příkladu webové ankety:

Základem bude funkce anketa

def anketa(dotaz,moznosti,uloziste):

`--> stáhnout

anketa:
dotaz je text otázky.
možnosti je seznam řetězců různých možností odpovědi.
uloziste identifikátor určuje příslušnost dat ke konkrétní anketě.

Když ji potom budeme volat budeme psát něco jako :

anketa('Kokik hodin denně se učím?',
       ['jednu','dvě','24','neučím se'],
       'uceni')

`--> stáhnout

Základem bude výpis možností a bargraf. Chci aby funkce tiskla asi následující HTML.

<p>Kolik hodin denně se učím?</p>
<ul>
    <li><a href="?uloziste=uceni&volba=0">jednu</a><br/>
        <span style="display:block; width:100px; background-color:#aa99ee;">100</span>
        </li>
    <li><a href="?uloziste=uceni&volba=1">dvě</a><br/>
        <span style="display:block; width:50px; background-color:#aa99ee;">50</span>
        </li>
    <li><a href="?uloziste=uceni&volba=2">24</a><br/>
        <span style="display:block; width:200px; background-color:#aa99ee;">200</span>
        </li>
</ul>

`--> stáhnout

Kolik hodin denně se učím?

Bargraf je tvořen prvkem <span> o zadané šířce. Všimněte si také parametru href= u odkazů. Takto lze skriptu předat parametry pomocí metody GET.

Toto HTML musíme vytisknou v Pythonu. Pokud se vám nedaří rozluštit některou část kódu jako je .format 1, map 2 nebo enumerate podívejte se na tahák.

print "<p>{0}</p>".format(dotaz)
print "<ul>"
for i,m in enumerate(moznosti):
    print """<li><a href="?uloziste={0}&volba={1}">{2}</a><br/>
                <span style="display:block; 
                             width:{3}px; 
                             background-color:#aa99ee;">
                {4}</span></li>""".format(uloziste,i,m, 200, 0)
print "</ul>"

`--> stáhnout

Šířka bargrafu 200 a počet kliknutí 0 je zatím vložen přímo, ale bude posléze nahrazen hodnotou přečtenou z úložiště.

Jako úložiště nám bude sloužit textový soubor. (Stejně tak to může být id řádku tabulky v SQL databázi).

V pythonu potřebujeme zjisti jestli existuje soubor zadaného jména.

import os.path
if os.path.exists('jmeno souboru'):
    pass

`--> stáhnout

Pokud soubor existuje je třeba jej přečíst 3 a připravit na další zápis tj. zkrátit ho na nulovou délku. 4

Pokud souboru neexistuje, znamená to, že na anketu ještě nikdo neklikl a hodnoty všech bargrafů jsou 0. Soubor je ale třeba založit -- otevřít pro zápis.

Soubor musí být umístěn v adresáři, kam má web-server právo zápisu. Doporučuji pro tento účel zřídit samostatný adresář:

cd /var/www
mkdir write
chmod a+w write
jmeno = 'write/XartYTg_'+uloziste+'.txt'
if os.path.exists(jmeno):
    f = open(jmeno,"r+")
    data = f.readlines()
    data = map(int,data) # převedu řetězce na čísla
    f.truncate(0)
    f.seek(0)
else:
    data = map(lambda x: 0, range(len(moznosti)))
    f = open(jmeno,'w')

`--> stáhnout

Divné část jména souboru XartYTg je bezpečnost prvek, který má zamezit nepovolaným návštěvníkům našich stránek zkoumat soubory do kterých jim nic není.

Teď už si do seznamu odpovědí můžu nechat vypsat konkrétní hodnotu data[i].

            """.......{4}</span></li>""".format(uloziste,i,m, 200, data[i])

`--> stáhnout

Ještě před zapsáním dat je ale třeba zjisti, jestli uživatel neklikl na odkaz pro zvýšení počítadla:

form = cgi.FieldStorage()
formUloziste = form.getvalue('uloziste') 
formVoba = form.getvalue('volba')

`--> stáhnout

... pokud ano, navýším počítadlo ...

if formUloziste == uloziste and formVoba:
    data[int(formVoba)] += 1

`--> stáhnout

... zapíšu data a zavřu soubor.

data=map(lambda x: str(x)+'\n',data) # převedu čísla na řetězce a přidám konec řádku
f.writelines(data)
f.close()

`--> stáhnout

Zbývá ještě vyřešit proměnnou délku bargrafu. Ta by se dala počítat například takto:

delka = 200*data[i]/max(data)

`--> stáhnout

Nejvyšší bargraf by pak měl šířku 200px a ostatní úměrně ke své hodnotě. To ale nejde pokud je anketa na začátku prázdná a všechny hodnoty data[] jsou nulové. Nulou totiž dělit nejde. Kód bude jen maličko složitější:

mm = max(data) if max(data) != 0 else 1
#... a pak
delka = 200*data[i]/mm

`--> stáhnout

Taky by to ale chtělo dát do seznamu...

            """.......{4}</span></li>""".format(uloziste,i,m, delka, data[i])

`--> stáhnout

Funguje to?

| navigace |

Licence Creative Commons Valid XHTML 1.0 Strict Valid CSS! Antispam.er.cz Blog: Tlapicka.net