1/1

Bottle.py web framework

Obsah:

  1. Začínáme s Bottle
  2. Routing
  3. Zpracování dat z formulářů
  4. Statické soubory
  5. Šablony
  6. Chybové stránky
  7. Přesměrování
  8. HTTP hlavička posílaná klientem
  9. HTTP hlavička odpovědi a výchozí kódování
  10. WSGI/CGI prostředí
  11. Cookies
  12. Další odkazy na dokumentaci
  13. Odkazy

V dnešní době je nespočet technologií, které lze použít pro vytváření dynamických webových stránek. Jednou z možností je použít programovací jazyk Python. Vývoj webových aplikací v holém Pythonu by ale byl příliš složitý a pracný. Proto se většinou používají frameworky, které celou věc zjednoduší a umožňují aby se programátor soustředil na to podstatné.

Pro Python existuje web-frameworků celá řada. Za zmínku jistě stojí

Pokud si chcete udělat přehled, jaké možnosti python má určitě si přečtěte článek Znám jen PHP. Jak napíšu webovou aplikaci v Pythonu?.

Začínáme s Bottle

My se seznámíme s framewokem Bottle. Ten je maximálně jednoduchý a k ničemu nás nenutí. První, malá, nic moc nedělající stránka/aplikace může vypadat například takto:

#!/usr/bin/python
# -*- coding: utf8 -*-
# Soubor:  hello.py
# Datum:   07.02.2014 10:26
# Autor:   Marek Nožka, nozka <@t> spseol <d.t> cz
# Licence: GNU/GPL 
# Úloha:   Hello World Bottle 
############################################################################

from bottle import route, run

@route('/')
@route('/ahoj')
def hello():
    return """<h1>Hurá Hurá!</h1>
<p>Funguje to!</p>"""

run(host='localhost', port=8090, debug=True, reloader=True)

`--> stáhnout

Vytvořený soubor uložíme na disk a spustíme. Spustí se tak malý vývojový web-server, který naslouchá na portu 8090.

$ python hello.py

Nyní se na náš první výtvor můžeme podívat we webovém prohlížeči na adrese http://localhost:8090/. Pokud v souboru uděláme změny okamžitě se promítnou bez toho aniž bychom aplikaci (web-server) ukončovali a znovu spouštěli.

Úkol

  1. Udělejte ve zdrojovém kódu syntaktickou chyby. Například a=8+'8' a podívejte se jak na to spuštěná aplikace (web-server) zareaguje.
  2. Otestujte adresu http://localhost:8090/nejaka_blbost
  3. Otestujte adresu http://localhost:8090/ahoj

Routing

Každé adrese URL lze přiřadit kód, který se má provést.

   1 #!/usr/bin/python
   2 # -*- coding: utf8 -*-
   3 # Soubor:  name.py
   4 # Datum:   07.02.2014 11:12
   5 # Autor:   Marek Nožka, nozka <@t> spseol <d.t> cz
   6 # Licence: GNU/GPL 
   7 # Úloha:   Hello World Bottle +
   8 ############################################################################
   9 
  10 from bottle import route, run
  11 import datetime
  12 
  13 @route('/')
  14 @route('/ahoj')
  15 def funguje():
  16     return """<h1>Hurá Hurá!</h1>
  17 <p>Funguje to!</p>"""
  18 
  19 @route('/cas')
  20 def cas():
  21     q='<h1>Aktuální čas</h1>'
  22 
  23     td=datetime.datetime.today()
  24 
  25     q+="<p>Aktuální datum: {}. {}. {}</p>".format(td.day, td.month, td.year)
  26     q+="<p>Aktuální čas: {}:{}:{}</p>".format(td.hour, td.minute, td.second)
  27 
  28     return q
  29 
  30 @route('/ahoj/<name>')
  31 def ahoj(name):    
  32     return "<h1>Ahoj {0}</h1><p>Funguje mi to! Hurá! Huráááááá.</p>".format(name)
  33 
  34 @route('/nasobeni/<a>/<b>')
  35 @route('/<a>*<b>')
  36 def nasobeni(a,b):
  37     return str(float(a)*float(b))
  38 
  39 @route('/mocnina/<a:float>/<b:int>')
  40 @route('/<a:float>^<b:int>')
  41 def nocnina(a,b):
  42     vysledek=a**b
  43     return str(vysledek)
  44 
  45 ##########################################################################
  46 run(host='localhost', port=8090, debug=True, reloader=True)

`--> stáhnout

Úkol

Vyzkoušejte si následující URL a prostudujte zdrojový kód, který je obhospodařuje.

  1. http://localhost:8090/ahoj
  2. http://localhost:8090/ahoj/Karle
  3. http://localhost:8090/ahoj/Tondo
  4. http://localhost:8090/cas
  5. http://localhost:8090/cas/blbost
  6. http://localhost:8090/nasobeni/4/7
  7. http://localhost:8090/4*7
  8. http://localhost:8090/4*abc
  9. http://localhost:8090/4^abc
  10. http://localhost:8090/mocnina/4/abc
  11. http://localhost:8090/4^3
  12. http://localhost:8090/mocnina/4/3

Více o routingu se dozvíte v dokumentaci: http://bottlepy.org/docs/dev/routing.html

Zpracování dat z formulářů

Existuje více možností jak přistupovat k datům z formulářů. Záleží na tom, která metoda je použita (GET, POST). Více informací naleznete v dokumentaci:

Atribut GET POST File Uploads
request.query yes no no
request.forms no yes no
request.files no no yes
request.params yes yes no
request.GET yes no no
request.POST no yes yes
   1 #!/usr/bin/python
   2 # -*- coding: utf8 -*-
   3 # Soubor:  forms.py
   4 # Datum:   07.02.2014 11:59
   5 # Autor:   Marek Nožka, nozka <@t> spseol <d.t> cz
   6 # Licence: GNU/GPL 
   7 # Úloha:   Bottle a formuláře
   8 ############################################################################
   9 
  10 from bottle import run, route, get, post, request, redirect
  11 import subprocess
  12 
  13 # přesměrování
  14 @route('/')
  15 def redir():
  16     redirect("/login")
  17 
  18 @get('/login') # nebo @route('/login')
  19 def login():
  20     print '---------------------'
  21     print request.method
  22     print '---------------------'
  23     return '''
  24         <form action="/login" method="post">
  25             Username: <input name="username" type="text" />
  26             Password: <input name="password" type="password" />
  27             <input value="Login" type="submit" />
  28         </form>
  29     '''
  30 
  31 @post('/login') # nebo @route('/login', method='POST')
  32 def do_login():
  33     print '---------------------'
  34     print request.method
  35 #    print dir(request.params)
  36     for klic in  request.params.keys():
  37         print klic,'->',request.params[klic]
  38     print '---------------------'
  39     username = request.forms.get('username')
  40     password = request.forms.get('password')
  41     if 'abcd' in password :
  42         return """
  43     <h1>{0}</h1>
  44     <p>Hurá! Huráááá. Uhodl jsi</p>
  45     """.format(username)
  46     else :
  47         return "<h1>Ne! Ne! Ne!!!</h1><p>Špatné heslo :-P</p>"
  48 
  49 
  50 @route('/pwgen')
  51 def pwgen():
  52     h='<h1>Zapamatovatelné heslo?</h1>\n'
  53     form='''<form method="get">
  54 <p>
  55     Délka hesla (od 5 do 40): <input name="length" type="text" /><br />
  56     <input value="goo" type="submit" />
  57 </p>
  58 </form>
  59 '''
  60 
  61     length = request.query.length
  62     if length :
  63         try :
  64             length=int(length)
  65             length= 8 if length<5 or length>40 else length
  66         except :
  67             length=8
  68         pswd= subprocess.check_output(['pwgen','-Ccn',str(length), ])
  69         return h+'\n<p>Délka '+str(length)+'</p>\n<pre style="font-size:large;">\n'+pswd+'</pre>\n'+form
  70     else : 
  71         return h+form
  72 
  73 ##########################################################################
  74 run(host='localhost', port=8090, debug=True, reloader=True)

`--> stáhnout

Úkol

Opět vyzkoušejte jednotlivá URL a prozkoumejte zdrojový kód k nim příslušející.

| navigace |

Statické soubory

Webové aplikace napsané v Pythonu mají sklon „vlastnit“ celý adresář (nebo i celou doménu). Přiřazování konkrétních URL určitému kódu je tedy o něco pružnější a je obvykle spravováno tzv. routingem1.

Někdy se ale přece jen hodí mít některé části webu umístěny ve statických souborech. Typickým příkladem můžou být obrázky nebo CSS. V těchto případech použijeme následující kód.

from bottle import static_file
@route('/static/<filename>')
def server_static(filename):
    return static_file(filename, root='/path/to/your/static/files')

`--> stáhnout

nebo lépe:

@route('/static/<filepath:path>')
def server_static(filepath):
    return static_file(filepath, root='/path/to/your/static/files')

`--> stáhnout


Funkce static_file() je obecně doporučovanou cestou pro předávání statický souborů. Funkce může automaticky odhadnout MIME daného souboru. Ale můžeme to i přímo určit.

from bottle import static_file
@route('/images/<filename:re:.*\.png>')
def send_image(filename):
    return static_file(filename, root='/path/to/image/files', mimetype='image/png')

@route('/static/<filename:path>')
def send_static(filename):
    return static_file(filename, root='/path/to/static/files')

`--> stáhnout

Prohlížeče většinu souboru jejichž typ znají zobrazí. Je to například PDF nebo JPEG. Dialog pro stažení lze vynutit pomocí parametru download. Ten může obsahuje jméno souboru. Pokud se rozhodneme ho nechat stejné jako na serveru stačí hodnotu nastavit na True.

@route('/download/<filename:path>')
def download(filename):
    return static_file(filename, root='/path/to/static/files', download=filename)

`--> stáhnout

Šablony

Vytvářet stránku vždy přímo v obslužné funkci by bylo hodně nepohodlné. Proto Bottle přicházíjednoduchým šablonovacím systémem. Pokud ale tento vestavěný šablonovací systém nedostačuje je možné použít robustnější řešení jako je mako, jinja2 nebo cheetah.

Bottle šablony hledá v adresáři ./views/ (respektive v seznamu bottle.TEMPLATE_PATH).

Šablona může vypadat například takto:

%if name == 'World':
    <h1>Hello {{name}}!</h1>
    <p>This is a test.</p>
%else:
    <h1>Hello {{name.title()}}!</h1>
    <p>How are you?</p>
%end

`--> stáhnout

A následující kód slouží pro použití šablony.

@route('/hello')
@route('/hello/<name>')
def hello(name='World'):
    return template('hello_template', name=name)

`--> stáhnout

Alternativní způsob rendrování šablony je decorator @view. Obslužná funkce potom musí vracet dict s proměnnými a jejich hodnotami.

@route('/hello')
@route('/hello/<name>')
@view('hello_template')
def hello(name='World'):
    return dict(name=name)

`--> stáhnout

Chybové stránky

Pokud se něco pokazí Bottle automaticky zobrazí chybovou stránky. Chybovou stránku si ale můžeme pro konkrétní HTTP status code vytvořit sami.

Například status 404 říká, že dokument nelze nalézt:

from bottle import error
@error(404)
def error404(error):
    return 'Nic tu není, jdi pryč'

`--> stáhnout

Pokud z nějakého důvodu chceme chybový stav programově vyvolat poslouží nám k tomu funkce abort().

from bottle import route, abort
@route('/restricted')
def restricted():
    abort(401, "Promiň, ale sem nesmíš.")

`--> stáhnout

Příklad

@error(404)
def notFound(error):
    r='<h1>'+error.status+'</h1>'
    r+='<p>Sorry. Tady nic není</p><hr />'
    r+='<p>'+error.body+'</p>'
    return r

@route('/nic')
def nic():
    abort(404,'bbbbeeeeeeeeeee')

`--> stáhnout

Vyzkoušejte si rozdíl v chování pro adresu /nic a /nejakablbost_nmfioewkwfjweioq

Přesměrování

Funkce redirect() vyvolá status 303 See Other a přesměruje požadavek na jinou stránku.

from bottle import redirect
@route('/spatna/url')
def wrong():
    redirect("/spravna/url")

`--> stáhnout

HTTP hlavička posílaná klientem

Všechny HTTP hlavičky zaslané klientem (např. Referer, Agent or Accept-Language) jsou dostupné ve slovníkovém objektu request.headers.

@get('/req')
def req():
    r='<pre>'
    for k in request.headers.keys() :
        r += k+':'+request.headers[k]+'\n'
    r+='</pre>'
    return r

`--> stáhnout

HTTP hlavička odpovědi a výchozí kódování

Pokud chceme zasáhnou do HTTP hlavičky, kterou naše aplikace odesílá, použijeme objekt Response.

@route('/wiki/<page>')
def wiki(page):
    response.set_header('Content-Language', 'en')
    ...

`--> stáhnout

Pokud má hlavička obsahovat více stejnojmenných polí použijeme kromě metody .set_header() také metodu add_header.().

....
response.set_header('Set-Cookie', 'name=value')
response.add_header('Set-Cookie', 'name2=value2')
....

`--> stáhnout

Výchozí kódování

Změna výchozího kódování se děje pomocí pole Content-Type. Výchozí hodnotu:

Content-Type: text/html; charset=UTF-8

... je možné změnit pomocí Response.content_type nebo přímo pomocí Response.charset:

from bottle import response
@route('/iso')
def get_iso():
    response.charset = 'ISO-8859-15'
    return u'This will be sent with ISO-8859-15 encoding.'

@route('/latin9')
def get_latin():
    response.content_type = 'text/html; charset=latin9'
    return u'ISO-8859-15 is also known as latin9.'

`--> stáhnout

WSGI/CGI prostředí

Některé informace se WSGI/CGI programu předávájí pomocí proměnných prostředí. Je to například REQUEST_METHOD nebo REMOTE_ADDR.

@route('/my_ip')
def show_ip():
    ip = request.environ.get('REMOTE_ADDR')
    # or ip = request.get('REMOTE_ADDR')
    # or ip = request['REMOTE_ADDR']
    return template("Your IP is: {{ip}}", ip=ip)

`--> stáhnout

nebo

@get('/env')
def env():
    r='<pre>'
    for k in request.environ.keys() :
        r += '<strong>'+k+'</strong>:'+str(request.environ[k])+'\n\n'
    r+='</pre>'
    return r

`--> stáhnout

Cookies

Cookie je pojmenovaný kus textu uložené ve webovém prohlížeče. Cookie si můžeme vyžádat přes request.get_cookie() a nastavit nové cookies pomocí response.set_cookie():

@route('/hello')
def hello_again():
    if request.get_cookie("visited"):
        return "Vítej zpět! Je jezké, že jsi zase přišel"
    else:
        response.set_cookie("visited", "yes")
        return "Nazdar! Vítam tě tu."

`--> stáhnout

Metoda response.set_cookie() přijímá řadu dalších pojmenovaných argumentů, které řídí život a chování souborů cookie. Některé z nejběžnějších nastavení jsou popsány v následující tabulce:

max_age    Platnost cookie v sekundách. (default: None)
expires Expirace: datetime objekt nebo UNIX timestamp. (default: None)
domain Doména pro kterou cookie platí (default: aktuální doména).
path Limituje cookie pro určitou cestu (default: /).
secure Limituje cookie pro HTTPS spojení (default: off).
httponly Zabrání Jabascriptu na straně klienta ve čtení cookie (default: off).

Pokud není nastaven expires ani max_age, cookie vyprší na konci relace prohlížeče.

Dále byste měli vzít v úvahu:

| navigace |


Další odkazy na dokumentaci

| navigace |


Odkazy

| navigace |

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