2008-12-03 21 views
35

Quiero crear una GUI basada en HTML/AJAX muy simple para un programa de Python. Entonces, la interfaz es una página HTML que se comunica con el programa a través de AJAX. ¿Me puede dar una implementación mínima para el lado del servidor usando el pitón SimpleHTTPServer.SimpleHTTPRequestHandler?¿Cómo implementar un servidor mínimo para AJAX en Python?

Un ejemplo simple sería un campo de texto y un botón. Cuando se presiona el botón, el contenido del campo se envía al servidor, que luego envía una respuesta correspondiente. Soy consciente de que hay muchas soluciones potentes para esto en Python, pero me gustaría mantener esto muy simple. Ya encontré algunos buenos ejemplos para un servidor de este tipo (por ejemplo, here), pero hasta ahora no he podido encontrar uno realmente mínimo.

En caso de que se pregunte por qué deseo implementar la GUI de esta manera: mi enfoque para esta aplicación es mostrar muchos datos en un diseño agradable con una interacción mínima, por lo que el uso de HTML + CSS parece más conveniente (y Ya lo he estado usando para visualización de datos no interactiva).

Respuesta

48

O.K., creo que ahora puedo responder mi propia pregunta. Aquí hay una implementación de ejemplo para calcular el cuadrado de un número en el servidor. Por favor, avíseme si hay mejoras o conceptos erróneos.

el archivo del servidor de pitón:

import threading 
import webbrowser 
import BaseHTTPServer 
import SimpleHTTPServer 

FILE = 'frontend.html' 
PORT = 8080 


class TestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): 
    """The test example handler.""" 

    def do_POST(self): 
     """Handle a post request by returning the square of the number.""" 
     length = int(self.headers.getheader('content-length'))   
     data_string = self.rfile.read(length) 
     try: 
      result = int(data_string) ** 2 
     except: 
      result = 'error' 
     self.wfile.write(result) 


def open_browser(): 
    """Start a browser after waiting for half a second.""" 
    def _open_browser(): 
     webbrowser.open('http://localhost:%s/%s' % (PORT, FILE)) 
    thread = threading.Timer(0.5, _open_browser) 
    thread.start() 

def start_server(): 
    """Start the server.""" 
    server_address = ("", PORT) 
    server = BaseHTTPServer.HTTPServer(server_address, TestHandler) 
    server.serve_forever() 

if __name__ == "__main__": 
    open_browser() 
    start_server() 

... y el archivo HTML (yo lo llamo 'frontend.html', por desgracia, el nombre tiene que aparecer en el código JavaScript también):

<html> 
<head> 
<title>AJAX test</title> 
</head> 
<body> 
<script type="text/javascript"> 

function xml_http_post(url, data, callback) { 
    var req = false; 
    try { 
     // Firefox, Opera 8.0+, Safari 
     req = new XMLHttpRequest(); 
    } 
    catch (e) { 
     // Internet Explorer 
     try { 
      req = new ActiveXObject("Msxml2.XMLHTTP"); 
     } 
     catch (e) { 
      try { 
       req = new ActiveXObject("Microsoft.XMLHTTP"); 
      } 
      catch (e) { 
       alert("Your browser does not support AJAX!"); 
       return false; 
      } 
     } 
    } 
    req.open("POST", url, true); 
    req.onreadystatechange = function() { 
     if (req.readyState == 4) { 
      callback(req); 
     } 
    } 
    req.send(data); 
} 

function test_button() { 
    var data = document.test_form.test_text.value;   
    xml_http_post("frontend.html", data, test_handle) 
} 

function test_handle(req) { 
    var elem = document.getElementById('test_result') 
    elem.innerHTML = req.responseText 
} 

</script> 

<form name=test_form> 
sqr(
<input type="text" name="test_text" value="0" size="4"> 
) = 
<span id="test_result">0</span> 
<input type=button onClick="test_button();" value="start" title="start"> 
</form> 

</body> 
</html> 

Por supuesto, sería mucho más conveniente usar jQuery para la solicitud XML, pero en aras de la simplicidad lo dejo así.

Finalmente una implementación alternativa el uso de WSGI (por desgracia no vi una forma de caer de nuevo en el controlador de servidor de archivos estándar si la solicitud no es un POST):

import threading 
import webbrowser 
from wsgiref.simple_server import make_server 

FILE = 'frontend.html' 
PORT = 8080 

