Jak zaimplementować minimalny serwer dla AJAX w Pythonie?

Chcę stworzyć bardzo prosty GUI oparty na HTML / AJAX dla programu Pythona. Tak więc frontend jest stroną HTML, która komunikuje się z programem za pośrednictwem AJAX. Możesz mi podać minimalną implementację po stronie serwera używając Pythona SimpleHTTPServer.SimpleHTTPRequestHandler?

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 odpowiednią odpowiedź. Zdaję sobie sprawę, że istnieje wiele potężnych rozwiązań do tego w Pythonie, ale chciałbym, żeby to było bardzo proste. Znalazłem już kilka fajnych przykładów dla takiego serwera (np. tutaj), ale jak na razie nie mogłem wymyślić naprawdę minimalnego.

W przypadku, gdy zastanawiasz się, dlaczego chcę zaimplementować GUI w taki sposób: moim celem dla tej aplikacji jest wyświetlanie wielu danych w ładnym układzie z tylko minimalną interakcją - więc używanie HTML + CSS wydaje się najwygodniejsze (I już używałem go do nieinteraktywnego wyświetlania danych).

Author: nikow, 2008-12-03

4 answers

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

Plik serwera Pythona:

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 (nazywam go ' frontend.html', niestety nazwa musi pojawić się również w kodzie JavaScript):

<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 o wiele wygodniej byłoby użyć jQuery dla żądania XML, ale w interes prostoty zostawię tak.

Wreszcie alternatywna implementacja wykorzystująca WSGI (niestety nie widziałem sposobu na powrót do standardowego obsługi plików, jeśli żądanie nie jest postem):

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()
 49
Author: nikow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-22 14:48:58

Użyj implementacji wzorcowej WSGI . Na dłuższą metę 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 żądania Ajax.

 9
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-05 13:46:05

Dzięki za bardzo intuicyjny przykład @nikow Próbowałem podążać za twoim przykładem, ale dostałem błąd:

(process:10281): GLib-CRITICAL**: g_slice_set_config :twierdzenie' sys_page_size == 0 ' failed

Zmodyfikowałem Twój kod, aby zaspokoić moje 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 być umieszczony na serwerze WWW ? Jeszcze go tam nie umieściłem !.

 0
Author: JonB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-23 20:42:27

Oto prosty przykład dla Pythona 3 na podstawie przykładu @nikow

Wiem, że to może mieć błędy, skomentuj, co to są, jeśli je znajdziesz.

Kod wysyła ciąg "I sent you this message" po kliknięciu run, python odpowiada "I got it"

Kod Html

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

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

Kod Pythona: import http.Serwer

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()
 0
Author: Aditya Shankar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-25 05:36:06