53

The Python documentation includes an example of creating an HTTP server:

def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
    server_address = ('', 8000)
    httpd = server_class(server_address, handler_class)
    httpd.serve_forever()

A RequestHandler class is provided to the Server, which then takes care of instantiating the handler automatically.

Let's say I want to pass in custom parameters to the request handler when it's created. How can and should I do that?

More specifically, I want to pass in parameters from the command line, and having to access sys.argv inside the request handler class seems unnecessarily clunky.

It seems like this should be possible by overriding parts of the Server class, but I feel like I'm overlooking a simpler and better solution.

Jakob
  • 2,588
  • 5
  • 27
  • 34
  • Why not subclass the handler class? – XORcist Feb 07 '14 at 15:41
  • 4
    This seems like a major flaw in the socketserver library design – Tamas Hegedus Dec 30 '17 at 16:38
  • 1
    @TamasHegedus I thought so too until I remembered the elegance of partial application, which solves the general case/problem of passing additional arguments to any callable, without any other code having to re-implement the same argument passthrough logic in its interface and implementation. – mtraceur Aug 25 '18 at 02:06
  • 1
    Good question as simply adding your own __init__ to the handler doesn't work. – andrew pate Jul 25 '20 at 20:23

9 Answers9

38

I solved this in my code using "partial application".

Example is written using Python 3, but partial application works the same way in Python 2:

from functools import partial
from http.server import HTTPServer, BaseHTTPRequestHandler

class ExampleHandler(BaseHTTPRequestHandler):
    def __init__(self, foo, bar, qux, *args, **kwargs):
        self.foo = foo
        self.bar = bar
        self.qux = qux
        # BaseHTTPRequestHandler calls do_GET **inside** __init__ !!!
        # So we have to call super().__init__ after setting attributes.
        super().__init__(*args, **kwargs)

    def do_HEAD(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()

    def do_GET(self):
        self.do_HEAD()
        self.wfile.write('{!r} {!r} {!r}\n'
                         .format(self.foo, self.bar, self.qux)
                         .encode('utf8'))

# We "partially apply" the first three arguments to the ExampleHandler
handler = partial(ExampleHandler, sys.argv[1], sys.argv[2], sys.argv[3])
# .. then pass it to HTTPHandler as normal:
server = HTTPServer(('', 8000), handler)
server.serve_forever()

This is very similar to a class factory, but in my opinion it has a couple of subtle advantages:

  • partial objects are much easier to introspect for what's inside them than nested classes defined and returned by factory functions.
  • partial objects can be serialized with pickle in modern Python, whereas nested class definitions inside factory functions cannot (at least not without going out of your way to code a __reduce__ method on the class to make it possible).
  • In my limited experience explicit "pre-attaching" of arguments with partial to an otherwise Pythonic and normal class definition is easier (less cognitive load) to read, understand, and verify for correctness than a nested class definition with the parameters of the wrapping function buried somewhere inside it.

The only real disadvantage is that many people are unfamiliar with partial - but in my experience it is better for everyone to become familiar with partial anyway, because partial has a way of popping up as an easy and composable solution in many places, sometimes unexpectedly, like here.

mtraceur
  • 3,254
  • 24
  • 33
  • 2
    Very useful, thanks for the idea! In my case I needed to keep state between different calls, and didn't want to make it global. In that case keep in mind that instance of ExampleHandler will be created every time the request is served, so creating a handler class helped me there: `partial(TheHandler, myStatefulHandler)` then `def do_POST(self): self.myStatefulHandler.handle(self.path, ...)` – Mikl X Jan 22 '19 at 19:48
  • @BrianReinhold Cheers, and here's [one decent explanation](https://stackoverflow.com/a/15331841/4372452) of how `partial` works under the hood. Also, here is [CPython's own Python implementation of `partial`](https://github.com/python/cpython/blob/9e35d054223d60c118d18d31102f97b9052afd73/Lib/functools.py#L276) (though CPython uses an equivalent C implementation instead) - you can see it boils down to just a class that holds onto the function and arguments, and implements a `__call__` method so that it can then be called like a function. – mtraceur Sep 05 '22 at 06:35
31

Use a class factory:

def MakeHandlerClassFromArgv(init_args):
    class CustomHandler(BaseHTTPRequestHandler):
        def __init__(self, *args, **kwargs):
             super(CustomHandler, self).__init__(*args, **kwargs)
             do_stuff_with(self, init_args)
    return CustomHandler

if __name__ == "__main__":
    server_address = ('', 8000)
    HandlerClass = MakeHandlerClassFromArgv(sys.argv)
    httpd = HTTPServer(server_address, HandlerClass)
    httpd.serve_forever()
csl
  • 10,937
  • 5
  • 57
  • 89
Thomas Orozco
  • 53,284
  • 11
  • 113
  • 116
  • So in this case, I won't be passing the arguments into the constructor but rather someplace else? – Jakob Feb 07 '14 at 16:02
  • @Jakob You don't need to pass the arguments anymore, actually, since they are in scope when you create the class. See my edit. – Thomas Orozco Feb 07 '14 at 17:24
  • Nice solution. I think it should be: self.do_stuff_with_self(init_args). – arhuaco Mar 07 '15 at 08:22
  • @arhuaco — the `do_stuff_with` was intended as a placeholder for whatever initialization steps might be needed here. It doesn't have to be a function call. – Thomas Orozco Mar 07 '15 at 15:33
  • 9
    I see. BTW, it only worked for me when I added self.do_stuff_with_self(init_args) before the call to super. I am just saving the parameters in an attribute. – arhuaco Mar 08 '15 at 23:36
  • 7
    One thing to bear in mind is that `BaseHTTPRequestHandler` actually runs the handler functions like `do_GET` **inside** its `__init__` method, so you have to do your initialization before calling `super().__init__`, contrary to more typical best-practice-by-default. – mtraceur Aug 27 '18 at 19:53
15

At the time of this writing all the answers here essentially stick to the (very awkward) intention that the author of the socketserver module seemed to have that the handler passed in be a class (i.e. constructor). Really the only thing that's required of the handler is that it's callable, so we can work around the socketserver API by making instances of our handler class callable and having them run the superclass's __init__ code when called. In Python 3:

class MyHandler(http.server.BaseHTTPRequestHandler):
    def __init__(self, message):
        self.message = message

    def __call__(self, *args, **kwargs):
        """Handle a request."""
        super().__init__(*args, **kwargs)

    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(self.message.encode("utf-8"))

This keeps the superclass "constructor" call out of __init__ which eliminates the possibility of dispatching a request (from the superclass's constructor) before the subclass's constructor is finished. Note that the __init__ override must be present to divert execution even if it's not needed for initialization; an empty implementation using pass would work.

With this design the weird interface is hidden and using the API looks more natural:

handler = MyHandler("Hello world")
server = http.server.HTTPServer(("localhost", 8000), handler)
server.serve_forever()
Jakob
  • 383
  • 5
  • 15
  • 1
    +1 for the creativity and persistence in forcing a better design over the existing design. Cool hack. Just beware of how surprising this is to people with enough relevant expectations. Many people who don't already have this problem fresh in mind *will* misread this in various interesting ways. In real projects I suggest making your own `class SensibleHTTPRequestHandler(http.server.BaseHTTPRequestHandler)` which *just* implements this `__call__` method that calls `super()__init__`, with a very clear comment/docstring explaining this, and having all your actual handlers inherit from that. – mtraceur Nov 25 '21 at 05:58
  • Sadly, this otherwise fine solution triggers static code checking for not calling `super()`'s `__init__()` in the `__init__()` method. Probably quite safe to ignore this with knowledge about the internal workings of the `BaseHTTPRequestHandler` (if I understand this correctly). However, that's also a small problem: It could easily be changed with a future Python version, so one shouldn't rely on this. – mozzbozz Jul 20 '22 at 16:26
6

I would just comment on Thomas Orozco's answer but since I can't.. Perhaps this will help others who also run into this problem. Before Python3, Python has "old-style" classes, and BaseHTTPRequestHandler seems to be one of them. So, the factory should look like

def MakeHandlerClassFromArgv(init_args):
    class CustomHandler(BaseHTTPRequestHandler, object):
        def __init__(self, *args, **kwargs):
             do_stuff_with(self, init_args)
             super(CustomHandler, self).__init__(*args, **kwargs)
    return CustomHandler

to avoid errors like TypeError: must be type, not classobj.

user5004049
  • 691
  • 1
  • 8
  • 17
5

Ref Subclassing the HTTPServer is another option. Variables on the server are accessible in the Request Handler methods via self.server.context. It basically works like this:

class MyHTTPServer(HTTPServer):

    def __init__(self, *args, **kwargs):
        HTTPServer.__init__(self, *args, **kwargs)
        self.context = SomeContextObject()


class MyHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        context = self.server.context
        ...

# Drawback, Notice that you cannot actually pass the context parameter during constructor creation, but can do it within the __init__ of the MyHTTPServer
server = MyHTTPServer(('', port), MyHandler)
server.serve_forever()
Abe
  • 8,623
  • 10
  • 50
  • 74
  • Worked for me, although I just added properties to MyHTTPServer in its init, then injected what I needed into those properties before setting it serving. – andrew pate Jul 25 '20 at 20:34
4

Why not just subclass the RequestHandler ?

class RequestHandler(BaseHTTPRequestHandler):
     a_variable = None

class Server(HTTPServer):
    def serve_forever(self, variable):
        self.RequestHandlerClass.a_variable = variable 
        HTTPServer.serve_forever(self)

def run(server_class=Server, handler_class=RequestHandler):
    server_address = ('', 8000)
    httpd = server_class(server_address, handler_class)
    variable = sys.argv
    httpd.serve_forever(variable)
plover
  • 439
  • 5
  • 17
  • I already have a `RequestHandler` subclass. Can I pass command-line arguments to it without hardcoding `sys.argv` into it? Doing so couples the class very tightly to global variables and the command-line arguments of the script (reducing testability, for instance). – Jakob Feb 07 '14 at 16:00
  • Actually, I overlooked your use of `sys.argv` in this example. I'll take a closer look at this method and try it out. – Jakob Feb 07 '14 at 16:04
  • Yes you can. Maybe you should look into multiprocessing library. What I've done in the past (not for commandline arguments, but I guess you could do something similar) is create a Queue() that is shared between the RequestHandler Process and the main, and then the RequestHandler waits for a QEvent. – plover Feb 07 '14 at 16:05
  • This works for "statically available" data but doesn't let you pass parameters into the requesthandler instance. – Carl Walsh Mar 08 '22 at 17:33
4

If you do not need instance properties, but only class properties you could use this approach:

def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
    server_address = ('', 8000)
    httpd = server_class(server_address, handler_class)
    httpd.RequestHandlerClass.my_custom_variable = "hello!"
    httpd.serve_forever()

or maybe you could:

def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
    server_address = ('', 8000)
    httpd = server_class(server_address, handler_class)
    httpd.my_custom_variable = "hello!"
    httpd.serve_forever()

and retrieve in your RequestHandler with:

self.server.my_custom_variable
2

Using a lambda is a pretty simple way to create a new function that takes the request handler args and creates your custom class.

Here I want to pass a variable that will be used in do_POST(), and set the directory used by SimpleHTTPRequestHandler, so setup calls

HTTPServer(('', 8001), lambda *_: _RequestHandler("[1, 2]", *_, directory=sys.path[0]))

Full program:

from http.server import HTTPServer, SimpleHTTPRequestHandler

import sys

class _RequestHandler(SimpleHTTPRequestHandler):
  def __init__(self, x, *args, **kwargs):
    self.x = x # NEEDS TO HAPPEN BEFORE super().__init__()
    super().__init__(*args, **kwargs)

  def _set_headers(self):
    self.send_response(200)
    self.send_header('Content-type', 'application/json')
    self.end_headers()

  def do_POST(self):
    print("POST")
    length = int(self.headers.get('content-length'))
    message = self.rfile.read(length).decode('utf-8')
    print(message)

    self._set_headers()
    self.wfile.write(self.x.encode('utf-8'))

def run_server():
  server_address = ('', 8001)
  httpd = HTTPServer(server_address, lambda *_: _RequestHandler("[1, 2]", *_, directory=sys.path[0]))
  print('serving http://localhost:8001')
  httpd.serve_forever()

if __name__ == '__main__':
  run_server()
Carl Walsh
  • 6,100
  • 2
  • 46
  • 50
-6

You could use a global, like this.

CONFIG = None

class MyHandler(BaseHTTPRequestHandler):
     def __init__(self, ...
         self.config = CONFIG       # CONFIG is now 'stuff'

if __name__ == "__main__":
    global CONFIG
    CONFIG = 'stuff'
    server_address = ('', 8000)
    httpd = HTTPServer(server_address, MyHandler)
    httpd.serve_forever()

Use sparingly, such as in the privacy of your own home.

TylerH
  • 20,799
  • 66
  • 75
  • 101
John Mee
  • 50,179
  • 34
  • 152
  • 186