class TCPServer( object ): """A non-blocking, single-threaded "Mixin class" implementing TCP server. To use `TCPServer`, define a `Plugin` subclass which overrides the `handle_stream` method. `TCPServer` can serve SSL traffic with Python 2.6+ and OpenSSL. To make this server serve SSL traffic, configure the sub-class plugin with `ssloptions.*` settings. which is required for the `ssl.wrap_socket` method, including "certfile" and "keyfile" """ def __init__( self, sett ): # configuration settings self.sett = sett self._sockets = {} # fd -> socket object self._pending_sockets = [] self._started = False self.ioloop = None def listen( self ): """Starts accepting connections on the given port. This method may be called more than once to listen on multiple ports. `listen` takes effect immediately; it is not necessary to call `TCPServer.start` afterwards. It is, however, necessary to start the `HTTPIOLoop`. """ sett = self.sett sockets = bind_sockets( sett['port'], sett['host'], None, sett['backlog'] ) self.add_sockets(sockets) def add_sockets( self, sockets ): """Make the server start accepting connections using event loop on the given sockets. The ``sockets`` parameter is a list of socket objects such as those returned by `bind_sockets`. """ self.ioloop = HTTPIOLoop( self.sett ) for sock in sockets: self._sockets[ sock.fileno()] = sock add_accept_handler( sock, self._handle_connection, self.ioloop ) def add_socket( self, socket ): """Singular version of `add_sockets`. Takes a single socket object.""" self.add_sockets([socket]) def bind( self ): """Binds this server to the addres, port and family configured in server settings. This method may be called multiple times prior to `start` to listen on multiple ports or interfaces.""" family = socket.AF_UNSPEC sett = self.sett sockets = bind_sockets( sett['port'], sett['host'], family, sett['backlog'] ) if self._started : self.add_sockets( sockets ) else: self._pending_sockets.extend( sockets ) def start( self ): """Starts this server using HTTPIOloop. By default, we run the server in this process and do not fork any additional child process. If `multiprocess` settings not configured or configured as <= 0, we detect the number of cores available on this machine and fork that number of child processes. If `multiprocess` settings configured as > 0, we fork that specific number of sub-processes. Since we use processes and not threads, there is no shared memory between any server code. Note that multiple processes are not compatible with the autoreload module (or the ``debug=True`` option to `Platform`). When using multiple processes, no HTTPIOLoop can be created or referenced until after the call to ``TCPServer.start(n)``. """ assert not self._started self._started = True sett = self.sett if sett['multiprocess'] <= 0: # Single process #log.info("Starting server in single process mode ...") self.listen() else : # multi-process #log.info("Starting server in multi process mode ...") sockets = bind_sockets( sett['port'], sett['host'], None, sett['backlog'] ) process.fork_processes( sett['multiprocess'], sett['max_restart'] ) self.add_sockets( sockets ) # TODO : Setup logging for multiple process ? self.ioloop.start() # Block ! def stop(self): """Stops listening for new connections. Requests currently in progress may still continue after the server is stopped. """ for fd, sock in self._sockets.items() : self.ioloop.remove_handler(fd) sock.close() def handle_stream(self, stream, address): """Override to handle a new `IOStream` from an incoming connection.""" raise NotImplementedError() def _handle_connection( self, conn, address ): ssloptions = h.settingsfor( 'ssloptions.', self.sett ) is_ssl = ssloptions['keyfile'] and ssloptions['certfile'] if is_ssl : try: conn = ssl.wrap_socket( conn, server_side=True, do_handshake_on_connect=False, **ssloptions ) except ssl.SSLError as err: if err.args[0] == ssl.SSL_ERROR_EOF: return conn.close() else: raise except socket.error as err: if err.args[0] == errno.ECONNABORTED: return conn.close() else: raise try: if is_ssl : stream = HTTPSSLIOStream( conn, address, self.ioloop, self.sett, ssloptions=ssloptions ) else : stream = HTTPIOStream( conn, address, self.ioloop, self.sett ) self.handle_stream( stream, address ) except Exception: #log.error("Error in connection callback", exc_info=True) pass