如何在Python中实现一个最小的AJAX服务器?

39

我想为一个Python程序创建一个非常简单的基于HTML/AJAX的GUI。因此,前端是一个HTML页面,通过AJAX与程序通信。您能否使用Python SimpleHTTPServer.SimpleHTTPRequestHandler提供服务器端的最小实现?

一个简单的例子是一个文本字段和一个按钮。当按下按钮时,将字段的内容发送到服务器,然后服务器发送回相应的答案。我知道在Python中有许多强大的解决方案,但我想保持这个非常简单。我已经找到了一些关于这样的服务器的不错例子(例如这里),但到目前为止,我还没有找到一个真正简洁的例子。

如果你想知道为什么我想以这种方式实现GUI:我的重点是在一个漂亮的布局中显示大量的数据,并只进行最少的交互 - 因此使用HTML+CSS似乎是最方便的选择(我已经在非交互式数据显示方面使用它)。

4个回答

53

好的,我认为我现在可以回答自己的问题了。这里是一个计算服务器上一个数字平方的示例实现。如果有任何改进或误解,请告诉我。

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

...和HTML文件(我称之为“frontend.html”,不幸的是这个名称也必须出现在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>

当然使用jQuery来进行XML请求会更加方便,但出于简化的考虑,我将保留此方式。
最后,以下是使用WSGI的替代实现(不幸的是,如果请求不是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
仅供比较,这是一个Ramaze的示例:http://news.ycombinator.com/item?id=383960 - jfs
如果请求不是POST,是否有任何方法可以回退到标准的Python处理程序?“standard”处理程序是什么意思? - S.Lott
S.Lott:在第一个服务器实现中,SimpleHTTPRequestHandler的行为仅针对POST请求进行更改。因此,加载HTML文件不需要任何其他代码。在WSGI实现中,我必须显式发送HTML,GET不能自动处理。 - nikow
1
这个例子在最新版本的Google Chrome(55.0.2883)之前是可以工作的,但现在返回错误net::ERR_INVALID_HTTP_RESPONSE。在网上搜索,其他应用程序似乎也遇到了与此版本的Chrome类似的问题,特别是在非标准端口上。有什么想法吗? - SkyNT
我做了一个Python3版本:https://gitlab.com/Hugo-Trentesaux/simple-api - Hugo Trentesaux
req.responseText为空,而result不是。同样的问题已经发布在https://stackoverflow.com/q/40314747/11769765中。 - Friedrich -- Слава Україні

11

使用WSGI参考实现。从长远来看,您会更加快乐。

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相对容易编写,它处理您的Ajax请求。


谢谢,我终于成功构建了一个简单的示例(请参见下面的答案)。 - nikow
听起来很有趣。你能解释一下为什么这比“BaseHTTPServer”方法更好吗? - FriendFX

3

以下是一个基于@nikow示例的Python 3简单示例。

我知道这可能会有错误,请在发现错误时进行评论。

当您单击“运行”时,代码将发送字符串“I sent you this message”,Python将响应“我收到了它”。

HTML代码

(您需要使用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>

Python代码:

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

0

非常感谢 @nikow 提供的直观示例。

我试图按照您的示例进行操作,但是出现了错误:

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

我修改了您的代码以满足我的需求。

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

我的HTML文件必须放在Web服务器上吗?我还没有放置它!


是的,在我的例子中,文件由Web服务器提供,因此它必须在那里可用。 - nikow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接