65

I'm trying to create multithreaded web server in python, but it only responds to one request at a time and I can't figure out why. Can you help me, please?

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from SocketServer import ThreadingMixIn
from  BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
from time import sleep

class ThreadingServer(ThreadingMixIn, HTTPServer):
    pass

class RequestHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        sleep(5)
        response = 'Slept for 5 seconds..'
        self.send_header('Content-length', len(response))
        self.end_headers()
        self.wfile.write(response)

ThreadingServer(('', 8000), RequestHandler).serve_forever()
user1937459
  • 857
  • 1
  • 9
  • 17
  • With non-blocking socket you can server thousands of clients. No need to create thread for every single request. – Shiplu Mokaddim Dec 30 '12 at 08:47
  • @shiplu.mokadd.im can you please post ans ..your help would be highly appreciated – Deepak Ingole Mar 05 '14 at 17:00
  • @Pilot two things are needed here. `select()` and non-blocking. Python has a [socket](http://docs.python.org/2/library/socket.html) library. IBM got some [good articles](https://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=%2Frzab6%2Frzab6xnonblock.htm) on socket programming using select(). – Shiplu Mokaddim Mar 06 '14 at 06:24
  • @shiplu.mokadd thanks Master for you helpful comment – Deepak Ingole Mar 06 '14 at 16:29
  • `BaseHTTPServer` only handles one connection at a time. `ThreadingMixIn` and `gunicorn` (even the `gevent` version, sadly) just gather up the results from your threads and return them into a single connection at a time, which totally breaks streaming. Fortunately, there is a simple setting you can change in `BaseHTTPServer` to fix this. See my answer below. – personal_cloud Sep 14 '17 at 22:59
  • @shiplu Yeah, non-blocking is fine if you want to code with state machines. That probably will run a lot faster especially considering how bad the Python thread scheduler is. But it will take 2X longer to develop, so then why are you using Python and not threads in C++ (and then you'll be 100X faster). – personal_cloud Sep 15 '17 at 18:44
  • Here is another good example of a multithreaded SimpleHTTPServer-like HTTP server: [MultithreadedSimpleHTTPServer on GitHub](https://github.com/Nakiami/MultithreadedSimpleHTTPServer). – GBC Apr 05 '14 at 02:34

6 Answers6

86

Check this post from Doug Hellmann's blog.

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

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."""

if __name__ == '__main__':
    server = ThreadedHTTPServer(('localhost', 8080), Handler)
    print 'Starting server, use <Ctrl-C> to stop'
    server.serve_forever()
twasbrillig
  • 17,084
  • 9
  • 43
  • 67
root
  • 76,608
  • 25
  • 108
  • 120
19

I have developed a PIP Utility called ComplexHTTPServer that is a multi-threaded version of SimpleHTTPServer.

To install it, all you need to do is:

pip install ComplexHTTPServer

Using it is as simple as:

python -m ComplexHTTPServer [PORT]

(By default, the port is 8000.)

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
ViCky
  • 381
  • 3
  • 7
18

In python3, you can use the code below (https or http):

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

USE_HTTPS = True

class Handler(BaseHTTPRequestHandler):

    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'Hello world\t' + threading.currentThread().getName().encode() + b'\t' + str(threading.active_count()).encode() + b'\n')


class ThreadingSimpleServer(ThreadingMixIn, HTTPServer):
    pass

def run():
    server = ThreadingSimpleServer(('0.0.0.0', 4444), Handler)
    if USE_HTTPS:
        import ssl
        server.socket = ssl.wrap_socket(server.socket, keyfile='./key.pem', certfile='./cert.pem', server_side=True)
    server.serve_forever()


if __name__ == '__main__':
    run()

You will figure out this code will create a new thread to deal with every request.

Command below to generate self-sign certificate:

openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365

If you are using Flask, this blog is great.

g10guang
  • 4,647
  • 3
  • 28
  • 22
9

If streaming might be needed down the road, then ThreadingMixIn and gunicorn are no good because they just collect up the response and write it as a unit at the end (which actually does nothing if your stream is infinite).

Your basic approach of combining BaseHTTPServer with threads is fine. But the default BaseHTTPServer settings re-bind a new socket on every listener, which won't work in Linux if all the listeners are on the same port. Change those settings before the serve_forever() call. (Just like you have to set self.daemon = True on a thread to stop ctrl-C from being disabled.)

The following example launches 100 handler threads on the same port, with each handler started through 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://stackoverflow.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)
TylerH
  • 20,799
  • 66
  • 75
  • 101
personal_cloud
  • 3,943
  • 3
  • 28
  • 38
  • 2
    Wouldn't one want to use Websockets for something like this? – Sirmabus Dec 29 '18 at 06:55
  • 2
    A server based on this code worked very well for me as the response was taking up to 2 minutes to be prepared. Being able to return "Working..." right away helped. Alas, Chrome worked out of the box with streaming, Internet Explorer v11 returned the entire page after 1-2 minutes. Don't know yet if the server needs something else or IE is hopeless with streaming. – Adrian Rosoga Jan 28 '19 at 09:50
  • @Adrian Just a thought, you might try chunked transfer encoding. Maybe if you have a chunk header containing the content-length of the part you want displayed right away, the browser might "accept" it sooner? But I have not tried it myself. Of course, if that doesn't work, you could always serve up a – personal_cloud Feb 02 '19 at 03:31
  • Is your server an apt one for a use case of "dozens of concurrent requests that take up to maybe twenty seconds apiece with a mix of remote DB Calls and computations ?" In other words there *is* sufficient "downtime" on each worker task (e.g waiting on a complex remote database operation to complete) to warrant more active threads than the number of CPU's in the system. – WestCoastProjects Mar 16 '21 at 17:32
  • @StephenBoesch Yes, I have used it under much more demanding conditions than that. By all means, use fewer threads if you don't need as many. – personal_cloud Mar 19 '21 at 04:08
3

A multithreaded https server in python3.7

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

hostName = "localhost"
serverPort = 8080


class MyServer(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(bytes("<html><head><title>https://pythonbasics.org</title></head>", "utf-8"))
        self.wfile.write(bytes("<p>Request: %s</p>" % self.path, "utf-8"))
        self.wfile.write(bytes("<p>Thread: %s</p>" % threading.currentThread().getName(), "utf-8"))
        self.wfile.write(bytes("<p>Thread Count: %s</p>" % threading.active_count(), "utf-8"))
        self.wfile.write(bytes("<body>", "utf-8"))
        self.wfile.write(bytes("<p>This is an example web server.</p>", "utf-8"))
        self.wfile.write(bytes("</body></html>", "utf-8"))


class ThreadingSimpleServer(ThreadingMixIn,HTTPServer):
    pass

if __name__ == "__main__":
    webServer = ThreadingSimpleServer((hostName, serverPort), MyServer)
    webServer.socket = ssl.wrap_socket(webServer.socket, keyfile='./privkey.pem',certfile='./certificate.pem', server_side=True)
    print("Server started http://%s:%s" % (hostName, serverPort))

    try:
        webServer.serve_forever()
    except KeyboardInterrupt:
        pass

    webServer.server_close()
    print("Server stopped.")

you can test it in a browser: https://localhost:8080 the running result is: enter image description here
enter image description here remind that you can generate your own keyfile and certificate use

$openssl req -newkey rsa:2048  -keyout privkey.pem -x509 -days 36500 -out certificate.pem

To learn details about creating self-signed certificate with openssl:https://www.devdungeon.com/content/creating-self-signed-ssl-certificates-openssl

  • this examples work great, thanks for sharing. is this actually using a thread pool? if so how to I control the size of this pool? – rustyfinger Dec 08 '20 at 16:41
1

Python 3.7 comes with a ThreadingHTTPServer:

"""Custom response code server by Cees Timmerman, 2023-07-11.
Run and visit http://localhost:4444/300 for example."""

from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler


class Handler(BaseHTTPRequestHandler):

    def do_GET(self):
        try:
            response_code = int(self.path[1:])
        except:
            response_code = 500
        self.send_response(response_code)
        self.end_headers()
        self.wfile.write(f'Hello world! This response has code {response_code}.\n'.encode('utf8'))


def run():
    server = ThreadingHTTPServer(('0.0.0.0', 4444), Handler)
    server.serve_forever()


if __name__ == '__main__':
    run()
Cees Timmerman
  • 17,623
  • 11
  • 91
  • 124