11

I've set up a threaded (with Python threads) HTTP server by creating a class that inherits from HTTPServer and ThreadingMixIn:

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    pass

I have a handler class which inherits from BaseHTTPRequestHandler, and I start the server with something like this:

class MyHandler(BaseHTTPRequestHandler):
    ...

server = ThreadedHTTPServer(('localhost', 8080), MyHandler)
# Prevent issues with socket reuse
server.allow_reuse_address = True
# Start the server
server.serve_forever()

This is all pretty straightforward. The problem that I'm encountering is that, ThreadingMixIn, ForkingMixIn, or otherwise, the request winds up blocking on the request handler to return. This can easily be seen by implementing this example code:

class MyHandler(BaseHTTPRequestHandler):
    def respond(self, status_code):
        self.send_response(status_code)
        self.end_headers()

    def do_GET(self):
         print "Entered GET request handler"
         time.sleep(10)
         print "Sending response!"
         respond(200)

If the server were processing these simultaneously, then we would be able to send two requests and see the server enter both GET request handlers before sending either response. Instead, the server will enter the GET request handler for the first request, wait for it to return, then enter it for the second (so the second request takes ~20 seconds to return instead of 10).

Is there a straightforward way for me to implement a system where the server doesn't wait on the handler to return? Specifically, I'm trying to write a system which waits to receive several requests before returning any of them (a form of long polling) and running into issues where the first request waiting blocks any future requests from connecting to the server.

Dylnuge
  • 525
  • 1
  • 4
  • 12

1 Answers1

15
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    pass

is enough. Your client probably don't make concurrent requests. If you make the requests in parallel the threaded server works as expected. Here's the client:

#!/usr/bin/env python
import sys
import urllib2

from threading import Thread

def make_request(url):
    print urllib2.urlopen(url).read()

def main():
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
    for _ in range(10):
        Thread(target=make_request, args=("http://localhost:%d" % port,)).start()

main()

And the corresponding server:

import time
from BaseHTTPServer   import BaseHTTPRequestHandler, HTTPServer, test as _test
from SocketServer     import ThreadingMixIn


class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    pass

class SlowHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.end_headers()

        self.wfile.write("Entered GET request handler")
        time.sleep(1)
        self.wfile.write("Sending response!")

def test(HandlerClass = SlowHandler,
         ServerClass = ThreadedHTTPServer):
    _test(HandlerClass, ServerClass)


if __name__ == '__main__':
    test()

All 10 requests finish in 1 second. If you remove ThreadingMixIn from the server definition then all 10 requests take 10 seconds to complete.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • 2
    I was testing this by sending requests to the server through Google Chrome. Turns out Chrome was serializing my requests to the same server, waiting for one to return before even sending the next one. Running a simple Python script fixed it up. Thanks! – Dylnuge Sep 29 '12 at 10:43
  • From my experience time.sleep(1) is not reflecting the real workload of 1 second so I do not think you should test the latency using thread.sleep. Regretfully I do not have the formal explanation but I am guessing it has something to do with the optimization of resource sharing in case of thread.sleep. – MikeL Jun 04 '17 at 11:31
  • @MikeL: `time.sleep(1)` is appropriate for the case in the answer. All we are interested here is whether the server can handle multiple simultaneous requests at all. As OP said: the issue was that the browser doesn't make more than one request to the same url. – jfs Jun 15 '17 at 23:06