def test_app(environ, start_response): 
    if environ['REQUEST_METHOD'] == 'POST': 
     try: 
      request_body_size = int(environ['CONTENT_LENGTH']) 
      request_body = environ['wsgi.input'].read(request_body_size) 
     except (TypeError, ValueError): 
      request_body = "0" 
     try: 
      response_body = str(int(request_body) ** 2) 
     except: 
      response_body = "error" 
     status = '200 OK' 
     headers = [('Content-type', 'text/plain')] 
     start_response(status, headers) 
     return [response_body] 
    else: 
     response_body = open(FILE).read() 
     status = '200 OK' 
     headers = [('Content-type', 'text/html'), 
        ('Content-Length', str(len(response_body)))] 
     start_response(status, headers) 
     return [response_body] 

def open_browser(): 
    """Start a browser after waiting for half a second.""" 
    def _open_browser(): 
     webbrowser.open('http://localhost:%s/%s' % (PORT, FILE)) 
    thread = threading.Timer(0.5, _open_browser) 
    thread.start() 

def start_server(): 
    """Start the server.""" 
    httpd = make_server("", PORT, test_app) 
    httpd.serve_forever() 

if __name__ == "__main__": 
    open_browser() 
    start_server() 
+2

Solo como comparación, aquí hay un ejemplo de Ramaze: http://news.ycombinator.com/item?id=383960 – jfs

+0

"¿hay alguna forma de recurrir al controlador estándar de Python si la solicitud no es un POST?" No significa mucho. ¿Cuál crees que es el controlador "estándar"? –

+0

S.Lott: en la primera implementación del servidor, el comportamiento de SimpleHTTPRequestHandler solo se cambia para las solicitudes POST. Cargando el archivo HTML por lo tanto no requiere ningún código adicional. En la implementación de WSGI tengo que enviar explícitamente el HTML, el GET no se maneja automáticamente. – nikow

9

Utilice WSGI reference implementation. A la larga, serás más feliz.

from wsgiref.simple_server import make_server, demo_app 

httpd = make_server('', 8000, demo_app) 
print "Serving HTTP on port 8000..." 

# Respond to requests until process is killed 
httpd.serve_forever() 

La demo_app es relativamente fácil de escribir; maneja sus solicitudes de Ajax.

+0

Que ks, finalmente logré construir un ejemplo simple (ver mi respuesta a continuación). – nikow

+0

Suena interesante. ¿Podría explicar por qué es preferible al enfoque 'BaseHTTPServer'? – FriendFX

0

Gracias por un muy intuitivo @nikow ejemplo yo estaba tratando de seguir su ejemplo, pero se hizo un error:

(proceso: 10281): GLib-CRITICAL **: g_slice_set_config: la afirmación de sys_page_size == 0 'no

modifiqué tu código para cumplir m y necesita

webbrowser.open('file:///home/jon/workspace/webpages/frontend_example/%s' % FILE) 
// skipped the port part 
httpd = make_server("", 8080, test_app) 
// hardcoded it here. 

¿Mi archivo html debe colocarse en el servidor web? ¡No lo he puesto todavía!

+0

Sí, en mi ejemplo, el servidor web sirve el archivo, por lo que debe estar disponible allí. – nikow

0

Aquí está un ejemplo sencillo para Python 3 basado en el ejemplo de @ nikow

Sé que esto puede tener errores, comentar lo que son si los encuentra.

El código envía la cadena "que le envié este mensaje" cuando se hace clic en Ejecutar, Python responde con "lo tengo"

código HTML

(vas a tener que utilizar la consola JS para esto)

<body> 
<button id="runButton">Run</button> 
<script type="text/javascript"> 
function xml_http_post(url, data) { 
var req = new XMLHttpRequest(); 
req.open("POST", url, true); 
req.onreadystatechange = function() { 
    if (req.readyState == 4) { 
    console.log(req.responseText); 
    } 
} 
req.send(data); 
} 

function runbuttonfunc() { 
    xml_http_post("frontend.html", "I sent you this message") 
} 

document.getElementById("runButton").onclick = runbuttonfunc; 
</script> 
</body> 
Código Python

: importación http.server

FILE = 'frontend.html' 
PORT = 8000 


class TestHandler(http.server.SimpleHTTPRequestHandler): 
    """The test example handler.""" 

    def do_POST(self): 
     """Handle a post request by returning the square of the number.""" 
     print(self.headers) 
     length = int(self.headers.get_all('content-length')[0]) 
     print(self.headers.get_all('content-length')) 
     data_string = self.rfile.read(length) 
     print(data_string) 
     self.send_response(200) 
     self.send_header("Content-type", "text/plain") 
     self.end_headers() 
     self.flush_headers() 
     self.wfile.write("I got it!".encode()) 


def start_server(): 
    """Start the server.""" 
    server_address = ("", PORT) 
    server = http.server.HTTPServer(server_address, TestHandler) 
    server.serve_forever() 

start_server() 
Cuestiones relacionadas