00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 """A non-blocking, single-threaded TCP server."""
00018 from __future__ import absolute_import, division, print_function, with_statement
00019
00020 import errno
00021 import os
00022 import socket
00023
00024 from tornado.log import app_log
00025 from tornado.ioloop import IOLoop
00026 from tornado.iostream import IOStream, SSLIOStream
00027 from tornado.netutil import bind_sockets, add_accept_handler, ssl_wrap_socket
00028 from tornado import process
00029 from tornado.util import errno_from_exception
00030
00031 try:
00032 import ssl
00033 except ImportError:
00034
00035 ssl = None
00036
00037
00038 class TCPServer(object):
00039 r"""A non-blocking, single-threaded TCP server.
00040
00041 To use `TCPServer`, define a subclass which overrides the `handle_stream`
00042 method.
00043
00044 To make this server serve SSL traffic, send the ssl_options dictionary
00045 argument with the arguments required for the `ssl.wrap_socket` method,
00046 including "certfile" and "keyfile"::
00047
00048 TCPServer(ssl_options={
00049 "certfile": os.path.join(data_dir, "mydomain.crt"),
00050 "keyfile": os.path.join(data_dir, "mydomain.key"),
00051 })
00052
00053 `TCPServer` initialization follows one of three patterns:
00054
00055 1. `listen`: simple single-process::
00056
00057 server = TCPServer()
00058 server.listen(8888)
00059 IOLoop.instance().start()
00060
00061 2. `bind`/`start`: simple multi-process::
00062
00063 server = TCPServer()
00064 server.bind(8888)
00065 server.start(0) # Forks multiple sub-processes
00066 IOLoop.instance().start()
00067
00068 When using this interface, an `.IOLoop` must *not* be passed
00069 to the `TCPServer` constructor. `start` will always start
00070 the server on the default singleton `.IOLoop`.
00071
00072 3. `add_sockets`: advanced multi-process::
00073
00074 sockets = bind_sockets(8888)
00075 tornado.process.fork_processes(0)
00076 server = TCPServer()
00077 server.add_sockets(sockets)
00078 IOLoop.instance().start()
00079
00080 The `add_sockets` interface is more complicated, but it can be
00081 used with `tornado.process.fork_processes` to give you more
00082 flexibility in when the fork happens. `add_sockets` can
00083 also be used in single-process servers if you want to create
00084 your listening sockets in some way other than
00085 `~tornado.netutil.bind_sockets`.
00086
00087 .. versionadded:: 3.1
00088 The ``max_buffer_size`` argument.
00089 """
00090 def __init__(self, io_loop=None, ssl_options=None, max_buffer_size=None,
00091 read_chunk_size=None):
00092 self.io_loop = io_loop
00093 self.ssl_options = ssl_options
00094 self._sockets = {}
00095 self._pending_sockets = []
00096 self._started = False
00097 self.max_buffer_size = max_buffer_size
00098 self.read_chunk_size = None
00099
00100
00101
00102
00103
00104 if self.ssl_options is not None and isinstance(self.ssl_options, dict):
00105
00106 if 'certfile' not in self.ssl_options:
00107 raise KeyError('missing key "certfile" in ssl_options')
00108
00109 if not os.path.exists(self.ssl_options['certfile']):
00110 raise ValueError('certfile "%s" does not exist' %
00111 self.ssl_options['certfile'])
00112 if ('keyfile' in self.ssl_options and
00113 not os.path.exists(self.ssl_options['keyfile'])):
00114 raise ValueError('keyfile "%s" does not exist' %
00115 self.ssl_options['keyfile'])
00116
00117 def listen(self, port, address=""):
00118 """Starts accepting connections on the given port.
00119
00120 This method may be called more than once to listen on multiple ports.
00121 `listen` takes effect immediately; it is not necessary to call
00122 `TCPServer.start` afterwards. It is, however, necessary to start
00123 the `.IOLoop`.
00124 """
00125 sockets = bind_sockets(port, address=address)
00126 self.add_sockets(sockets)
00127
00128 def add_sockets(self, sockets):
00129 """Makes this server start accepting connections on the given sockets.
00130
00131 The ``sockets`` parameter is a list of socket objects such as
00132 those returned by `~tornado.netutil.bind_sockets`.
00133 `add_sockets` is typically used in combination with that
00134 method and `tornado.process.fork_processes` to provide greater
00135 control over the initialization of a multi-process server.
00136 """
00137 if self.io_loop is None:
00138 self.io_loop = IOLoop.current()
00139
00140 for sock in sockets:
00141 self._sockets[sock.fileno()] = sock
00142 add_accept_handler(sock, self._handle_connection,
00143 io_loop=self.io_loop)
00144
00145 def add_socket(self, socket):
00146 """Singular version of `add_sockets`. Takes a single socket object."""
00147 self.add_sockets([socket])
00148
00149 def bind(self, port, address=None, family=socket.AF_UNSPEC, backlog=128):
00150 """Binds this server to the given port on the given address.
00151
00152 To start the server, call `start`. If you want to run this server
00153 in a single process, you can call `listen` as a shortcut to the
00154 sequence of `bind` and `start` calls.
00155
00156 Address may be either an IP address or hostname. If it's a hostname,
00157 the server will listen on all IP addresses associated with the
00158 name. Address may be an empty string or None to listen on all
00159 available interfaces. Family may be set to either `socket.AF_INET`
00160 or `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise
00161 both will be used if available.
00162
00163 The ``backlog`` argument has the same meaning as for
00164 `socket.listen <socket.socket.listen>`.
00165
00166 This method may be called multiple times prior to `start` to listen
00167 on multiple ports or interfaces.
00168 """
00169 sockets = bind_sockets(port, address=address, family=family,
00170 backlog=backlog)
00171 if self._started:
00172 self.add_sockets(sockets)
00173 else:
00174 self._pending_sockets.extend(sockets)
00175
00176 def start(self, num_processes=1):
00177 """Starts this server in the `.IOLoop`.
00178
00179 By default, we run the server in this process and do not fork any
00180 additional child process.
00181
00182 If num_processes is ``None`` or <= 0, we detect the number of cores
00183 available on this machine and fork that number of child
00184 processes. If num_processes is given and > 1, we fork that
00185 specific number of sub-processes.
00186
00187 Since we use processes and not threads, there is no shared memory
00188 between any server code.
00189
00190 Note that multiple processes are not compatible with the autoreload
00191 module (or the ``autoreload=True`` option to `tornado.web.Application`
00192 which defaults to True when ``debug=True``).
00193 When using multiple processes, no IOLoops can be created or
00194 referenced until after the call to ``TCPServer.start(n)``.
00195 """
00196 assert not self._started
00197 self._started = True
00198 if num_processes != 1:
00199 process.fork_processes(num_processes)
00200 sockets = self._pending_sockets
00201 self._pending_sockets = []
00202 self.add_sockets(sockets)
00203
00204 def stop(self):
00205 """Stops listening for new connections.
00206
00207 Requests currently in progress may still continue after the
00208 server is stopped.
00209 """
00210 for fd, sock in self._sockets.items():
00211 self.io_loop.remove_handler(fd)
00212 sock.close()
00213
00214 def handle_stream(self, stream, address):
00215 """Override to handle a new `.IOStream` from an incoming connection."""
00216 raise NotImplementedError()
00217
00218 def _handle_connection(self, connection, address):
00219 if self.ssl_options is not None:
00220 assert ssl, "Python 2.6+ and OpenSSL required for SSL"
00221 try:
00222 connection = ssl_wrap_socket(connection,
00223 self.ssl_options,
00224 server_side=True,
00225 do_handshake_on_connect=False)
00226 except ssl.SSLError as err:
00227 if err.args[0] == ssl.SSL_ERROR_EOF:
00228 return connection.close()
00229 else:
00230 raise
00231 except socket.error as err:
00232
00233
00234
00235
00236
00237
00238
00239
00240
00241
00242 if errno_from_exception(err) in (errno.ECONNABORTED, errno.EINVAL):
00243 return connection.close()
00244 else:
00245 raise
00246 try:
00247 if self.ssl_options is not None:
00248 stream = SSLIOStream(connection, io_loop=self.io_loop,
00249 max_buffer_size=self.max_buffer_size,
00250 read_chunk_size=self.read_chunk_size)
00251 else:
00252 stream = IOStream(connection, io_loop=self.io_loop,
00253 max_buffer_size=self.max_buffer_size,
00254 read_chunk_size=self.read_chunk_size)
00255 self.handle_stream(stream, address)
00256 except Exception:
00257 app_log.error("Error in connection callback", exc_info=True)