71

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()
7
  • With non-blocking socket you can server thousands of clients. No need to create thread for every single request. Commented Dec 30, 2012 at 8:47
  • @shiplu.mokadd.im can you please post ans ..your help would be highly appreciated Commented Mar 5, 2014 at 17:00
  • @Pilot two things are needed here. select() and non-blocking. Python has a socket library. IBM got some good articles on socket programming using select(). Commented Mar 6, 2014 at 6:24
  • @shiplu.mokadd thanks Master for you helpful comment Commented Mar 6, 2014 at 16:29
  • Here is another good example of a multithreaded SimpleHTTPServer-like HTTP server: MultithreadedSimpleHTTPServer on GitHub. Commented Apr 5, 2014 at 2:34

7 Answers 7

87

Check this post from Doug Hellmann's blog.

The following code is compatible with Python 2. For Python 3 see other answers.

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()
Sign up to request clarification or add additional context in comments.

6 Comments

Note that ThreadingMixIn must come before HTTPServer in the superclass list or it won't work
More detailed example at Python3 docs.
This won't stream. A better approach using BaseHTTPServer is here: stackoverflow.com/questions/46210672
I can't spot any essential differences between the code in this answer and the question. Is there one?
Python 3.7: doesn't work, ModuleNotFoundError: No module named 'BaseHTTPServer'
|
20

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.

2 Comments

This should be a comment. in python3 it's socketserver (lowercase)
I don't think ThreadingSimpleServer is needed, 'http' package has ThreadingHTTPServer, which is almost the same as what you did here.
17

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

Comments

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)

5 Comments

Wouldn't one want to use Websockets for something like this?
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 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 <script> tag that pulls the rest of the content (this is very standard).
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.
@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.
7

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()

7 Comments

Looks like the best answer in 2023.
@Pavel While it's nice to know that Python3 has a better solution, this answer is not entirely topical. The question is clearly about Python2 (which is still widely used in 2023).
@personal_cloud "We have decided that January 1, 2020, was the day that we sunset Python 2. That means that we will not improve it anymore after that day, even if someone finds a security problem in it. You should upgrade to Python 3 as soon as you can." - python.org/doc/sunset-python-2
@Cees There are many, many programming languages that are no longer supported by the initial maintainers, yet still have substantial code bases that are still used. As for newer versions of existing languages: In some cases, like C++, there is an effort to maintain compatibility with existing code. Unfortunately Python 3 is not compatible with most existing Python 2 code. So upgrading involves a cost/benefit analysis for each project.
I'm honestly surprised there's still a significant base of Python 2 users. Not because "Oh, it's old, it should be replaced", (unlike some people) I have ZERO problems using an older codebase if it's still working fine. Simply because I've ported Python 2 software to Python 3, and it's just not that hard. They made some incompatible changes, not intentionally change things around to break code. I only had to make minor syntactical changes to the code I had to update a few years back. But no skin off my nose, it's not like Python2 is suddenly going to quit working for people.
|
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

1 Comment

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?
1

Below is a simple Python 3.7+ HTTP server implementation using the http.server module that supports threading and is suitable for running in production environments. It provides two paths for testing the threading support:

/time -> fast to respond; returns the current machine time.

/delay -> delayed to respond; also returns the current machine time, but 5s delayed, to simulate a slow request, useful to test the threading support.

import datetime
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
from time import sleep


def format_result(text):
    return f"{text} - {datetime.datetime.now().isoformat()}".encode("utf-8")


class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
    pass


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.end_headers()
        if (self.path) == "/time":
            self.wfile.write(format_result("time"))
        if (self.path) == "/delay":
            sleep(5)
            self.wfile.write(format_result("delay"))


def run(port=8000):
    server = ThreadingHTTPServer(("", port), Handler)
    print(f"Server started on port {port}")
    server.serve_forever()


if __name__ == "__main__":
    run()

Open two tabs in your terminal, and call the two endpoints.

To simulate a slow processing:

% curl 'http://localhost:8000/delay' 

Fast to respond, even with the "slow processing" from the request above:

% curl 'http://localhost:8000/time'

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.