3.8.2. WiTTFind-Web

This repository contains all exercises for DHd2020 Paderborn.

3.8.2.1. Themen

In diesem Teil des Workshops wird die Verbindung zwischen Client (Webseite im Browser) und einem Server (Pythonanwendung: Flask) beschrieben.

3.8.2.1.1. Typische Frameworks für Python Services

Alle der folgenden Frameworks haben eine Gemeinsamkeit. Es werden grundlegende Funktionalitäten zur Verfügung gestellt um Webanwendungen online zu stellen. Der Programmierer kann e.g Routen oder Schnittstellen zur Interaktion mit einer Datenbank verwenden und konfigurieren ohne diese von Grund auf selbst implementieren zu müssen.

  • Flask

  • Django

  • Bottle

  • Tornado

  • Pyramid

3.8.2.1.2. Flask Intro

Im Gegensatz zu den meisten anderen Frameworks verfolgt Flask eine “Do it yourself” Philosophie d.h. das lediglich essentielle Funktionalität mitgeliefert ist und der Rest dem Programmierer überlassen wird oder aber in anderen Erweiterungen (flask-cors, flask-sqlalchemy, …)

Import

from flask import Flask

Routing mit “Decorator” über einer Python Methode

@app.route('/something') # definition der Route - /something nach Base-URL ruft Methode auf
def something(): # Python Methoden Definition
    return 'something!'  # Python Code hier - typisches Output Format = JSON

3.8.2.1.3. AJAX

AJAX steht für Asynchronous JavaScript And XML und erlaubt es Webseiten “asynchron” Daten mit einem Web-server auszutauschen.\

asynchron weil der Datenverkehr das Skript nicht blockiert und im Hintergrund abläuft bei “success” also wenn die Antwort korrekt am Client angekommen ist wird dann dafür vorgesehener Code ausgeführt
Das Gegenteil wäre ein synchroner Datenaustausch der aber dann den Client zwingt auf eine Antwort zu warten - im Fall einer Webseite bedeutet das stillstand weil der Request solange blockiert bis die gesamte Antwort eingetroffen ist

3.8.2.1.4. GET und POST

  • $.get(): kodiert die Daten/Parameter direkt in der URL und ist damit begrenzt

Beispiel (offen ersichtlich z.b in der Adresszeile im Browser):

?vorname=Foo&nachname=Bar
  • $.post(): lädt die Daten im sogenannten “Body” der Anfrage und kann beliebig Größe haben\

GET ist also für die Übertragung weniger Information durchaus geeignet, aber stark beschränkt hinsichtlich der Größe der zu übertragenden Daten. Diese Art des Datenaustausch unterliegt diversen Beschränkungen von Webseiten, Browser, Server etc. und sollte eine Größe von etwa 2 Kilobyte nicht überschreiten.
(Neben Daten wird jedes Trennzeichen jeder Name von Variablen und Feldern und Bezeichner mit übertragen und zählt zu dem Limit)
POST sollte verwendet werden wenn die Datengröße nicht nach oben beschränkt ist zum Beispiel beim Übertragen eines Eingabetextes aus einer “TEXTAREA(HTML)”, dem Upload einer Datei oder wenn es sich um kritische Daten handelt (e.g. Formular Werte die nicht in der URL angezeigt werden sollen).

3.8.2.1.5. Vor- und Nachteile GET&POST

GET POST
PRO PRO
Einfache Übergabe von Variablen in Links unbegrenzte Übertragungsgröße
Webseite inkl. Variablen als Favorit speichern Übertragung von Dateien
CON CON
begrenzte Größe & beschränkt auf ASCII codes POST Werte nicht in URLs/Links speicherbar
keine Dateienübertragung

3.8.2.1.6. GET Beispiel

GET Request - alert Ergebnis

$.get("http://someHOST:somePORT", function(data){
  alert("Data: " + data);
});

3.8.2.1.7. POST Beispiel

POST Request

// snippet from W3: https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_ajax_post
// go to this website for interactive and more detailed example
$.post("demo_test_post.asp",
    {
      name: "Donald Duck",
      city: "Duckburg"
    },
    function(data,status){
      alert("Data: " + data + "\nStatus: " + status);
    });

