Search code examples
python-2.7websockettornadobasehttpserverbasehttprequesthandler

Python BaseHTTPServer and Tornado


I'm running a BaseHTTPServer, passed through ThreadedHTTPServer so I get threading.

server = ThreadedHTTPServer(('', int(port)), MainHandler)

Next I fork according to the info here: Daemonizing python's BaseHTTPServer

Then I do:

server.serve_forever()

What I am trying to do is have the same Python script run a Tornado WebSocket server as well, I tried creating the second handler and in my main creating the second server similar to above, but then the serve_forever() blocks (I assume) and I can't start the Tornado WebSocket server.

I had considered using Tornado to serve my general web stuff too but performance was aweful and unusable, so I'd prefer to run it alongside, unless there is a simpler alternative to adding WebSockets to the BaseHTTPServer.

Can anyone offer a solution please?


Solution

  • Yes, serve_forever() blocks it all. You can use handle_request to serve one request at a time. To assure it won't block you have to set timeout. To run it periodically you can use tornado.ioloop.PeriodicCallback. Example:

    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    
    from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
    from SocketServer import ThreadingMixIn
    import threading
    
    import tornado.httpserver
    import tornado.ioloop
    import tornado.options
    import tornado.web
    
    from tornado.options import define, options
    define("port", default=8000, help="run on the given port", type=int)
    
    class Handler(BaseHTTPRequestHandler):  
        def do_GET(self):
            self.send_response(200)
            self.end_headers()
            message =  threading.currentThread().getName()
            self.wfile.write(message)
            self.wfile.write('\n')
            return
    
    class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
        """Handle requests in a separate thread."""
    
    class IndexHandler(tornado.web.RequestHandler):
        def get(self):
            greeting = self.get_argument('greeting', 'Hello')
            self.write(greeting + ', friendly user!\n')
    
    if __name__ == '__main__':
        # create Tornado Server
        tornado.options.parse_command_line()
        app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
        http_server = tornado.httpserver.HTTPServer(app)
        http_server.listen(options.port)
    
        # create BaseHTTPServer
        server = ThreadedHTTPServer(('localhost', 8080), Handler)
        server.timeout = 0.01 
    
        tornado.ioloop.PeriodicCallback(server.handle_request, 100).start() # every 100 milliseconds
        tornado.ioloop.IOLoop.instance().start()
    

    Running:

    $ curl http://localhost:8080/
    Thread-1
    $ curl http://localhost:8080/
    Thread-2
    $ curl http://localhost:8000/
    Hello, friendly user!
    $ curl http://localhost:8080/
    Thread-3
    $ curl http://localhost:8000/
    Hello, friendly user!
    $ curl http://localhost:8080/
    Thread-4
    $ curl http://localhost:8000/
    Hello, friendly user!
    $ curl http://localhost:8000/
    Hello, friendly user!
    

    I used here timeout attribute to set timeout. I'm not sure if it's proper way to do it. Other method: http://code.activestate.com/recipes/499376/

    Another solution: running every server in its own thread:

    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    
    from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
    from SocketServer import ThreadingMixIn
    import threading
    
    import tornado.httpserver
    import tornado.ioloop
    import tornado.options
    import tornado.web
    
    from tornado.options import define, options
    define("port", default=8000, help="run on the given port", type=int)
    
    class Handler(BaseHTTPRequestHandler):  
        def do_GET(self):
            self.send_response(200)
            self.end_headers()
            message =  threading.currentThread().getName()
            self.wfile.write(message)
            self.wfile.write('\n')
            return
    
    class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
        """Handle requests in a separate thread."""
    
    class IndexHandler(tornado.web.RequestHandler):
        def get(self):
            greeting = self.get_argument('greeting', 'Hello')
            self.write(greeting + ', friendly user!\n')
    
    def run_tornado():
        tornado.options.parse_command_line()
        app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
        http_server = tornado.httpserver.HTTPServer(app)
        http_server.listen(options.port)
        tornado.ioloop.IOLoop.instance().start()
    
    def run_base_http_server():
        server = ThreadedHTTPServer(('localhost', 8080), Handler)
        server.serve_forever()
    
    if __name__ == '__main__':
        threading.Thread(target=run_tornado).start()
        threading.Thread(target=run_base_http_server).start()