Python 2.7:支持一个端口上的多个连接的流式 HTTP 服务器

Posted

技术标签:

【中文标题】Python 2.7:支持一个端口上的多个连接的流式 HTTP 服务器【英文标题】:Python 2.7: streaming HTTP server supporting multiple connections on one port 【发布时间】:2018-02-22 22:33:55 【问题描述】:

我正在寻找一个标准的 Python 2.7 包,它提供一个 HTTP 服务器,该服务器在同一端口号上同时进行 连接。

各位版主您好,请停止将我的问题标记为与希望以非流媒体方式提供的问题的重复,例如: Multithreaded web server in python。不,我不想要像 ThreadingMixIn 这样的 hack,它只是收集响应并将其作为一个单元返回。

换句话说,我正在寻找执行以下示例程序的标准方法——但我自己没有编写整个 HTTP 服务器。

import time, socket, threading

sock = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
host = socket.gethostname()
port = 8000

sock.bind((host, port))
sock.listen(1)

# my OWN HTTP server... Oh man, this is bad style.
HTTP = "HTTP/1.1 200 OK\nContent-Type: text/html; charset=UTF-8\n\n"

class Listener(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.daemon = True # stop Python from biting ctrl-C
        self.start()

    def run(self):
        conn, addr = sock.accept()
        conn.send(HTTP)

        # serve up an infinite stream
        i = 0
        while True:
            conn.send("%i " % i)
            time.sleep(0.1)
            i += 1

[Listener() for i in range(100)]
time.sleep(9e9)

所以我首先尝试了:

# run with this command:
#    gunicorn -k gevent myapp:app
import time

def app(environ, start_response):
    data = b"Hello, World!\n"
    start_response("200 OK", [
        ("Content-Type", "text/plain"),
        ("Content-Length", str(len(data)))
    ])
    for i in range(5):
        time.sleep(1)
        yield "Hello %i\n" % i

# https://***.com/questions/22739394/streaming-with-gunicorn

但不幸的是,即使使用-k gevent,它也不会流式传输。

更新:gunicorn 似乎正在尝试进行保活,这需要使用 last-chunk 位进行分块传输编码。对来源的快速 grep 表明它没有实现这一点。所以我可能需要一个更高级的 HTTP 服务器,或者一个更简单的 HTTP 服务器(就像我上面的第一个示例,基于 socket),它不会打扰 keepalive(无论如何这对于大型流来说非常愚蠢)。

然后我尝试了:

import time
import threading

import BaseHTTPServer

class Handler(BaseHTTPServer.BaseHTTPRequestHandler):

    def do_GET(self):
        if self.path != '/':
            self.send_error(404, "Object not found")
            return
        self.send_response(200)
        self.send_header('Content-type', 'text/html; charset=utf-8')
        self.end_headers()

        # serve up an infinite stream
        i = 0
        while True:
            self.wfile.write("%i " % i)
            time.sleep(0.1)
            i += 1

class Listener(threading.Thread):

    def __init__(self, i):
        threading.Thread.__init__(self)
        self.i = i
        self.daemon = True
        self.start()

    def run(self):
        server_address = ('', 8000+self.i) # How to attach all of them to 8000?
        httpd = BaseHTTPServer.HTTPServer(server_address, Handler)
        httpd.serve_forever()

[Listener(i) for i in range(100)]
time.sleep(9e9)

这很好,但是我必须分配100个端口号有点烦人。这将需要一个令人讨厌的客户端重定向来让浏览器到达下一个可用端口(好吧,我可以用 javascript 隐藏它,但它不是那么优雅。我宁愿编写自己的 HTTP 服务器也不愿这样做)。

必须有一种干净的方法来让所有BaseHTTPServer 侦听器在一个端口上,因为它是设置Web 服务器的一种标准方法。或者也许gunicorn 或一些这样的包可以可靠地流式传输?

【问题讨论】:

看看Twisted @aergistal 嗯,该站点上的 hello Web 示例完全不是流式传输...如果您在该站点上搜索流式传输,您会得到 twistedmatrix.com/trac/attachment/ticket/9187/server.py,它实际上基于基础HTTP服务器。所以这只是进一步确认 BaseHTTPServer 是要走的路。 【参考方案1】:

默认的BaseHTTPServer 设置会在每个侦听器上重新绑定一个新套接字,如果所有侦听器都在同一个端口上,这在 Linux 中将不起作用。在BaseHTTPServer.HTTPServer() 调用和serve_forever() 调用之间更改这些设置。

以下示例在同一端口上启动 100 个处理程序线程,每个处理程序通过 BaseHTTPServer 启动。

import time, threading, socket, SocketServer, BaseHTTPServer

class Handler(BaseHTTPServer.BaseHTTPRequestHandler):

    def do_GET(self):
        if self.path != '/':
            self.send_error(404, "Object not found")
            return
        self.send_response(200)
        self.send_header('Content-type', 'text/html; charset=utf-8')
        self.end_headers()

        # serve up an infinite stream
        i = 0
        while True:
            self.wfile.write("%i " % i)
            time.sleep(0.1)
            i += 1

# Create ONE socket.
addr = ('', 8000)
sock = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addr)
sock.listen(5)

# Launch 100 listener threads.
class Thread(threading.Thread):
    def __init__(self, i):
        threading.Thread.__init__(self)
        self.i = i
        self.daemon = True
        self.start()
    def run(self):
        httpd = BaseHTTPServer.HTTPServer(addr, Handler, False)

        # Prevent the HTTP server from re-binding every handler.
        # https://***.com/questions/46210672/
        httpd.socket = sock
        httpd.server_bind = self.server_close = lambda self: None

        httpd.serve_forever()
[Thread(i) for i in range(100)]
time.sleep(9e9)

【讨论】:

这对我来说非常有用。我刚刚在 BaseHTTPServer 中重写了我所有的烧瓶/gunicorn/gevent 应用程序(甚至是非流式应用程序),吞吐量翻了一番!好在我在底部有自己的 URL 装饰器,可以轻松进行更改。 更新:我已经通过上面的代码运行了数百万个 SSL 连接(在任何给定时间最多同时有 2000 个),没有任何错误(在我的测试中,我使连接有限并减少了睡眠时间来压力系统)。 (在启动线程之前,可以通过sock = ssl.SSLSocket(sock,...) 轻松添加 SSL) 对于 Python3:“从 http.server 导入 BaseHTTPRequestHandler, HTTPServer”和“类 Handler(BaseHTTPRequestHandler):”和“httpd = HTTPServer(addr, Handler, False)”和“9e9”->” 9e7" 知道为什么这似乎不适用于多处理吗?它工作了一段时间,然后似乎只是挂起,可能是因为套接字共享......

以上是关于Python 2.7:支持一个端口上的多个连接的流式 HTTP 服务器的主要内容,如果未能解决你的问题,请参考以下文章

同一端口套接字C ++上的多个连接

SOCKET编程之一个端口如何建立多个TCP连接?(用fork子进程selectpollepoll都是可以的)一个端口最大支持建立多少个tcp连接?

python——初识socket

跨 TCP 443 连接的 Amazon ELB 会话粘性?

Python 2.7:serial.serialutil.SerialException:端口已打开。如何关闭这个端口?

python/socketserver