2008-12-03 14 views
35

Chcę utworzyć bardzo prosty GUI oparty na HTML/AJAX dla programu w języku Python. Tak więc frontend jest stroną HTML, która komunikuje się z programem za pośrednictwem AJAX. Czy możesz podać mi minimalną implementację po stronie serwera przy użyciu pythona SimpleHTTPServer.SimpleHTTPRequestHandler?Jak zaimplementować minimalny serwer dla AJAX w Pythonie?

Prostym przykładem może być pole tekstowe i przycisk. Po naciśnięciu przycisku zawartość pola jest wysyłana do serwera, który następnie odsyła odpowiedź. Mam świadomość, że w Pythonie jest wiele potężnych rozwiązań, ale chciałbym zachować to bardzo proste. Znalazłem już kilka ładnych przykładów takiego serwera (np. here), ale do tej pory nie mogłem wymyślić naprawdę minimalnego.

Jeśli zastanawiasz się, dlaczego chcę zaimplementować GUI w taki sposób: Moim celem dla tej aplikacji jest wyświetlanie dużej ilości danych w ładnym układzie z minimalną interakcją - więc używanie HTML + CSS wydaje się najbardziej wygodne (i Używam go już do nieinteraktywnego wyświetlania danych).

Odpowiedz

48

O.K., myślę, że mogę teraz odpowiedzieć na własne pytanie. Oto przykładowa implementacja do obliczania kwadratu liczby na serwerze. Daj mi znać, jeśli pojawią się jakieś ulepszenia lub nieporozumienia.

plik serwera python:

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() 

... i plik HTML (ja to nazywam 'frontend.html', niestety nazwa ma się pojawiać w kodzie JavaScript również):

<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> 

Oczywiście byłoby znacznie wygodniej użyć jQuery dla żądania XML, ale w interesie prostoty zostawię to w ten sposób.

Wreszcie alternatywa realizacja przy użyciu WSGI (niestety nie widziałem sposobu, by się znowu na standardowej obsługi plików stażem, jeżeli wniosek nie jest 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

Dla porównania, oto przykład Ramaze: http://news.ycombinator.com/item?id=383960 – jfs

+0

"czy istnieje sposób, aby powrócić do standardowego programu obsługi Pythona, jeśli żądanie nie jest POST?" Nie znaczy wiele. Jak myślisz, czym jest "standardowy" handler? –

+0

S.Lott: W pierwszej implementacji serwera zachowanie SimpleHTTPRequestHandler zmienia się tylko dla żądań POST. W związku z tym ładowanie pliku HTML nie wymaga dodatkowego kodu. W implementacji WSGI muszę jawnie wysłać kod HTML, GET nie jest obsługiwany automatycznie. – nikow

9

Użyj WSGI reference implementation. W dłuższej perspektywie będziesz szczęśliwszy.

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() 

Demo_app jest stosunkowo łatwy do napisania; obsługuje twoje żądania Ajax.

+0

niż ks, w końcu udało mi się zbudować prosty przykład (zobacz moją odpowiedź poniżej). – nikow

+0

Brzmi interesująco. Czy możesz wyjaśnić, dlaczego jest to lepsze niż podejście "BaseHTTPServer"? – FriendFX

0

Dzięki za bardzo intuicyjny przykładem @nikow starałem się podążać za przykładem, ale pojawia się błąd:

(proces: 10281): GLib znaczeniu krytycznym **: g_slice_set_config: twierdzenie 'sys_page_size == 0 Zmarnowana

zmodyfikowałem twój kod do spełnienia m y potrzeby.

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

Czy mój plik HTML musi zostać umieszczony na serwerze WWW? Nie umieściłem tego jeszcze!

+0

Tak, w moim przykładzie plik jest obsługiwany przez serwer WWW, więc musi być tam dostępny. – nikow

0

Oto prosty przykład dla Pythona 3 oparty na przykład @ nikow za

wiem, może to mieć błędy, komentarz co to jest, jeśli można je znaleźć.

Kod wysyła ciąg „Wysłałem Ci wiadomość” po kliknięciu przycisku Uruchom, pyton reaguje z „I got it”

kod HTML

(będziesz musiał użyć konsoli js do tego)

<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> 

kodu Pythona: import 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() 
Powiązane problemy