Passing extra metadata to a RequestHandler using p

2020-08-22 07:56发布

问题:

I'm implementing a python application which is using ThreadingTCPServer and a custom subclass of BaseRequestHandler. The problem with this is that the ThreadingTCPServer seems to automatically spawn threads and create instances of the handler, calling their handle() function. However this leaves me with no way to pass data to the handler other than using global variables or class variables, both of which seem hackish. Is there any better way to do it?

Ideally this should be something like:

class ThreadedTCPServer(ThreadingTCPServer):
    def process_request(self, *args, **kwargs):
        ThreadingTCPServer.process_request(self, data, *args, **kwargs)

with the handler like

class ThreadedTCPRequestHandler(BaseRequestHandler):
    def handle(self,data):
        #do something with data

回答1:

I stumbled upon the very same thing. My solution was the following:

class ThreadedTCPRequestHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        print(self.server.mycustomdata)

class ThreadedTCPServer(SocketServer.ThreadingTCPServer):
    pass

server = ThreadedTCPServer((args.host, args.port), ThreadedTCPRequestHandler)
server.mycustomdata = 'foo.bar.z'
server.serve_forever()

The RequestHandler is called with a server object as a third parameter, and it is saved as self.server attribute, so you can access it. If you would set this attribute to a callable, you could easily call it, too:

def handle(self):
    mycustomdata = self.server.mycustomdata()


回答2:

The first answer worked for me, but I think it is cleaner to alter the __init__ method and pass the attribute in the constructor:

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):

    def __init__(self, host_port_tuple, streamhandler, Controllers):
        super().__init__(host_port_tuple, streamhandler)
        self.Controllers = Controllers

Note the third parameter 'Controllers' in the constructor, then the call to super without that parameter, then setting the new attribute Controllers to the property self.Controllers. The rest of the class is unchanged. Then, in your Requesthandler, you get access to the parameter using the 'server' attribute, as described above:

def handle(self):        
    self.Controllers = self.server.Controllers
    <rest of your code>

It's much the same as the answer above but I find it a little cleaner because the constructor is overloaded and you simply add the attribute you want in the constructor:

server = ServerInterface.ThreadedTCPServer((HOST, PORT), ServerInterface.ThreadedTCPRequestHandler, Controllers)


回答3:

Since handle is implemented by your BaseRequest subclass, it can get the data from itself without having it passed by the caller. (handle could also be a callable attribute of the request instance, such as a lambda—explicit user_data arguments are normally unnecessary in idiomatically designed python.)

Looking at the SocketServer code, it should be straightforward to override finish_request to pass the additional data to your BaseRequestHandler subtype constructor which would store it in the instance for handle to use.