pythonsocketserver

How to pass arguments to TCPServer handler class


Here's a piece of experimental/educational code written solely to "play" with socketserver functionality.

The code works without error. All it does is open and read a file sending the file's contents over a TCP connection to a TCPServer. The server prints the received data during the overridden handle() function of BaseRequestHandler.

from socket import socket, AF_INET, SOCK_STREAM
from socketserver import BaseRequestHandler, TCPServer
from threading import Thread

HOST = "localhost"
PORT = 10101
ADDR = HOST, PORT
RECVBUF = 4096
SENDBUF = RECVBUF // 2
INPFILE = "inputfile.txt"


class MyHandler(BaseRequestHandler):
    def handle(self):
        while data := self.request.recv(RECVBUF):
            print(data.decode(), end="")


def server(tcpserver: TCPServer):
    tcpserver.serve_forever()


if __name__ == "__main__":
    with open(INPFILE, "rb") as indata:
        with TCPServer(ADDR, MyHandler) as tcpserver:
            (t := Thread(target=server, args=[tcpserver])).start()
            with socket(AF_INET, SOCK_STREAM) as s:
                s.connect(ADDR)
                while chunk := indata.read(SENDBUF):
                    s.sendall(chunk)
            tcpserver.shutdown()
            t.join()

So this is fine but what if I want the handle() function to write the received data to a file? Sure, I could hard-code the filename into the handle() function or maybe even make it globally available. Neither of those options seem particularly Pythonic to me.

The TCPServer is constructed based on a RequestHandler type - i.e., not a class instance. What I really want to be able to do is (somehow) pass a filename into the MyHandler class. The TCPServer instance will (presumably) have an internal instance variable for the constructed MyHandler but that's not documented so I don't know where that is.

Maybe I'm missing something fundamental but I just can't figure out the "right" way to do achieve this


Solution

  • One way of getting around this API limitation is to use class factory function:

    def get_handler(fname):
        class MyHandler(BaseRequestHandler):
            def handle(self):
                while data := self.request.recv(RECVBUF):
                    print(data.decode(), end="")
                    # copy it to file, I dunno =)
                    with open(fname, 'wb') as f:
                        f.write(data)
        return MyHandler
    

    Then use it like that:

        with TCPServer(ADDR, get_handler('/tmp/file.bin')) as tcpserver: