# WiTTFind-Web This repository contains all exercises for DHd2020 Paderborn. ## Themen In diesem Teil des Workshops wird die Verbindung zwischen Client (Webseite im Browser) und einem Server (Pythonanwendung: Flask) beschrieben. ### 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 ### 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 ```bash from flask import Flask ``` Routing mit "Decorator" über einer Python Methode ```bash @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 ``` ### 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 ### 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): ```bash ?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). ### 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` | | ### GET Beispiel GET Request - `alert` Ergebnis ```bash $.get("http://someHOST:somePORT", function(data){ alert("Data: " + data); }); ``` ### POST Beispiel POST Request ```bash // 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); }); ``` ### Beispiel mit "success" und "error" ```bash $.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 } }); ``` ## Aufgabe 1 Herunterladen des HTML-Templates für die Übungswebseite: https://www.cis.uni-muenchen.de/kurse/max/wast/data/wittfind-wf-template.zip ## Aufgabe 2 Schreiben Sie einen Server mit Flask der lokal auf Ihrem Rechner läuft. ( HOST:localhost, PORT:default - http://localhost:5000) ### 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!"} ### Lösungsvorschlag ```bash #!/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() ``` ## 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. ### 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. ### Flask Methode als Erweiterung ```bash @app.route('/mult//') 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) ``` ### Lösungsvorschlag ```bash 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); }); ``` ## Aufgabe 4 ### 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)` ### 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) ### Beispiel * Input = {"key" : "test", "key2" : "test2", "key3" : "test3"} * Output = {"test" : "key", "test2" : "key2", "test3" : "key3"} ### Lösung Flask ```bash 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 ```bash @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) ``` # Flask ## 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. ## Flask Installation [https://flask.palletsprojects.com/en/1.1.x/installation/](url)\ Wir arbeiten mit der empfohlenen Python Version - Python 3.5 oder neuer. Die Installation erfolgt über "pip". ```bash pip install flask pip install flask_cors ``` ### Kurzbeschreibung CORS [https://developer.mozilla.org/de/docs/Web/HTTP/CORS](url)\ 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.\ ### 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 ```bash 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. ```bash @app.route("/") @cross_origin() # decorator def helloWorld(): return "Hello, cross-origin-world!" ``` ## Flask Code Beispiel ```bash #!/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('//') def do_something(arg1, arg2): return 'Did something with ' + arg1 + ' & ' + arg2 + '! (Try localhost:5000/mult//)' @app.route('/mult//') def mult(arg1, arg2): result = "NaN" try: result = int(arg1)*int(arg2) except: pass finally: return arg1 + ' * ' + arg2 + ' = ' + str(result) @app.route('/json//') 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() ```