diff options
-rw-r--r-- | Lib/SocketServer.py | 190 |
1 files changed, 139 insertions, 51 deletions
diff --git a/Lib/SocketServer.py b/Lib/SocketServer.py index c7439dd..170696f 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 @@ This module tries to capture the various aspects of defining a server: - 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 @@ write: a synchronous TCP/IP server. This is bad class design, but 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 @@ server is appropriate. 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 @@ explicit table of partially finished requests and to use select() to 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 @@ Future work: 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 <lkcl@samba.org> + + 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,17 +127,15 @@ import sys import os -class TCPServer: +class BaseServer: - """Base class for various socket-based server classes. - - Defaults to synchronous IP stream (i.e., TCP). + """Base class for server classes. Methods for the caller: - __init__(server_address, RequestHandlerClass) - serve_forever() - - handle_request() # if you don't use serve_forever() + - handle_request() # if you do not use serve_forever() - fileno() -> int # for select() Methods that may be overridden: @@ -128,6 +144,7 @@ class TCPServer: - server_activate() - get_request() -> request, client_address - verify_request(request, client_address) + - server_close() - process_request(request, client_address) - handle_error() @@ -140,43 +157,19 @@ class TCPServer: - address_family - socket_type - - request_queue_size (only for stream sockets) - reuse_address Instance variables: - - server_address - RequestHandlerClass - socket """ - address_family = socket.AF_INET - - socket_type = socket.SOCK_STREAM - - request_queue_size = 5 - - allow_reuse_address = 1 - def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass - self.socket = socket.socket(self.address_family, - self.socket_type) - self.server_bind() - self.server_activate() - - def server_bind(self): - """Called by constructor to bind the socket. - - May be overridden. - - """ - if self.allow_reuse_address: - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.socket.bind(self.server_address) def server_activate(self): """Called by constructor to activate the server. @@ -184,15 +177,7 @@ class TCPServer: May be overridden. """ - self.socket.listen(self.request_queue_size) - - def fileno(self): - """Return socket file number. - - Interface required by select(). - - """ - return self.socket.fileno() + pass def serve_forever(self): """Handle one request at a time until doomsday.""" @@ -222,14 +207,6 @@ class TCPServer: except: self.handle_error(request, client_address) - def get_request(self): - """Get the request and client address from the socket. - - May be overridden. - - """ - return self.socket.accept() - def verify_request(self, request, client_address): """Verify the request. May be overridden. @@ -246,6 +223,14 @@ class TCPServer: """ 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) @@ -260,14 +245,117 @@ class TCPServer: print 'Exception happened during processing of request from', print client_address import traceback - traceback.print_exc() + traceback.print_exc() # XXX But this goes to stderr! print '-'*40 +class TCPServer(BaseServer): + + """Base class for various socket-based server classes. + + Defaults to synchronous IP stream (i.e., TCP). + + Methods for the caller: + + - __init__(server_address, RequestHandlerClass) + - serve_forever() + - handle_request() # if you don't 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) + - 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 + - request_queue_size (only for stream sockets) + - reuse_address + + Instance variables: + + - server_address + - RequestHandlerClass + - socket + + """ + + address_family = socket.AF_INET + + socket_type = socket.SOCK_STREAM + + request_queue_size = 5 + + allow_reuse_address = 0 + + def __init__(self, server_address, RequestHandlerClass): + """Constructor. May be extended, do not override.""" + BaseServer.__init__(self, server_address, RequestHandlerClass) + self.socket = socket.socket(self.address_family, + self.socket_type) + self.server_bind() + self.server_activate() + + def server_bind(self): + """Called by constructor to bind the socket. + + May be overridden. + + """ + if self.allow_reuse_address: + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.bind(self.server_address) + + def server_activate(self): + """Called by constructor to activate the server. + + May be overridden. + + """ + 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. + + Interface required by select(). + + """ + return self.socket.fileno() + + def get_request(self): + """Get the request and client address from the socket. + + May be overridden. + + """ + return self.socket.accept() + + class UDPServer(TCPServer): """UDP server class.""" + allow_reuse_address = 0 + socket_type = socket.SOCK_DGRAM max_packet_size = 8192 @@ -318,7 +406,7 @@ class ForkingMixIn: # 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: |