Jython HomeDruckenJava-Online

HTTP Server

Das im TigerJython integrierte Modul HTTPServer ermöglicht es, einen einfachen Webserver zu programmieren. Dieser wartet auf einem bestimmten Port (default 80) auf einen Client. Der Server kann einen ankommenden HTTP GET-Request abarbeiten und einen HTTP-Response erzeugen. Auf dem Client wird irgendein Webbrowser verwendet, es ist also keine zusätzliche Programmierung nötig und es kann ein beliebiger Computer oder irgendein Smartphone verwendet werden. HTTPServer ermöglicht es beispielsweise, ferngesteuerte Turtlegrafik-Applikationen zu erstellen.

Beispiel 1: Die Farbe einer Figur ferngesteuert ändern.
Das Programm wird mit TigerJython in einem Turtlefenster ausgeführt. Es enthält aber einen HTML-Text, der dem Browser des Clients übermittelt und dort angezeigt wird.

 

Der Webserver wird automatisch beim Erzeugen der Instanz server der Klasse HTTPServer gestartet. requestHandler() ist eine Callbackfunktion, die automatisch bei einem eingehenden GET-Request aufgerufen wird. Sie hat drei Parameter clientIP, filename, params. clientIP liefert die IP-Adresse des Clients, filename ist der Name der im GET Request angeforderten Datei und in params werden die Parameter des GET-requests geliefert, z. B. für eine im Browser eingegebene URL:

http://192.168.0.103/index.html?color="red"
 
|
|
|
 
clientIP
filename
params

params ist ein Tupel, das die URL-Parameter als Tupel in der Form (key, value) abgibt. Mit dieser Information wird im Turtlefenster ein Stern gezeichnet und dem Browser des Clients wird als HTTP-Response die HTML-Seite zurückgeschickt (return html, None,  dieser Rückgabewert wird später erläutert). Die TCP-Verbindung wird nach dem Absenden des Responses vom Server geschlossen.

Der Webserver muss durch Aufruf von server.terminate() beendet werden, damit er den Port wieder freigibt. Dazu registriert man den Callback onCloseKlicked, der beim Schliessen des Turtlefensters aufgerufen wird. Falls das Programm z.B. wegen Programmfehlern nicht zu Ende läuft, kann der Serverport besetzt bleiben und beim nächsten Programmstart erscheint die Fehlermeldung Port 80 already in use. In diesem Fall muss TigerJython neu gestartet werden.

# WebServer1.py

from tcpcom import HTTPServer
from gturtle import *

html = """<!DOCTYPE html>
<html>
  <head> <title>Turtle Remote</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
     <h1>Turtle Controller</h1>
     <b>Press to change the color:</b><br><br>
     <form method="get">
       <input type="submit" style="font-size:40px; height:60px; width:150px" name="c" value="red"/>
       <input type="submit" style="font-size:40px; height:60px; width:150px" name="c" value="green"/>
     </form>   
  </body>
</html>
"""

def requestHandler(clientIP, filename, params):        
    if len(params) > 0:
        color = params[0][1]
        star(color)
    return html,None 

def star(color):
    setFillColor(color)
    setPenColor(color)
    startPath()
    repeat 5:
        forward(200)
        left(144)
    fillPath() 
    
def onCloseClicked():
    server.terminate()
    dispose()      
          
makeTurtle(closeClicked = onCloseClicked)
ht()
server = HTTPServer(requestHandler)
setTitle("Server listening at " + server.getServerIP())
star("red")
  
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Erklärungen zum Programmcode:

meta name="viewport"... : Passt die Darstellung der Webseite der Breite des Gerätes an. So erscheint die Seite auch auf den Smartphones korrekt
input type="submit" : Erzeugt eine Schaltfläche
params[0][1]: params werden als ein Tupel mit dem Format: ((param_key1, param_value1), (param_key2, param_value2), ...) vermittelt.
?color="red" liefert: params[0][0] = color, params[0][1] = "red"
server.getServerIP() : Die IP-Adresse des Webservers wird in der Titelleiste angezeigt
Die Portnummer kann auch geändert werden, z.B. mit server = HTTPServer(requestHandler, port=81). Der Client muss dann die Webseite mit 192.168.21.12:81 aufrufen.
Zum Testen kann TigerJython und Browser auf dem gleichen Computer gestartet werden. In diesem Fall kann man im Browser statt der IP-Adresse auch nur localhost eingeben.

 

Beispiel 2: Turtle mit Buttons auf Smartphone oder einem beliebigen Computer remote steuern

