diff --git a/Lib/SocketServer.py b/Lib/SocketServer.py index c7439ddb93e..170696fb83c 100644 --- a/Lib/SocketServer.py +++ b/Lib/SocketServer.py @@ -2,6 +2,8 @@ This module tries to capture the various aspects of defining a server: +For socket-based servers: + - address family: - AF_INET: IP (Internet Protocol) sockets (default) - AF_UNIX: Unix domain sockets @@ -9,6 +11,9 @@ - socket type: - SOCK_STREAM (reliable stream, e.g. TCP) - SOCK_DGRAM (datagrams, e.g. UDP) + +For request-based servers (including socket-based): + - client address verification before further looking at the request (This is actually a hook for any processing that needs to look at the request before anything else, e.g. logging) @@ -22,9 +27,14 @@ save some typing. (There's also the issue that a deep class hierarchy slows down method lookups.) -There are four classes in an inheritance diagram that represent +There are five classes in an inheritance diagram, four of which represent synchronous servers of four types: + +------------+ + | BaseServer | + +------------+ + | + v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ @@ -77,7 +87,7 @@ class will essentially render the service "deaf" while one request is In some cases, it may be appropriate to process part of a request synchronously, but to finish processing in a forked child depending on the request data. This can be implemented by using a synchronous -server and doing an explicit fork in the request handler class's +server and doing an explicit fork in the request handler class handle() method. Another approach to handling multiple simultaneous requests in an @@ -87,7 +97,7 @@ class will essentially render the service "deaf" while one request is decide which request to work on next (or whether to handle a new incoming request). This is particularly important for stream services where each client can potentially be connected for a long time (if -threads or subprocesses can't be used). +threads or subprocesses cannot be used). Future work: - Standard classes for Sun RPC (which uses either UDP or TCP) @@ -98,6 +108,14 @@ class will essentially render the service "deaf" while one request is XXX Open problems: - What to do with out-of-band data? +BaseServer: +- split generic "request" functionality out into BaseServer class. + Copyright (C) 2000 Luke Kenneth Casson Leighton + + example: read entries from a SQL database (requires overriding + get_request() to return a table entry from the database). + entry is processed by a RequestHandlerClass. + """ @@ -109,7 +127,129 @@ class will essentially render the service "deaf" while one request is import os -class TCPServer: +class BaseServer: + + """Base class for server classes. + + Methods for the caller: + + - __init__(server_address, RequestHandlerClass) + - serve_forever() + - handle_request() # if you do not use serve_forever() + - fileno() -> int # for select() + + Methods that may be overridden: + + - server_bind() + - server_activate() + - get_request() -> request, client_address + - verify_request(request, client_address) + - server_close() + - process_request(request, client_address) + - handle_error() + + Methods for derived classes: + + - finish_request(request, client_address) + + Class variables that may be overridden by derived classes or + instances: + + - address_family + - socket_type + - reuse_address + + Instance variables: + + - RequestHandlerClass + - socket + + """ + + def __init__(self, server_address, RequestHandlerClass): + """Constructor. May be extended, do not override.""" + self.server_address = server_address + self.RequestHandlerClass = RequestHandlerClass + + def server_activate(self): + """Called by constructor to activate the server. + + May be overridden. + + """ + pass + + def serve_forever(self): + """Handle one request at a time until doomsday.""" + while 1: + self.handle_request() + + # The distinction between handling, getting, processing and + # finishing a request is fairly arbitrary. Remember: + # + # - handle_request() is the top-level call. It calls + # get_request(), verify_request() and process_request() + # - get_request() is different for stream or datagram sockets + # - process_request() is the place that may fork a new process + # or create a new thread to finish the request + # - finish_request() instantiates the request handler class; + # this constructor will handle the request all by itself + + def handle_request(self): + """Handle one request, possibly blocking.""" + try: + request, client_address = self.get_request() + except socket.error: + return + if self.verify_request(request, client_address): + try: + self.process_request(request, client_address) + except: + self.handle_error(request, client_address) + + def verify_request(self, request, client_address): + """Verify the request. May be overridden. + + Return true if we should proceed with this request. + + """ + return 1 + + def process_request(self, request, client_address): + """Call finish_request. + + Overridden by ForkingMixIn and ThreadingMixIn. + + """ + self.finish_request(request, client_address) + + def server_close(self): + """Called to clean-up the server. + + May be overridden. + + """ + pass + + def finish_request(self, request, client_address): + """Finish one request by instantiating RequestHandlerClass.""" + self.RequestHandlerClass(request, client_address, self) + + def handle_error(self, request, client_address): + """Handle an error gracefully. May be overridden. + + The default is to print a traceback and continue. + + """ + print '-'*40 + print 'Exception happened during processing of request from', + print client_address + import traceback + traceback.print_exc() # XXX But this goes to stderr! + print '-'*40 + + +class TCPServer(BaseServer): """Base class for various socket-based server classes. @@ -157,12 +297,11 @@ class TCPServer: request_queue_size = 5 - allow_reuse_address = 1 + allow_reuse_address = 0 def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" - self.server_address = server_address - self.RequestHandlerClass = RequestHandlerClass + BaseServer.__init__(self, server_address, RequestHandlerClass) self.socket = socket.socket(self.address_family, self.socket_type) self.server_bind() @@ -186,6 +325,14 @@ def server_activate(self): """ self.socket.listen(self.request_queue_size) + def server_close(self): + """Called to clean-up the server. + + May be overridden. + + """ + self.socket.close() + def fileno(self): """Return socket file number. @@ -194,34 +341,6 @@ def fileno(self): """ return self.socket.fileno() - def serve_forever(self): - """Handle one request at a time until doomsday.""" - while 1: - self.handle_request() - - # The distinction between handling, getting, processing and - # finishing a request is fairly arbitrary. Remember: - # - # - handle_request() is the top-level call. It calls - # get_request(), verify_request() and process_request() - # - get_request() is different for stream or datagram sockets - # - process_request() is the place that may fork a new process - # or create a new thread to finish the request - # - finish_request() instantiates the request handler class; - # this constructor will handle the request all by itself - - def handle_request(self): - """Handle one request, possibly blocking.""" - try: - request, client_address = self.get_request() - except socket.error: - return - if self.verify_request(request, client_address): - try: - self.process_request(request, client_address) - except: - self.handle_error(request, client_address) - def get_request(self): """Get the request and client address from the socket. @@ -230,44 +349,13 @@ def get_request(self): """ return self.socket.accept() - def verify_request(self, request, client_address): - """Verify the request. May be overridden. - - Return true if we should proceed with this request. - - """ - return 1 - - def process_request(self, request, client_address): - """Call finish_request. - - Overridden by ForkingMixIn and ThreadingMixIn. - - """ - self.finish_request(request, client_address) - - def finish_request(self, request, client_address): - """Finish one request by instantiating RequestHandlerClass.""" - self.RequestHandlerClass(request, client_address, self) - - def handle_error(self, request, client_address): - """Handle an error gracefully. May be overridden. - - The default is to print a traceback and continue. - - """ - print '-'*40 - print 'Exception happened during processing of request from', - print client_address - import traceback - traceback.print_exc() - print '-'*40 - class UDPServer(TCPServer): """UDP server class.""" + allow_reuse_address = 0 + socket_type = socket.SOCK_DGRAM max_packet_size = 8192 @@ -318,7 +406,7 @@ def process_request(self, request, client_address): # Child process. # This must never return, hence os._exit()! try: - self.socket.close() + self.server_close() self.finish_request(request, client_address) os._exit(0) except: