5

I have an own class derived from BaseHTTPRequestHandler, which implements my specific GET method. This works quite fine:

from http.server import BaseHTTPRequestHandler, HTTPServer

class MyHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        """ my implementation of the GET method """

myServer = HTTPServer(("127.0.0.1", 8099), MyHTTPRequestHandler)
myServer.handle_request()

But why do I need to pass my class MyHTTPRequestHandler to the HTTPServer? I know that it is required by documentation:

class http.server.BaseHTTPRequestHandler(request, client_address, server)

This class is used to handle the HTTP requests that arrive at the server. By itself, it cannot respond to any actual HTTP requests; it must be subclassed to handle each request method (e.g. GET or POST). BaseHTTPRequestHandler provides a number of class and instance variables, and methods for use by subclasses.

The handler will parse the request and the headers, then call a method specific to the request type. The method name is constructed from the request. For example, for the request method SPAM, the do_SPAM() method will be called with no arguments. All of the relevant information is stored in instance variables of the handler. Subclasses should not need to override or extend the init() method.

But I do want to pass an instantiated object of my subclass instead. I don't understand why this has been designed like that and it looks like design failure to me. The purpose of object oriented programming with polymorphy is that I can subclass to implement a specific behavior with the same interfaces, so this seems to me as an unnecessary restriction.

That is what I want:

from http.server import BaseHTTPRequestHandler, HTTPServer

class MyHTTPRequestHandler(BaseHTTPRequestHandler):
    def __init__(self, myAdditionalArg):
        self.myArg = myAdditionalArg

    def do_GET(self):
        """ my implementation of the GET method """
        self.wfile(bytes(self.myArg, "utf-8"))
        # ...

myReqHandler = MyHTTPRequestHandler("mySpecificString")
myServer = HTTPServer(("127.0.0.1", 8099), myReqHandler)
myServer.handle_request()

But if I do that, evidently I receive the expected error message:

TypeError: 'MyHTTPRequestHandler' object is not callable

How can I workaround this so that I can still use print a specific string? There is also a 2nd reason why I need this: I want that MyHTTPRequestHandler provides also more information about the client, which uses the GET method to retrieve data from the server (I want to retrieve the HTTP-Header of the client browser).

I just have one client which starts a single request to the server. If a solution would work in a more general context, I'll be happy, but I won't need it for my current project.

Somebody any idea to do that?

tangoal
  • 724
  • 1
  • 9
  • 28
  • do you mean that you want the server to keep track of all the headers from requests? – e.s. Jul 17 '18 at 04:56
  • @e.s.: I just have one request in that case. I just want to see the headers coming with that request. – tangoal Jul 17 '18 at 11:49
  • edited answer after above comment – e.s. Jul 17 '18 at 16:37
  • :) You're Java dev who's been coerced into doing Python right? Did you get an answer for this? I suspect it has something to do with multithreading. – Richard Apr 08 '20 at 23:17

2 Answers2

2

A server needs to create request handlers as needed to handle all the requests coming in. It would be bad design to only have one request handler. If you passed in an instance, the server could only ever handle one request at a time and if there were any side effects, it would go very very badly. Any sort of change of state is beyond the scope of what a request handler should do.

BaseHTTPRequestHandler has a method to handle message logging, and an attribute self.headers containing all the header information. It defaults to logging messages to sys.stderr, so you could do $ python -m my_server.py 2> log_file.txt to capture the log messages. or, you could write to file in your own handler.

class MyHTTPRequestHandler(BaseHTTPRequestHandler):
    log_file = os.path.join(os.path.abspath(''), 'log_file.txt')  # saves in directory where server is
    myArg = 'some fancy thing'                    

    def do_GET(self):
        # do things
        self.wfile.write(bytes(self.myArg,'utf-8'))
        # do more
        msg_format = 'start header:\n%s\nend header\n'
        self.log_message(msg_format, self.headers)

    def log_message(self, format_str, *args):  # This is basically a copy of original method, just changing the destination
        with open(self.log_file, 'a') as logger:
            logger.write("%s - - [%s] %s\n" %
                        self.log_date_time_string(),
                        format%args))

handler = MyHTTPRequestHandler
myServer = HTTPServer(("127.0.0.1", 8099), handler)  
e.s.
  • 1,351
  • 8
  • 12
  • Yes, I thought to write the header directly in a file, too. And in fact, this is what I did first, too. I'd like to avoid such a misuse of the file system as interface to pass data, which could have been passed internally. I found another workaround, which I am not proud of, but I can live with it. I will post it soon. – tangoal Jul 17 '18 at 19:38
  • I understand that a server must handle several requests at a time. I can still imagine that a different design can serve both aspects: (1) pass request handler as object (2) handle multiple requests at same time. – tangoal Jul 17 '18 at 19:55
1

It is possible to derive a specific HTTPServer class (MyHttpServer), which has the following attributes:

  • myArg: the specific "message text" which shall be printed by the HTTP request handler
  • headers: a dictionary storing the headers set by a HTTP request handler

The server class must be packed together with MyHTTPRequestHandler. Furthermore the implementation is only working properly under the following conditions:

  • only one HTTP request handler requests an answer from the server at the same time (otherwise data kept by the attributes are corrupted)
  • MyHTTPRequestHandler is only used with MyHttpServer and vice versa (otherwise unknown side effects like exceptions or data corruption can occur)

That's why both classes must be packed and shipped together in a way like this:

from http.server import BaseHTTPRequestHandler, HTTPServer

class MyHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.wfile.write(bytes(self.server.myArg, 'utf-8'))
        #...
        self.server.headers = self.headers

class MyHttpServer(HTTPServer):
    def __init__(self, server_address, myArg, handler_class=MyHttpRequestHandler):
        super().__init__(server_address, handler_class)
        self.myArg = myArg
        self.headers = dict()

The usage of these classes could look like this, whereas only one request of a client (i.e. Web-Browser) is answered by the server:

def get_header():
    httpd = MyHttpServer(("127.0.0.1", 8099), "MyFancyText", MyHttpRequestHandler)
    httpd.handle_request()
    return httpd.headers
tangoal
  • 724
  • 1
  • 9
  • 28