Bei der Programmausführung wird der Webserver gestartet und seine IP-Adresse ist in der Titelleiste ersichtlich. Wenn ein Client im Browser diese IP-Adresse (URL) wählt, wird er mit dem Server verbunden und erhält als HTTP-Response eine Webseite mit Buttons, mit welchen er die Bewegungsrichtung der Turtle ändern kann. Wie im oberen Beispiel wird bei einem eingehendem GET-Request die Callbackfunktion requestHandler(aufgerufen. Der Parameter params[0][1] liefert den Wert des gedrückten Buttons (d.h. "north", "west", "south". "east" oder "stop") und dieser wird in der Variablen state gespeichert. Im Hautprogramm wird in einer endlosen while-Schleife alle 10 Millisekunden der state abgefragt und die Bewegungsrichtung entsprechend geändert.

Durch Schliessen des Turtlefensters wird sowohl die while-Schleife als auch der Webserver beendet (server.terminate()).

# WebServer2.py

from tcpcom import HTTPServer
from gturtle import *

html = """<!DOCTYPE html>
<html>
  <head> <title>Turtle Remote</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <h2>Turtle Controller</h2>
    <b>Press to change the direction:</b>
    <form method="get">
    <table>
    <tr>
      <td>&nbsp;</td>
      <td><input type="submit" style="font-size:24px; height:50px; width:75px" name="btn" value="north"/></td>
      <td>&nbsp;</td>
    </tr>
    <tr>
     <td><input type="submit" style="font-size:24px; height:50px; width:75px" name="btn" value="west"/></td>
     <td><input type="submit" style="font-size:24px; height:50px; width:75px" name="btn" value="stop"/></td>
     <td><input type="submit" style="font-size:24px; height:50px; width:75px" name="btn" value="east"/></td>
    </tr>
    <tr>
     <td>&nbsp;</td>
     <td><input type="submit" style="font-size:24px; height:50px; width:75px" name="btn" value="south"/></td>
     <td>&nbsp;</td>
    </tr>
    </table>
    </form>
  </body>
</html>
"""

def requestHandler(clientIP, filename, params):
    global state     
    if len(params) > 0:
        state = params[0][1]                          
    return html, None        
    
def onCloseClicked():
    server.terminate()
    dispose()

makeTurtle("sprites/beetle.gif", closeClicked = onCloseClicked)
setPos(0, -200)
speed(-1)
server = HTTPServer(requestHandler)
setTitle("Server listening at " + server.getServerIP())
state = 'stop' 
while not isDisposed():
    if state == 'north':
       setHeading(0)     
    elif state == 'west':
       setHeading(-90) 
    elif state == 'south':
       setHeading(180)
    elif state == 'east':
       setHeading(90)          
    if state != 'stop':
       forward(2)        
    delay(10)     
    
   
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Erklärungen zum Programmcode:

makeTurtle("sprites/beetle.gif") : das Turtle-Bild festlegen
global state : da der Wert der Variable state in der Funktion requestHandler() ändert, muss sie als global definiert werden
state=params[0][1]: liefert "north", "south", "east", "west", oder "stop"
while not isDisposed(): solange das Turtlefenster nicht geschlossen wird, werden die Schleifenbefehle ausgeführt
if state != 'stop': forward(2): die Turtle bewegt sich immer vorwärts, ausser wenn der Button 'stop' gedrückt wurde
delay(10): wartet jeweils 10 Millisekunden, damit das System nicht mit ständigen state-Abfragen überbelastet wird


Beispiel 3: Mit Smartphone Auto auf einer Rennbahn steuern

Wie in den vorhergehenden Beispielen, wird der Webserver automatisch bei der Programmausführung gestartet.   Im Turtle-Fenster erscheint eine Rennbahn und ein Autot. Der Client gibt die IP-Adresse des Webservers im Browser ein und erhält eine Webseite mit den Steuerbuttons. Das Ziel ist es, das Auto durch die Rennbahn zu steuern. Zum Testen kann TigerJython und Browser auch auf dem gleichen Computer gestartet werden. In diesem Fall ist der Webserver auch als localhost erreichbar.

# WebServer3.py

from tcpcom import HTTPServer
from gturtle import *

html = """<!DOCTYPE html>
<html>
  <head> <title>Turtle Remote</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <h2>Turtle Controller</h2>
    <b>Press to change the direction:</b>
    <form method="get">
    <table>
    <tr>
      <td>&nbsp;</td>
      <td><input type="submit" style="font-size:24px; height:50px; width:100px" name="btn" value="forward"/></td>
      <td>&nbsp;</td>
    </tr>
    <tr>
      <td><input type="submit" style="font-size:24px; height:50px; width:100px" name="btn" value="left"/></td>
      <td><input type="submit" style="font-size:24px; height:50px; width:100px" name="btn" value="stop"/></td>
      <td><input type="submit" style="font-size:24px; height:50px; width:100px" name="btn" value="right"/></td>
    </tr>
    <tr>
      <td>&nbsp;</td>
      <td><input type="submit" style="font-size: 24px; height: 50px; width: 100px" name="btn" value="back" /></td>
      <td>&nbsp;</td>
      </tr>
     </table>
     </form>
  </body>
</html>
"""

def requestHandler(clientIP, filename, params):
    global state 
    if len(params) > 0:
        state = params[0][1]                          
    return html, None         
    
def onCloseClicked():
    server.terminate()
    dispose()

makeTurtle("sprites/tinycar.png", closeClicked = onCloseClicked)
drawImage("sprites/racetrack.gif")
setPos(160, -200)
speed(25)
server = HTTPServer(requestHandler)
setTitle("Server listening at " + server.getServerIP())
state = 'stop'
while not isDisposed():
    if state == 'forward':      
       forward(2)
    elif state == 'left':
       leftArc(25, 5)
       forward(2)
    elif state == "right":
       rightArc(25, 5)
       forward(2)
    elif state == 'back':
       back(2)      
    else:
        delay(50)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Erklärungen zum Programmcode:

drawImage("sprites/racetrack.gif"): stellt das Bild der Rennbahn im Turtlefenster dar. Das Bild ist in der Bilderbibliothek der TigerJython IDE integriert. Eigene Bilder speichert man am besten im gleichen Verzeichnis, in dem sich das Programm befindet.
while not isDisposed(): die While-Schleife wird beim Schliessen des Turtlefensters beendet
leftArc(30, 4): bewegt Turtle auf einem Linksbogen mit dem Radius 30 und Sektor-Winkel

 

Beispiel 4: Sparsäuli
Im Unterschied zu den vorhergehenden Beispielen werden hier auch Text-Informationen, die im Response vom Webserver zum Client geschickt werden, im Browserfenster angezeigt. Verschiedene Clients können mit Klick auf die Schaltflächen CH 1 bzw. CH 2 Geld spenden. Die Spende wird jeweils auf dem Webserver im Sparsäuli abgelegt und zum bereits vorhandenen Spargeld addiert. Der aktuelle Betrag im Sparsäuli wird als HTTP-Response an der Client zurückgemeldet und im Browser angezeigt.

Webserver
 
Client(Browser)
 

 


# PiggyBank.py

from tcpcom import HTTPServer
from gturtle import *

html = """<!DOCTYPE html>
<html>
  <head> <title>Turtle Donation</title> 
  <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body> 
    <h2>Your Turtle Donation</h2>
    <b>Press a button to make a donation for the lucky turtle</b>
    <form method="get">
    <input type="submit" style="font-size:50px; height:100px; width:150px" name="donation" value="CHF 1"/>
    <input type="submit" style="font-size:50px; height:100px; width:150px" name="donation" value="CHF 2"/>
    </form>
    <br>
    %s<br>
    Current amount in my piggy bank: %s CHF<br>
  </body>
</html>
"""

thanks = "<b>Thank you for your donation!</b>"

def stateHandler():
    if gift != 0:
        animate(gift)

def requestHandler(clientIP, filename, params):
    global balance, gift
    gift = 0
    if len(params) > 0: 
        value = params[0][1]
        if value == 'CHF+1':
            gift = 1
            balance += gift
        elif value == 'CHF+2':
            gift = 2
            balance += gift
        text = html % (thanks, balance)
    else:
        text = html % ("", balance)
    return text, stateHandler

def onCloseClicked():
    server.terminate()
    dispose()

def animate(gift):
    for y in range(100, -100, -2):
       setPos(0, y)
       if gift == 1:
            drawImage("sprites/onefranc.png")
       else:
            drawImage("sprites/twofranc.png")
       setPos(0, -150)             
       drawImage("sprites/piggybank.png")
       repaint()
       delay(30)
       clear()

makeTurtle(closeClicked = onCloseClicked)
ht()
speed(-1)
balance = 0
gift = 0
server = HTTPServer(requestHandler)
setTitle("Server listening at " + server.getServerIP())
setPos(0, -150)             
drawImage("sprites/piggybank.png")
enableRepaint(False)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

Erklärungen zum Programmcode:

enableRepaint(False): das automatische Rendern wird deaktiviert, um das Flackern zu vermeiden. Die Bilder bleiben im Bildbuffer und werden erst beim Aufruf von repaint() auf dem Bildschirm gerendert.
text = html % (thanks, balance) : Inhalt der Variable text wird an den Client als HTTP-Response zurückgeschickt. Es werden Formatstrings verwendet.
animate(gift): bewegt die Münze
return text, stateHandler: da die Bewegung der Münze ein länger dauernder Prozess ist, darf dieser nicht im Callback requestHandler() ausgeführt werden, da sonst der Browser den Response nicht rechtzeitig erhält. Gibt man im Rückgabewert (statt wie bisher None) eine Funktion stateHandler an, so wird diese NACH dem Response aufgerufen. Dort kann die länger laufende Animation ablaufen.

 


Aufgaben Serie 20

1)

Die Turtle soll mit einer im Browser laufenden Applikation über das Internet mit einem Smartphone wie folgt gesteuert werden:



Wenn man auf den Button forward klickt, bewegt sich die Turtle 20 Schritte vorwärts, mit Klick auf den Button left dreht sie 90° nach links und mit Klick auf right dreht sie 90° nach rechts.

 


2)

Ergänzen Sie das Programm aus dem Beispiel 3 so, dass eine Rückmeldung im Browserfenster erscheint,. wenn das Auto in die schwarze Randfarbe hinein fährt. Versuchen Sie dann, den Parcours "blind" abzufahren, d.h. ohne die Bewegung des Autos direkt zu sehen. Hinweis:

In der Turtle-Dokumentation findet man die Befehle:

getPixelColor(): gibt die Farbe des Pixels an der Turtle-Position zurück
getPixelColorStr(): gibt die Hintergrundfarbe als String zurück (z.B. "white", "black" usw.)