如何编写一个Python HTTP服务器以监听多个端口?

21

我正在使用BaseHTTPServer和自定义的BaseHTTPRequestHandler子类,用Python编写一个小型Web服务器。是否可能使其监听多个端口?

我现在在做什么:

class MyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
  def doGET
  [...]

class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): 
    pass

server = ThreadingHTTPServer(('localhost', 80), MyRequestHandler)
server.serve_forever()
3个回答

42

没问题,只需在两个不同的线程上启动两个使用相同处理程序的不同端口的服务器。这是我刚刚编写并测试的完整工作示例。如果您运行此代码,则可以从http://localhost:1111/http://localhost:2222/获得Hello World网页。

from threading import Thread
from SocketServer import ThreadingMixIn
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.end_headers()
        self.wfile.write("Hello World!")

class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
    daemon_threads = True

def serve_on_port(port):
    server = ThreadingHTTPServer(("localhost",port), Handler)
    server.serve_forever()

Thread(target=serve_on_port, args=[1111]).start()
serve_on_port(2222)

更新:

这个方法同样适用于Python 3,但需要稍微更改三行代码:

from socketserver import ThreadingMixIn
from http.server import HTTPServer, BaseHTTPRequestHandler

并且

self.wfile.write(bytes("Hello World!", "utf-8"))

2
@scrat:对于这段代码来说,GIL并不会有太大影响,因为这段代码主要是I/O绑定的,而Python中的大多数I/O都是使用低级C库编写的,这些库会释放GIL。就像大多数性能问题一样,我的建议是,除非你已经对代码进行了基准测试并确定它确实是一个问题,否则不要担心它。 - Eli Courtwright
你必须将daemon_threads = True作为class ThreadingHTTPServer(ThreadingMixIn, HTTPServer)的成员添加,否则产生的线程不会停止,你的Python进程将不会退出,直到你发送更多请求来帮助这些线程意识到他们需要停止。我已经相应地编辑了你的回答,请让我知道是否有问题。 - RedGlyph

6
不是很容易。您可以拥有两个ThreadingHTTPServer实例,编写自己的serve_forever()函数(不要担心它不是一个复杂的函数)。
现有的函数:
def serve_forever(self, poll_interval=0.5):
    """Handle one request at a time until shutdown.

    Polls for shutdown every poll_interval seconds. Ignores
    self.timeout. If you need to do periodic tasks, do them in
    another thread.
    """
    self.__serving = True
    self.__is_shut_down.clear()
    while self.__serving:
        # XXX: Consider using another file descriptor or
        # connecting to the socket to wake this up instead of
        # polling. Polling reduces our responsiveness to a
        # shutdown request and wastes cpu at all other times.
        r, w, e = select.select([self], [], [], poll_interval)
        if r:
            self._handle_request_noblock()
    self.__is_shut_down.set()

因此,我们的替换将类似于以下内容:
def serve_forever(server1,server2):
    while True:
        r,w,e = select.select([server1,server2],[],[],0)
        if server1 in r:
            server1.handle_request()
        if server2 in r:
            server2.handle_request()

6

我认为为这么简单的东西使用线程是过度设计。你最好使用某种形式的异步编程。

以下是使用Twisted的示例:

from twisted.internet import reactor
from twisted.web import resource, server

class MyResource(resource.Resource):
    isLeaf = True
    def render_GET(self, request):
        return 'gotten'

site = server.Site(MyResource())

reactor.listenTCP(8000, site)
reactor.listenTCP(8001, site)
reactor.run()

我认为将每个端口都以相同的方式处理,而不是让主线程处理一个端口,另一个线程处理另一个端口,这样看起来更加简洁。虽然在线程示例中可以修复这个问题,但那样就需要使用三个线程。


你提出了一些非常好的观点,我很想在我的回答中建议使用Twisted。但是,我采用的方法的主要优点是它不需要标准库之外的任何东西。对于真正的应用程序,我会在Apache后面使用CherryPy或其他不是BaseHTTPServer的东西。 - Eli Courtwright

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