Bottle
Bottle ist ein Webframework, das leicht mit pip installiert werden kann.
Installation
- Linux, MacOS:
pip3 install bottle
- Windows:
pip install bottle
oderpython -m pip install bottle
oderpy -m pip install bottle
Einfacher Webserver
Für jeden Pfad, der aufgerufen werden kann, muss eine Route in Bottle deklariert werden. Diese Methode muss einfaches HTML an den Browser liefern.
import bottle
Wir beginnen mit der Route für /
.
@bottle.route("/") # bindung route to a method
def index():
return "OK"
Zum Starten des Webservers wird schließlich die Methode bottle.run
verwendet.
Neben dem Parameter für den host
und port
gibt es die Option reloader=True
.
Sie startet den Server automatisch neu, sobald sich eine der Python-Dateien geändert
hat. Bei der Entwicklung ist dies besonders hilfreich.
bottle.run(host="127.0.0.1", port=8081)
Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8081/
Hit Ctrl-C to quit.
127.0.0.1 - - [30/Sep/2018 09:30:53] "GET / HTTP/1.1" 200 2
Unter http://127.0.0.1:8081 ist die Seite nun erreichbar. Ein Blick in den Quelltext zeigt, dass der Text OK
genau so auftaucht, wie er in der Methode angelegt wurde.
HTML
Unser Browser kann jedoch mehr als nur Text anzeigen. Er kann HTML interpretieren. Passen wir die Methode also an und lassen valides HTML zurückgeben.
@bottle.route("/")
def index():
html = """
<!DOCTYPE html>
<html>
<head><title>Bottle Demo</title></head>
<body>
<h1>Bottle-Demo</h1>
</body>
</html>
"""
return html
bottle.run(host="127.0.0.1", port=8081)
Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8081/
Hit Ctrl-C to quit.
127.0.0.1 - - [30/Sep/2018 09:31:00] "GET / HTTP/1.1" 200 182
Dynamische Webseiten
Nun soll die Seite etwas dynamischer werden und einen Zeitstempel ausgeben, der sich bei jedem Aufruf ändert.
import time
@bottle.route("/")
def index():
html = """
<!DOCTYPE html>
<html>
<head><title>Bottle Demo</title></head>
<body>
<h1>Bottle-Demo</h1>
{sekseit1970} Sekunden seit dem 1.1.1970.
</body>
</html>
"""
# return HTML but replace variable in string before
t = time.time()
return html.format(sekseit1970=t)
Wenn wir den Server nun starten, wird bei jedem Aufruf der Zeitstempel im Ergebnis ersetzt.
bottle.run(host="127.0.0.1", port=8081)
Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8081/
Hit Ctrl-C to quit.
127.0.0.1 - - [30/Sep/2018 09:31:06] "GET / HTTP/1.1" 200 242
Bilder ausgeben
Schließlich soll noch ein Bild auf der Webseite erscheinen. Wir legen das folgende Bild neben die Python-Datei.
@bottle.route("/")
def index():
html = """
<!DOCTYPE html>
<html>
<head><title>Bottle Demo</title></head>
<body>
<h1>Bottle-Demo</h1>
<img src="/img/ball.gif" />
</body>
</html>
"""
return html
In einem ersten Schritt schlägt die Anzeige jedoch fehl. Der Statuscode 404 hinter /img/ball.gif
weist auf eine fehlende Route für das Bild hin.
bottle.run(host="127.0.0.1", port=8081)
Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8081/
Hit Ctrl-C to quit.
127.0.0.1 - - [30/Sep/2018 09:35:42] "GET / HTTP/1.1" 200 222
127.0.0.1 - - [30/Sep/2018 09:35:42] "GET /img/ball.gif HTTP/1.1" 404 744
In dem HTML-Dokument taucht eine Route /img/
auf, für die bisher noch
keine Methode angegeben wurde. Dies holen wir nun nach. Diese Methode
soll jedoch kein HTML, sondern eine Bilddatei /img/ball.gif
liefern.
Hierfür können wir die Methode static_file
im bottle-Modul verwenden. Mit root
wird das Wurzelverzeichnis für Bilder angegeben - in diesem Falle mit .
das aktuelle Verzeichnis.
@bottle.route("/img/ball.gif")
def image():
return bottle.static_file(filename="ball.gif", root=".")
Wir testen auch diese Methode einmal und rufen sie direkt auf. Es fällt auf, dass nun kein HTML, sondern eine Binärdatei ausgeliefert wird.
image()
Content-Type: image/gif
Content-Length: 5015
Last-Modified: Thu, 09 Aug 2018 17:57:30 GMT
Accept-Ranges: bytes
Starten wir den Webserver nun erneut.
bottle.run(host="127.0.0.1", port=8081)
Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8081/
Hit Ctrl-C to quit.
127.0.0.1 - - [30/Sep/2018 09:31:44] "GET / HTTP/1.1" 200 222
Dynamische Routen
Da es umständlich ist, für jedes Bild eine eigene Route festzulegen, können Routen auch dynamisch sein und einen Parameter enthalten - z.B. einen Dateiname für ein Bild. Dieser Parameter taucht dann auch in der Methode auf.
@bottle.route("/img/<dateiname>") # dynamic route
def image(dateiname):
return bottle.static_file(filename=dateiname, # parameter from method
root=".")
Die Parameter von dynamischen Routen können sogar an einen Datentyp gebunden werden. Dies wird in der Dokumentation von Bottle zu “Request Routing” beschrieben.
Formulare
In HTML können mit Formularen Daten an den Webserver übertragen werden. Hierfür wird das form-tag genutzt. Das action
-Attribut gibt das Ziel des Formulares an, mit method
kann die HTTP-Methode für die Übertragung gesetzt werden.
@bottle.route("/formular")
def fomular():
return """
<!DOCTYPE html>
<html>
<head><title>Bottle Demo</title></head>
<body>
<h1>Bottle-Formular</h1>
<form action="/formularauswerter" method="POST">
<input type="textfield" name="eingabefeld"><br>
<input type="submit" value="Absenden">
</form>
</body>
</html>
"""
Das Formular leitet an die Route /formularauswerter
weiter. Und zwar mit einem POST
-Request. Hierfür muss noch eine Methode angegeben werden. Diesmal wird bottle.post
als Dekorator verwendet.
@bottle.post("/formularauswerter")
def fomularauswerter():
html = """
<!DOCTYPE html>
<html>
<head><title>Bottle Demo</title></head>
<body>
<h1>Bottle-Formular-Auswerter</h1>
Es wurde "{req}" eingegeben.
</body>
</html>
"""
formulareingabe = bottle.request.forms["eingabefeld"]
return html.format(req=formulareingabe)
bottle.run(host="127.0.0.1", port=8081)
Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8081/
Hit Ctrl-C to quit.
127.0.0.1 - - [30/Sep/2018 09:32:01] "GET /formular HTTP/1.1" 200 348
127.0.0.1 - - [30/Sep/2018 09:32:05] "POST /formularauswerter HTTP/1.1" 200 199
Nun ist das Formular unter http://localhost:8081/formular erreichbar.
Templates
Damit der HTML-Quelltext nicht mit dem Python-Quelltext verschmischt wird, trennt man beide. Dadurch können Änderungen am Design durchgeführt werden, ohne den Quelltext dafür ändern zu müssen.
Templates werden im Ordner views
abgelegt. Dort legen wir folgende einfache HTML-Datei index.tpl
ab. Sie enthält den Platzhalter ``, der noch ersetzt werden muss.
<!DOCTYPE html>
<html>
<head><title>Bottle Demo</title></head>
<body>
<h1>Bottle-Template</h1>
Hallo
</body>
</html>
Das Template kann nun angezeigt und die Variable mein_text
darin ersetzt werden. Dies erledigt die Methode template
.
@bottle.route('/')
def index():
return bottle.template('index', mein_text='Welt')
Nach einem Start wird das Template angezeigt und die Variable darin ersetzt.
bottle.run(host='127.0.0.1', port=8081)
Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8083/
Hit Ctrl-C to quit.
127.0.0.1 - - [14/Nov/2018 08:58:36] "GET / HTTP/1.1" 200 154
127.0.0.1 - - [14/Nov/2018 08:58:37] "GET /favicon.ico HTTP/1.1" 404 742
Templates mit Kontrollstrukturen
Innerhalb des Templates können nicht nur Variablen angezeigt, sondern auch Kontrollstrukturen wie Schleifen verwendet werden. Das ist besonders nützlich, wenn eine Liste mit Werten angezeigt werden soll.
Das nächste Beispiel zeigt ein Template, in dem eine Liste durchlaufen wird. Hierfür wird Python-Quelltext mit einem vorangestellten %
im Python-Quelltext eingetragen. Da die sonst bei Python wichtige Einrückung im Template keine Rolle spielt, muss das Ende der Schleife mit einem end
gekennzeichnet werden.
<!DOCTYPE html>
<html>
<head><title>Bottle Demo</title></head>
<body>
<h1>Bottle-Template</h1>
Eine Liste mit Elementen
<ul>
% for eintrag in meine_liste:
<li></li>
% end
</ul>
</body>
</html>
Beim Aufruf der Webseite wird nun eine Liste übergeben.
@bottle.route('/')
def index():
return bottle.template('index',
meine_liste=["eins", "zwei", "drei"])
bottle.run(host='127.0.0.1', port=8081)
Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://127.0.0.1:8082/
Hit Ctrl-C to quit.
127.0.0.1 - - [14/Nov/2018 09:18:29] "GET / HTTP/1.1" 200 275
127.0.0.1 - - [14/Nov/2018 09:18:29] "GET /favicon.ico HTTP/1.1" 404 742
Cheat-Sheet
In einem Cheat-Sheet werden die wichtigsten Dinge zusammenfasst.