text: # Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details.
  """TCP/SSL server"""
  import sys
  import _socket
  from gevent.baseserver import BaseServer
  from gevent.socket import EWOULDBLOCK, socket
  from gevent._compat import PYPY, PY3
  
  __all__ = ['StreamServer', 'DatagramServer']
  
  
  if sys.platform == 'win32':
      # SO_REUSEADDR on Windows does not mean the same thing as on *nix (issue #217)
      DEFAULT_REUSE_ADDR = None
  else:
      DEFAULT_REUSE_ADDR = 1
  
  
  class StreamServer(BaseServer):
      """
      A generic TCP server.
  
      Accepts connections on a listening socket and spawns user-provided
      *handle* function for each connection with 2 arguments: the client
      socket and the client address.
  
      Note that although the errors in a successfully spawned handler
      will not affect the server or other connections, the errors raised
      by :func:`accept` and *spawn* cause the server to stop accepting
      for a short amount of time. The exact period depends on the values
      of :attr:`min_delay` and :attr:`max_delay` attributes.
  
      The delay starts with :attr:`min_delay` and doubles with each
      successive error until it reaches :attr:`max_delay`. A successful
      :func:`accept` resets the delay to :attr:`min_delay` again.
  
      See :class:`~gevent.baseserver.BaseServer` for information on defining the *handle*
      function and important restrictions on it.
  
      **SSL Support**
  
      The server can optionally work in SSL mode when given the correct
      keyword arguments. (That is, the presence of any keyword arguments
      will trigger SSL mode.) On Python 2.7.9 and later (any Python
      version that supports the :class:`ssl.SSLContext`), this can be
      done with a configured ``SSLContext``. On any Python version, it
      can be done by passing the appropriate arguments for
      :func:`ssl.wrap_socket`.
  
      The incoming socket will be wrapped into an SSL socket before
      being passed to the *handle* function.
  
      If the *ssl_context* keyword argument is present, it should
      contain an :class:`ssl.SSLContext`. The remaining keyword
      arguments are passed to the :meth:`ssl.SSLContext.wrap_socket`
      method of that object. Depending on the Python version, supported arguments
      may include:
  
      - server_hostname
      - suppress_ragged_eofs
      - do_handshake_on_connect
  
      .. caution:: When using an SSLContext, it should either be
         imported from :mod:`gevent.ssl`, or the process needs to be monkey-patched.
         If the process is not monkey-patched and you pass the standard library
         SSLContext, the resulting client sockets will not cooperate with gevent.
  
      Otherwise, keyword arguments are assumed to apply to :func:`ssl.wrap_socket`.
      These keyword arguments bay include:
  
      - keyfile
      - certfile
      - cert_reqs
      - ssl_version
      - ca_certs
      - suppress_ragged_eofs
      - do_handshake_on_connect
      - ciphers
  
      .. versionchanged:: 1.2a2
         Add support for the *ssl_context* keyword argument.
  
      """
      # the default backlog to use if none was provided in __init__
      backlog = 256
  
      reuse_addr = DEFAULT_REUSE_ADDR
  
      def __init__(self, listener, handle=None, backlog=None, spawn='default', **ssl_args):
          BaseServer.__init__(self, listener, handle=handle, spawn=spawn)
          try:
              if ssl_args:
                  ssl_args.setdefault('server_side', True)
                  if 'ssl_context' in ssl_args:
                      ssl_context = ssl_args.pop('ssl_context')
                      self.wrap_socket = ssl_context.wrap_socket
                      self.ssl_args = ssl_args
                  else:
                      from gevent.ssl import wrap_socket
                      self.wrap_socket = wrap_socket
                      self.ssl_args = ssl_args
              else:
                  self.ssl_args = None
              if backlog is not None:
                  if hasattr(self, 'socket'):
                      raise TypeError('backlog must be None when a socket instance is passed')
                  self.backlog = backlog
          except:
              self.close()
              raise
  
      @property
      def ssl_enabled(self):
          return self.ssl_args is not None
  
      def set_listener(self, listener):
          BaseServer.set_listener(self, listener)
          try:
              self.socket = self.socket._sock
          except AttributeError:
              pass
  
      def init_socket(self):
          if not hasattr(self, 'socket'):
              # FIXME: clean up the socket lifetime
              # pylint:disable=attribute-defined-outside-init
              self.socket = self.get_listener(self.address, self.backlog, self.family)
              self.address = self.socket.getsockname()
          if self.ssl_args:
              self._handle = self.wrap_socket_and_handle
          else:
              self._handle = self.handle
  
      @classmethod
      def get_listener(cls, address, backlog=None, family=None):
          if backlog is None:
              backlog = cls.backlog
          return _tcp_listener(address, backlog=backlog, reuse_addr=cls.reuse_addr, family=family)
  
      if PY3:
  
          def do_read(self):
              sock = self.socket
              try:
                  fd, address = sock._accept()
              except BlockingIOError: # python 2: pylint: disable=undefined-variable
                  if not sock.timeout:
                      return
                  raise
              sock = socket(sock.family, sock.type, sock.proto, fileno=fd)
              # XXX Python issue #7995?
              return sock, address
  
      else:
  
          def do_read(self):
              try:
                  client_socket, address = self.socket.accept()
              except _socket.error as err:
                  if err.args[0] == EWOULDBLOCK:
                      return
                  raise
              sockobj = socket(_sock=client_socket)
              if PYPY:
                  client_socket._drop()
              return sockobj, address
  
      def do_close(self, sock, *args):
          sock.close()
  
      def wrap_socket_and_handle(self, client_socket, address):
          # used in case of ssl sockets
          ssl_socket = self.wrap_socket(client_socket, **self.ssl_args)
          return self.handle(ssl_socket, address)
  
  
  class DatagramServer(BaseServer):
      """A UDP server"""
  
      reuse_addr = DEFAULT_REUSE_ADDR
  
      def __init__(self, *args, **kwargs):
          # The raw (non-gevent) socket, if possible
          self._socket = None
          BaseServer.__init__(self, *args, **kwargs)
          from gevent.lock import Semaphore
          self._writelock = Semaphore()
  
      def init_socket(self):
          if not hasattr(self, 'socket'):
              # FIXME: clean up the socket lifetime
              # pylint:disable=attribute-defined-outside-init
              self.socket = self.get_listener(self.address, self.family)
              self.address = self.socket.getsockname()
          self._socket = self.socket
          try:
              self._socket = self._socket._sock
          except AttributeError:
              pass
  
      @classmethod
      def get_listener(cls, address, family=None):
          return _udp_socket(address, reuse_addr=cls.reuse_addr, family=family)
  
      def do_read(self):
          try:
              data, address = self._socket.recvfrom(8192)
          except _socket.error as err:
              if err.args[0] == EWOULDBLOCK:
                  return
              raise
          return data, address
  
      def sendto(self, *args):
          self._writelock.acquire()
          try:
              self.socket.sendto(*args)
          finally:
              self._writelock.release()
  
  
  def _tcp_listener(address, backlog=50, reuse_addr=None, family=_socket.AF_INET):
      """A shortcut to create a TCP socket, bind it and put it into listening state."""
      sock = socket(family=family)
      if reuse_addr is not None:
          sock.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, reuse_addr)
      try:
          sock.bind(address)
      except _socket.error as ex:
          strerror = getattr(ex, 'strerror', None)
          if strerror is not None:
              ex.strerror = strerror + ': ' + repr(address)
          raise
      sock.listen(backlog)
      sock.setblocking(0)
      return sock
  
  
  def _udp_socket(address, backlog=50, reuse_addr=None, family=_socket.AF_INET):
      # backlog argument for compat with tcp_listener
      # pylint:disable=unused-argument
  
      # we want gevent.socket.socket here
      sock = socket(family=family, type=_socket.SOCK_DGRAM)
      if reuse_addr is not None:
          sock.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, reuse_addr)
      try:
          sock.bind(address)
      except _socket.error as ex:
          strerror = getattr(ex, 'strerror', None)
          if strerror is not None:
              ex.strerror = strerror + ': ' + repr(address)
          raise
      return sock
  