3.8.2.1.8. Beispiel mit “success” und “error”

$.ajax({
    url: "http://someHOST:somePORT",
    type: "POST",
    crossDomain: true,
    data: yourData, 
    dataType: "json",
    success: function (response) {
        //code to execute when all went well
    },
    error: function (xhr, status) {
        //handle error here
    }
  });

3.8.2.2. Aufgabe 1

Herunterladen des HTML-Templates für die Übungswebseite: https://www.cis.uni-muenchen.de/kurse/max/wast/data/wittfind-wf-template.zip

3.8.2.3. Aufgabe 2

Schreiben Sie einen Server mit Flask der lokal auf Ihrem Rechner läuft. ( HOST:localhost, PORT:default - http://localhost:5000)

3.8.2.3.1. Aufgabe 2.1

Der Server soll eine Route “@app.route(‘/hello’)” enthalten welche bei Aufruf “Hello World” zurückgibt Optional Der Server definiert einen “@app.errorhandler(404)”

-> {“response” : “Hello, world!”}

3.8.2.3.2. Lösungsvorschlag

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask, jsonify, abort

app = Flask(__name__)

@app.route('/hello')
def hello_world():
    return 'Hello, world!' # Print 'Hello, world!' as the response body

@app.errorhandler(404)
def page_not_found():
    return 'This page does not exist! Try localhost:5000/hello', 404

if __name__ == "__main__":
    app.run()

3.8.2.4. Aufgabe 3

Schreiben Sie eine Javascript Funktion “sendRequest1()” in der Sie den für diese Aufgabe vorbereiteten Button (id=”aufgabe3-button”) einen Ajax-Request ausführen lassen.

3.8.2.4.1. Aufgabe 3.1

Die Funktion ist in der HTML-Datei vordefiniert (Zeile 32) und wird mit “onclick” von dem Button aufgerufen. Die Methode “mult” bildet das Produkt der beiden über Ajax gesendeten Argumente.

3.8.2.4.2. Flask Methode als Erweiterung

@app.route('/mult/<arg1>/<arg2>')
def mult(arg1, arg2):
    result = "NaN"
    try:
        result = int(arg1)*int(arg2)
    except:
        return 'Arguments incompatible with method. Result =' + result
    finally:
        return arg1 + ' * ' + arg2 + ' = ' + str(result)

3.8.2.4.3. Lösungsvorschlag

var num1 = $("#num1").val();
var num2 = $("#num2").val();
var host = "localhost"
var port = 5000
$.get("http://" + host + ":" + port + "/mult/" + num1 + "/" + num2, function (response) {
    alert(response);
});

3.8.2.5. Aufgabe 4

3.8.2.5.1. Aufgabe 4.1

Erstellen Sie eine weitere Route in der Flask-anwendung in der ein JSON-Objekt von der Webseite empfangen wird und die KEY-VALUE Paare vertauscht zurückgegeben werden.
Wichtig CORS soll unterstützt sein.
app = Flask(__name__)
CORS(app)

3.8.2.5.2. Aufgabe 4.2

Erweitern sie außerdem die Methode “sendRequest2()” in der “dhd.html” Datei um einen POST Request der das Objekt an den Server sendet. (Erstellen Sie ein Objekt wie unten beschrieben)

3.8.2.5.3. Beispiel

  • Input = {“key” : “test”, “key2” : “test2”, “key3” : “test3”}

  • Output = {“test” : “key”, “test2” : “key2”, “test3” : “key3”}

3.8.2.5.4. Lösung

Flask

var num1 = $("#num1").val();
var num2 = $("#num2").val();
var host = "localhost"
var port = 5000
$.get("http://" + host + ":" + port + "/mult/" + num1 + "/" + num2, function (response) {
    alert(response);
});

Javascript AJAX POST

@app.route('/keys', methods=['POST'])
def keys():
    response = {}
    try:
        json_data = request.get_json(force=True)
        for key, value in json_data.items():
            response[value] = key
    except Exception as e:
        abort(400, 'Unexpected Error occurred')
    return jsonify(response)

3.8.3. Flask

3.8.3.1. Was ist Flask?

Flask ist ein Micro-Webframework für Python und erspart dem Entwickler grundlegende Mechanismen selbst zu implementieren. Flask wird für die Programmierung von Wepapplikationen genutzt und besteht im Gegensatz zu anderen bekannenten Frameworks wie Django nur aus den elementaren Funktionen und Komponenten um eine Webanwendung online verfügbar zu machen.

3.8.3.2. Flask Installation

https://flask.palletsprojects.com/en/1.1.x/installation/
Wir arbeiten mit der empfohlenen Python Version - Python 3.5 oder neuer. Die Installation erfolgt über “pip”.

pip install flask
pip install flask_cors

3.8.3.2.1. Kurzbeschreibung CORS

https://developer.mozilla.org/de/docs/Web/HTTP/CORS
Cross-Origin Resource Sharing (CORS) ist ein Mechanismus, der zusätzliche HTTP Header verwendet um einem Browser mitzuteilen, dass er einer Webanwendung, die auf einer anderen Domain(Origin) läuft, die Berechtigung erteilt auf ausgewählte Ressourcen von einem Server eines anderen Ursprungs(Origin) zuzugreifen. Eine Webanwendung stellt eine cross-origin HTTP-Anfage, wenn sie eine Ressource anfordert, die einen anderen Ursprung(Domain, Protokoll und Port) hat, als ihren eigenen.

Aus Sicherheitsgründen beschränken Browser die aus Skripten heraus initiierten HTTP-Anfragen mit unterschiedlichen Herkunftsbezeichnungen. Beispielsweise folgen XMLHttpRequest und die Fetch-API der Richtlinie gleichen Ursprungs. Das bedeutet, dass eine Webanwendung, die diese APIs verwendet, nur HTTP-Ressourcen aus der gleichen Herkunft anfordern kann, aus der die Anwendung geladen wurde, es sei denn, die Antwort aus der anderen Herkunft enthält die richtigen CORS-Header.\

3.8.3.2.2. Warum Flask-CORS ?

Es handelt sich um eine Erweiterung für “Cross Origin Resource Sharing (CORS)” diese ermöglicht “cross-origin” AJAX requests.\

Flask mit default Einstellungen für CORS - unterstützt CORS in allen Routen

app = Flask(__name__)
cors = CORS(app)

@app.route("/")
def helloWorld():
  return "Hello, cross-origin-world!"

Erweiterung als einfacher “decorator” (@cross_origin()) nach der Definition einer Route.
Diese Verwendung führt dazu, dass genau diese Route CORS unterstützt.

@app.route("/")
@cross_origin() # decorator
def helloWorld():
  return "Hello, cross-origin-world!"

3.8.3.3. Flask Code Beispiel

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask, jsonify, abort
import json

app = Flask(__name__)

def load_json():
	# just a dict here actually
	data = {
	  "1-2": {"1:": "You did it!", "2:": {"3:": "You did it!", "4:": {"1:": "You did it!"}}},
	  "name": "John",
	  "age": 30,
	  "city": "New York"
	}
	return data

@app.route('/')
def hello_world():
    return 'Hello, world!'  # Print 'Hello, world!' as the response body

@app.route('/<arg1>/<arg2>')
def do_something(arg1, arg2):
    return 'Did something with ' + arg1 + ' & ' + arg2 + '! (Try localhost:5000/mult/<arg1>/<arg2>)'

@app.route('/mult/<arg1>/<arg2>')
def mult(arg1, arg2):
	result = "NaN"
	try:
		result = int(arg1)*int(arg2)
	except:
		pass
	finally:
		return arg1 + ' * ' + arg2 + ' = ' + str(result)
	
@app.route('/json/<arg1>/<arg2>')
def json(arg1, arg2):
    data = load_json()
    key = str(arg1) + "-" + str(arg2)
    if key in data:
        return jsonify(data[key])
    else:
        return abort(404)

@app.errorhandler(404)
def page_not_found():
    return 'This page does not exist! Try localhost:5000/json/1/2', 404

if __name__ == "__main__":
    app.run()