Para este proyecto aparte de implementar un editor de videojuegos, también ha sido necesaria la implementación de un servidor que pueda gestionar todos los datos que maneje el editor. Para el servidor he decidido usar un framework llamado flask explicado en uno de los puntos anteriores. Es un framework muy sencillo, que se programa en python, de utilizar y por eso en este punto explicaré su uso como si fuera un tutorial, porque lo implementado en este proyecto sirve para gestionar todo tipo de aplicaciones.
Primero empiezo explicando los requisitos que debemos cumplir para hacer que nuestro pc sea capaz de ejecutar este servidor. Necesitamos tener instalado en nuestro sistema, python y flask,
Una vez instalados python y flask, solo tenemos que ejecutar el archivo en el que tengamos programado el servidor de la siguiente forma:
A continuación explicaré las funciones que he tenido que implementar para mi proyecto y como las he implementado. Introduciré todo el código necesario para hacer que este servidor funcione correctamente.
6.2.7.1. Flask
Para que el servidor arranque satisfactoriamente deberemos tener una función “main”, que puede estar vacía porque es lo que se ejecutará continuamente.
Primero importamos las librerías necesarias para poder recibir archivos y peticiones.
from flask import Flask from flask import json from flask import jsonify import os
import xml
from xml.dom.minidom import Document
from xml.dom.minidom import parse, parseString from flask import request, redirect, url_for from werkzeug import secure_filename
Después declaramos nuestras variables de entorno que necesitemos.
path = os.path.dirname(__file__) + "\..\pfcHTML5\public_html" UPLOAD_FOLDER = path+'\js\images'
UPLOAD_MAP = path+'\js\maps'
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif','json'])
A continuación configuramos el servidor con las rutas que necesitemos acceder.
app = Flask(__name__, static_folder=path, static_url_path="") app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['UPLOAD_MAP'] = UPLOAD_MAP
Definimos a qué directorio nos enviará el servidor cuando escribamos una dirección raíz como por ejemplo: http://www.ejemplo.com, en este caso he decidido que el servidor redireccione al usuario a index de mi aplicación que es la pantalla principal del editor.
@app.route("/", methods=['PUT', 'GET']) def hello():
if request.method == 'GET': return redirect('index.html') elif request.method == 'PUT':
return "Error, no es posible enviar una petición PUT a esta dirección"
def allowed_file(filename): return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
Creamos la función main que se ejecutará cuando el servidor se inicie. En este caso tengo el modo debug activado para consultar los mensajes.
if __name__ == "__main__":
app.run(debug = True, host = "0.0.0.0")
La siguiente función sirve para actualizar todo tipo de xml, en este caso busca en el servidor un xml con el nombre “imagenes.xml”, lo lee y le introduce un nuevo campo, en este caso al campo le llamo foto porque mi editor trabaja con imágenes pero puede utilizarse cualquier nomenclatura.
def actuXML(nombreFoto):
fname = path + "\js\images\imagenes.xml" file = open(fname, 'r')
file.close()
datasource = open(fname)
doc = parse(datasource) # parse an open file top_element = doc.documentElement text = doc.createElement("foto") a = doc.createTextNode(nombreFoto) text.appendChild(a) #text.setAttribute("id", "miid") #foto = doc.createElement("foto") #foto.appendChild(a) #text.appendChild(foto) top_element.appendChild(text) file = open(fname, 'w') #file.write(doc.toprettyxml()) file.write(doc.toxml()) #import pdb; pdb.set_trace() return doc.toxml()
A partir de aquí explicaré las funciones más concretas de mi proyecto pero no obstante son reusables para cualquier tipo de programa que trabaje con comunicación continua con servidor. 6.2.7.2. Get XML
La función get xml la utiliza el editor cada vez que se ejecuta o actualiza el explorador de archivos para ver el estado actual del explorador de archivos. Primero le indicamos la dirección en la que el editor tendrá que llamar para obtener esta información, en este caso la dirección sería http://www.ejemplo.com/get/xml, y el servidor envía al cliente el xml que hay en su ubicación: path + "\js\images\imagenes.xml".
@app.route("/get/xml") def aaa():
fname = path + "\js\images\imagenes.xml" datasource = open(fname)
doc = parse(datasource) return doc.toxml()
6.2.7.3. Set XML
La función set xml en el caso de mi servidor lo que hace es crear una nueva carpeta para el explorador de archivos del editor. Internamente lo que hace el servidor es leer el xml actual introducir una nueva entrada en la jerarquía de carpetas con una nueva carpeta y devolverle el xml al editor.
@app.route("/set/xml", methods=['POST']) def bbb():
fname = path + "\js\images\imagenes.xml" file = open(fname, 'r')
data = file.read() file.close()
datasource = open(fname)
doc = parse(datasource) # parse an open file top_element = doc.documentElement text = doc.createElement("carpeta") text.setAttribute("id", "miid") top_element.appendChild(text) file = open(fname, 'w') #file.write(doc.toprettyxml()) file.write(doc.toxml()) #import pdb; pdb.set_trace() return doc.toxml() 6.2.7.4. Actualizar mapa
Como he comentado anteriormente el editor tiene una manera de comunicarse automáticamente con el motor. Para comunicarse con el game engine, editor y motor deben acceder al mismo escenario. Lo que hacen internamente es enviar el nuevo escenario por parte del editor, eliminar el mapa anterior por parte del servidor y guardar el nuevo mapa por parte del servidor también, finalmente el servidor guarda la actualización y contesta al editor, el editor llamará al game engine pidiéndole que ejecute el nuevo mapa.
@app.route('/guardaMapa', methods=['POST']) def upload_map():
if request.method == 'POST':
fname = UPLOAD_MAP + "\mapaTEST.json" file = open(fname, 'w')
file.write(request.data) return "OK"
6.2.7.5. Upload
El servidor tiene la opción de recibir imágenes desde cualquier parte del mundo y almacenarlas, para después poder proporcionarselas a todos los usuarios. Para eso el servidor espera que la función de llamada utilice el método POST, en caso de no ser así no se ejecutará la subida del archivo.
Una vez recibido el archivo que el editor nos ha enviado, llamaremos la función “actuXML” que actualizará el XML que utiliza el servidor con la nueva información que es el nombre de la foto recibida y su dirección en el servidor, finalmente retornará este nuevo XML para que el editor pueda actualizar su explorador de archivos con la nueva imagen.
@app.route('/upload', methods=['POST']) def upload_file():
if request.method == 'POST': file = request.files['file']
if file and allowed_file(file.filename): filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return actuXML('./js/images/' + filename)
6.2.7.6. CrossDomain
Cross Domain es un seguro de dominios cruzados que necesita el navegador para permitir el envío de información de un servidor a un cliente y viceversa. Por eso necesita saber que el servidor dispone de un cross domain.
Una solución de dominios cruzados es un medio de aseguramiento de la información que proporciona la capacidad de acceder de forma manual, de forma automática o mediante una transferencia entre dos o más dominios diferentes. Se trata de sistemas de hardware y software que permiten la transferencia de información entre integrados dominios incompatibles en este caso entre python y javascript del navegador. Cross domain es distinto de los enfoques más rigurosos, porque es compatible con la transferencia que de otro modo sería excluida por los modelos establecidos de los ordenadores, la red y la seguridad de datos, por ejemplo, el modelo Bell-LaPadula y modelo Clark-Wilson.
@app.route("/crossdomain.xml") def cors():
xml = """<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain- policy.dtd">
<cross-domain-policy>
<!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html --> <!-- Most restrictive policy: -->
<!-- <site-control permitted-cross-domain-policies="none"/>--> <!-- Least restrictive policy: -->
<site-control permitted-cross-domain-policies="all"/> <allow-access-from domain="*" to-ports="*" secure="false"/>
<allow-http-request-headers-from domain="*" headers="*" secure="false"/> </cross-domain-policy>
""" return xml