Package web2py :: Package gluon :: Module rocket |
[hide private]
[frames] | no frames]

# Source Code for Module web2py.gluon.rocket

       1  # -*- coding: utf-8 -*-
       2
       3  # This file is part of the Rocket Web Server
       4  # Copyright (c) 2010 Timothy Farrell
       5
       6  # Import System Modules
       7  import sys
       8  import errno
       9  import socket
      10  import logging
      11  import platform
      12
      13  # Define Constants
      14  VERSION = '1.2.2'
      15  SERVER_NAME = socket.gethostname()
      16  SERVER_SOFTWARE = 'Rocket %s' % VERSION
      17  HTTP_SERVER_SOFTWARE = '%s Python/%s' % (SERVER_SOFTWARE, sys.version.split(' ')[0])
      18  BUF_SIZE = 16384
      19  SOCKET_TIMEOUT = 1 # in secs
      20  THREAD_STOP_CHECK_INTERVAL = 1 # in secs, How often should threads check for a server stop message?
      21  IS_JYTHON = platform.system() == 'Java' # Handle special cases for Jython
      22  IGNORE_ERRORS_ON_CLOSE = set([errno.ECONNABORTED, errno.ECONNRESET])
      23  DEFAULT_LISTEN_QUEUE_SIZE = 5
      24  DEFAULT_MIN_THREADS = 10
      25  DEFAULT_MAX_THREADS = 0
      26  DEFAULTS = dict(LISTEN_QUEUE_SIZE = DEFAULT_LISTEN_QUEUE_SIZE,
      27                  MIN_THREADS = DEFAULT_MIN_THREADS,
      28                  MAX_THREADS = DEFAULT_MAX_THREADS)
      29
      30  PY3K = sys.version_info[0] > 2
      31
      32 -class NullHandler(logging.Handler):
      33      "A Logging handler to prevent library errors."
      34 -    def emit(self, record):
      35          pass
      36
      37  if PY3K:
      38 -    def b(val):
      39          """ Convert string/unicode/bytes literals into bytes.  This allows for
      40          the same code to run on Python 2.x and 3.x. """
      41          if isinstance(val, str):
      42              return val.encode()
      43          else:
      44              return val
      45
      46 -    def u(val, encoding="us-ascii"):
      47          """ Convert bytes into string/unicode.  This allows for the
      48          same code to run on Python 2.x and 3.x. """
      49          if isinstance(val, bytes):
      50              return val.decode(encoding)
      51          else:
      52              return val
      53
      54  else:
      55 -    def b(val):
      56          """ Convert string/unicode/bytes literals into bytes.  This allows for
      57          the same code to run on Python 2.x and 3.x. """
      58          if isinstance(val, unicode):
      59              return val.encode()
      60          else:
      61              return val
      62
      63 -    def u(val, encoding="us-ascii"):
      64          """ Convert bytes into string/unicode.  This allows for the
      65          same code to run on Python 2.x and 3.x. """
      66          if isinstance(val, str):
      67              return val.decode(encoding)
      68          else:
      69              return val
      70
      71  # Import Package Modules
      72  # package imports removed in monolithic build
      73
      74  __all__ = ['VERSION', 'SERVER_SOFTWARE', 'HTTP_SERVER_SOFTWARE', 'BUF_SIZE',
      75             'IS_JYTHON', 'IGNORE_ERRORS_ON_CLOSE', 'DEFAULTS', 'PY3K', 'b', 'u',
      76             'Rocket', 'CherryPyWSGIServer', 'SERVER_NAME', 'NullHandler']
      77
      78  # Monolithic build...end of module: rocket\__init__.py
      79  # Monolithic build...start of module: rocket\connection.py
      80
      81  # Import System Modules
      82  import sys
      83  import time
      84  import socket
      85  try:
      86      import ssl
      87      has_ssl = True
      88  except ImportError:
      89      has_ssl = False
      90  # package imports removed in monolithic build
      91
      92 -class Connection(object):
      93      __slots__ = [
      94          'setblocking',
      95          'sendall',
      96          'shutdown',
      97          'makefile',
      98          'fileno',
      99          'client_addr',
     100          'client_port',
     101          'server_port',
     102          'socket',
     103          'start_time',
     104          'ssl',
     105          'secure'
     106      ]
     107
     108 -    def __init__(self, sock_tuple, port, secure=False):
     109          self.client_addr, self.client_port = sock_tuple[1]
     110          self.server_port = port
     111          self.socket = sock_tuple[0]
     112          self.start_time = time.time()
     113          self.ssl = has_ssl and isinstance(self.socket, ssl.SSLSocket)
     114          self.secure = secure
     115
     116          if IS_JYTHON:
     117              # In Jython we must set TCP_NODELAY here since it does not
     118              # inherit from the listening socket.
     119              # See: http://bugs.jython.org/issue1309
     120              self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
     121
     122          self.socket.settimeout(SOCKET_TIMEOUT)
     123
     124          self.sendall = self.socket.sendall
     125          self.shutdown = self.socket.shutdown
     126          self.fileno = self.socket.fileno
     127          self.makefile = self.socket.makefile
     128          self.setblocking = self.socket.setblocking
     129
     130 -    def close(self):
     131          if hasattr(self.socket, '_sock'):
     132              try:
     133                  self.socket._sock.close()
     134              except socket.error:
     135                  info = sys.exc_info()
     136                  if info[1].errno != socket.EBADF:
     137                      raise info[1]
     138                  else:
     139                      pass
     140          self.socket.close()
     141
     142  # Monolithic build...end of module: rocket\connection.py
     143  # Monolithic build...start of module: rocket\listener.py
     144
     145  # Import System Modules
     146  import os
     147  import socket
     148  import logging
     149  import traceback
     150  from threading import Thread
     151
     152  try:
     153      import ssl
     154      from ssl import SSLError
     155      has_ssl = True
     156  except ImportError:
     157      has_ssl = False
     158 -    class SSLError(socket.error):
     159          pass
     160  # Import Package Modules
     161  # package imports removed in monolithic build
     162
     163 -class Listener(Thread):
     164      """The Listener class is a class responsible for accepting connections
     165      and queuing them to be processed by a worker thread."""
     166
     167 -    def __init__(self, interface, queue_size, active_queue, *args, **kwargs):
     168          Thread.__init__(self, *args, **kwargs)
     169
     170          # Instance variables
     171          self.active_queue = active_queue
     172          self.interface = interface
     173          self.addr = interface[0]
     174          self.port = interface[1]
     175          self.secure = len(interface) == 4 and \
     176                        os.path.exists(interface[2]) and \
     177                        os.path.exists(interface[3])
     178          self.ready = False
     179
     180          # Error Log
     181          self.err_log = logging.getLogger('Rocket.Errors.Port%i' % self.port)
     182          self.err_log.addHandler(NullHandler())
     183
     184          # Build the socket
     185          listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     186
     187          if not listener:
     188              self.err_log.error("Failed to get socket.")
     189              return
     190
     191          if self.secure:
     192              if not has_ssl:
     193                  self.err_log.error("ssl module required to serve HTTPS.")
     194                  return
     195              elif not os.path.exists(interface[2]):
     196                  data = (interface[2], interface[0], interface[1])
     197                  self.err_log.error("Cannot find key file "
     198                            "'%s'.  Cannot bind to %s:%s" % data)
     199                  return
     200              elif not os.path.exists(interface[3]):
     201                  data = (interface[3], interface[0], interface[1])
     202                  self.err_log.error("Cannot find certificate file "
     203                            "'%s'.  Cannot bind to %s:%s" % data)
     204                  return
     205
     206          # Set socket options
     207          try:
     208              listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     209          except:
     210              msg = "Cannot share socket.  Using %s:%i exclusively."
     211              self.err_log.warning(msg % (self.addr, self.port))
     212
     213          try:
     214              if not IS_JYTHON:
     215                  listener.setsockopt(socket.IPPROTO_TCP,
     216                                      socket.TCP_NODELAY,
     217                                      1)
     218          except:
     219              msg = "Cannot set TCP_NODELAY, things might run a little slower"
     220              self.err_log.warning(msg)
     221
     222          try:
     223              listener.bind((self.addr, self.port))
     224          except:
     225              msg = "Socket %s:%i in use by other process and it won't share."
     226              self.err_log.error(msg % (self.addr, self.port))
     227          else:
     228              # We want socket operations to timeout periodically so we can
     229              # check if the server is shutting down
     230              listener.settimeout(THREAD_STOP_CHECK_INTERVAL)
     231              # Listen for new connections allowing queue_size number of
     232              # connections to wait before rejecting a connection.
     233              listener.listen(queue_size)
     234
     235              self.listener = listener
     236
     237              self.ready = True
     238
     239 -    def wrap_socket(self, sock):
     240          try:
     241              sock = ssl.wrap_socket(sock,
     242                                     keyfile = self.interface[2],
     243                                     certfile = self.interface[3],
     244                                     server_side = True,
     245                                     ssl_version = ssl.PROTOCOL_SSLv23)
     246          except SSLError:
     247              # Generally this happens when an HTTP request is received on a
     248              # secure socket. We don't do anything because it will be detected
     249              # by Worker and dealt with appropriately.
     250              pass
     251
     252          return sock
     253
     254
     255 -    def run(self):
     256          if not self.ready:
     257              self.err_log.warning('Listener started when not ready.')
     258              return
     259
     260          if __debug__:
     261              self.err_log.debug('Entering main loop.')
     262          while True:
     263              try:
     264                  sock, addr = self.listener.accept()
     265
     266                  if self.secure:
     267                      sock = self.wrap_socket(sock)
     268
     269                  self.active_queue.put(((sock, addr),
     270                                         self.interface[1],
     271                                         self.secure))
     272
     273              except socket.timeout:
     274                  # socket.timeout will be raised every THREAD_STOP_CHECK_INTERVAL
     275                  # seconds.  When that happens, we check if it's time to die.
     276
     277                  if not self.ready:
     278                      if __debug__:
     279                          self.err_log.debug('Listener exiting.')
     280                      return
     281                  else:
     282                      continue
     283              except:
     284                  self.err_log.error(str(traceback.format_exc()))
     285
     286  # Monolithic build...end of module: rocket\listener.py
     287  # Monolithic build...start of module: rocket\main.py
     288
     289  # Import System Modules
     290  import sys
     291  import time
     292  import socket
     293  import logging
     294  import traceback
     295
     296  try:
     297      from queue import Queue
     298  except ImportError:
     299      from Queue import Queue
     300
     301  # Import Package Modules
     302  # package imports removed in monolithic build
     303
     304
     305
     306
     307
     308  # Setup Logging
     309  log = logging.getLogger('Rocket')
     310  log.addHandler(NullHandler())
     311
     312 -class Rocket(object):
     313      """The Rocket class is responsible for handling threads and accepting and
     314      dispatching connections."""
     315
     316 -    def __init__(self,
     317                   interfaces = ('127.0.0.1', 8000),
     318                   method = 'wsgi',
     319                   app_info = None,
     320                   min_threads = None,
     321                   max_threads = None,
     322                   queue_size = None,
     323                   timeout = 600,
     324                   handle_signals = True):
     325
     326          self.handle_signals = handle_signals
     327
     328          if not isinstance(interfaces, list):
     329              self.interfaces = [interfaces]
     330          else:
     331              self.interfaces = interfaces
     332
     333          if min_threads is None:
     334              min_threads = DEFAULTS['MIN_THREADS']
     335
     336          if max_threads is None:
     337              max_threads = DEFAULTS['MAX_THREADS']
     338
     339          if not queue_size:
     340              if hasattr(socket, 'SOMAXCONN'):
     341                  queue_size = socket.SOMAXCONN
     342              else:
     343                  queue_size = DEFAULTS['LISTEN_QUEUE_SIZE']
     344
     345          if max_threads and queue_size > max_threads:
     346              queue_size = max_threads
     347
     348          if isinstance(app_info, dict):
     349              app_info['server_software'] = SERVER_SOFTWARE
     350
     351          monitor_queue = Queue()
     352          active_queue = Queue()
     353
     354          self._monitor = Monitor(monitor_queue, active_queue, timeout)
     355
     356          self._threadpool = ThreadPool(get_method(method),
     357                                        app_info = app_info,
     358                                        active_queue=active_queue,
     359                                        monitor_queue = monitor_queue,
     360                                        min_threads=min_threads,
     361                                        max_threads=max_threads)
     362
     363          # Build our socket listeners
     364          self.listeners = [Listener(i, queue_size, active_queue) for i in self.interfaces]
     365          for ndx in range(len(self.listeners)-1, 0, -1):
     366              if not self.listeners[ndx].ready:
     367                  del self.listeners[ndx]
     368
     369          if not self.listeners:
     370              log.critical("No interfaces to listen on...closing.")
     371              sys.exit(1)
     372
     373 -    def _sigterm(self, signum, frame):
     374          log.info('Received SIGTERM')
     375          self.stop()
     376
     377 -    def _sighup(self, signum, frame):
     378          log.info('Received SIGHUP')
     379          self.restart()
     380
     381 -    def start(self):
     382          log.info('Starting %s' % SERVER_SOFTWARE)
     383
     384          # Set up our shutdown signals
     385          if self.handle_signals:
     386              try:
     387                  import signal
     388                  signal.signal(signal.SIGTERM, self._sigterm)
     389                  signal.signal(signal.SIGUSR1, self._sighup)
     390              except:
     391                  log.debug('This platform does not support signals.')
     392
     393          # Start our worker threads
     394          self._threadpool.start()
     395
     396          # Start our monitor thread
     397          self._monitor.setDaemon(True)
     398          self._monitor.start()
     399
     400          # I know that EXPR and A or B is bad but I'm keeping it for Py2.4
     401          # compatibility.
     402          str_extract = lambda l: (l.addr, l.port, l.secure and '*' or '')
     403
     404          msg = 'Listening on sockets: '
     405          msg += ', '.join(['%s:%i%s' % str_extract(l) for l in self.listeners])
     406          log.info(msg)
     407
     408          for l in self.listeners:
     409              l.start()
     410
     411          tp = self._threadpool
     412          dynamic_resize = tp.dynamic_resize
     413
     414          while not tp.stop_server:
     415              try:
     416                  dynamic_resize()
     417                  time.sleep(THREAD_STOP_CHECK_INTERVAL)
     418              except KeyboardInterrupt:
     419                  # Capture a keyboard interrupt when running from a console
     420                  break
     421              except:
     422                  if not tp.stop_server:
     423                      log.error(str(traceback.format_exc()))
     424                      continue
     425
     426          return self.stop()
     427
     428 -    def stop(self, stoplogging = True):
     429          log.info("Stopping Server")
     430
     431          # Stop listeners
     432          for l in self.listeners:
     433              l.ready = False
     434              if l.isAlive():
     435                  l.join()
     436
     437          # Stop Worker threads
     438          self._threadpool.stop()
     439
     440          # Stop Monitor
     441          self._monitor.stop()
     442          if self._monitor.isAlive():
     443              self._monitor.join()
     444
     445          if stoplogging:
     446              logging.shutdown()
     447
     448 -    def restart(self):
     449          self.stop(False)
     450          self.start()
     451
     452 -def CherryPyWSGIServer(bind_addr,
     453                         wsgi_app,
     454                         numthreads = 10,
     455                         server_name = None,
     456                         max = -1,
     457                         request_queue_size = 5,
     458                         timeout = 10,
     459                         shutdown_timeout = 5):
     460      """ A Cherrypy wsgiserver-compatible wrapper. """
     461      max_threads = max
     462      if max_threads < 0:
     463          max_threads = 0
     464      return Rocket(bind_addr, 'wsgi', {'wsgi_app': wsgi_app},
     465                    min_threads = numthreads,
     466                    max_threads = max_threads,
     467                    queue_size = request_queue_size,
     468                    timeout = timeout)
     469
     470  # Monolithic build...end of module: rocket\main.py
     471  # Monolithic build...start of module: rocket\monitor.py
     472
     473  # Import System Modules
     474  import time
     475  import logging
     476  import select
     477  from threading import Thread
     478
     479  # Import Package Modules
     480  # package imports removed in monolithic build
     481
     482 -class Monitor(Thread):
     483      # Monitor worker class.
     484
     485 -    def __init__(self,
     486                   monitor_queue,
     487                   active_queue,
     488                   timeout,
     489                   *args,
     490                   **kwargs):
     491
     492          Thread.__init__(self, *args, **kwargs)
     493
     494          # Instance Variables
     495          self.monitor_queue = monitor_queue
     496          self.active_queue = active_queue
     497          self.timeout = timeout
     498
     499          self.connections = set()
     500          self.active = False
     501
     502 -    def run(self):
     503          self.name = self.getName()
     504          self.log = logging.getLogger('Rocket.Monitor')
     505          self.log.addHandler(NullHandler())
     506
     507          self.active = True
     508          conn_list = list()
     509          list_changed = False
     510
     511          if __debug__:
     512              self.log.debug('Entering monitor loop.')
     513
     514          # Enter thread main loop
     515          while self.active:
     516              # Move the queued connections to the selection pool
     517              while not self.monitor_queue.empty() or not len(self.connections):
     518                  if __debug__:
     519                      self.log.debug('In "receive timed-out connections" loop.')
     520
     521                  c = self.monitor_queue.get()
     522
     523                  if c is None:
     524                      # A non-client is a signal to die
     525                      if __debug__:
     526                          self.log.debug('Received a death threat.')
     527                      return
     528
     529                  self.log.debug('Received a timed out connection.')
     530
     531                  if __debug__:
     532                      assert(c not in self.connections)
     533
     534                  if IS_JYTHON:
     535                      # Jython requires a socket to be in Non-blocking mode in
     536                      # order to select on it.
     537                      c.setblocking(False)
     538
     539                  if __debug__:
     540                      self.log.debug('Adding connection to monitor list.')
     541
     542                  self.connections.add(c)
     543                  list_changed = True
     544
     545              # Wait on those connections
     546              self.log.debug('Blocking on connections')
     547              if list_changed:
     548                  conn_list = list(self.connections)
     549                  list_changed = False
     550
     551              try:
     552                  readable = select.select(conn_list,
     553                                           [],
     554                                           [],
     555                                           THREAD_STOP_CHECK_INTERVAL)[0]
     556              except:
     557                  if self.active:
     558                      raise
     559                  else:
     560                      break
     561
     562              # If we have any readable connections, put them back
     563              for r in readable:
     564                  if __debug__:
     565                      self.log.debug('Restoring readable connection')
     566
     567                  if IS_JYTHON:
     568                      # Jython requires a socket to be in Non-blocking mode in
     569                      # order to select on it, but the rest of the code requires
     570                      # that it be in blocking mode.
     571                      r.setblocking(True)
     572
     573                  r.start_time = time.time()
     574                  self.active_queue.put(r)
     575
     576                  self.connections.remove(r)
     577                  list_changed = True
     578
     579              # If we have any stale connections, kill them off.
     580              if self.timeout:
     581                  now = time.time()
     582                  stale = set()
     583                  for c in self.connections:
     584                      if (now - c.start_time) >= self.timeout:
     585                          stale.add(c)
     586
     587                  for c in stale:
     588                      if __debug__:
     589                          # "EXPR and A or B" kept for Py2.4 compatibility
     590                          data = (c.client_addr, c.server_port, c.ssl and '*' or '')
     591                          self.log.debug('Flushing stale connection: %s:%i%s' % data)
     592
     593                      self.connections.remove(c)
     594                      list_changed = True
     595
     596                      try:
     597                          c.close()
     598                      finally:
     599                          del c
     600
     601 -    def stop(self):
     602          self.active = False
     603
     604          if __debug__:
     605              self.log.debug('Flushing waiting connections')
     606
     607          for c in self.connections:
     608              try:
     609                  c.close()
     610              finally:
     611                  del c
     612
     613          if __debug__:
     614              self.log.debug('Flushing queued connections')
     615
     616          while not self.monitor_queue.empty():
     617              c = self.monitor_queue.get()
     618
     619              if c is None:
     620                  continue
     621
     622              try:
     623                  c.close()
     624              finally:
     625                  del c
     626
     627          # Place a None sentry value to cause the monitor to die.
     628          self.monitor_queue.put(None)
     629
     630  # Monolithic build...end of module: rocket\monitor.py
     631  # Monolithic build...start of module: rocket\threadpool.py
     632
     633  # Import System Modules
     634  import logging
     635  # Import Package Modules
     636  # package imports removed in monolithic build
     637
     638  # Setup Logging
     639  log = logging.getLogger('Rocket.Errors.ThreadPool')
     640  log.addHandler(NullHandler())
     641
     642 -class ThreadPool:
     643      """The ThreadPool class is a container class for all the worker threads. It
     644      manages the number of actively running threads."""
     645
     646 -    def __init__(self,
     647                   method,
     648                   app_info,
     649                   active_queue,
     650                   monitor_queue,
     651                   min_threads=DEFAULTS['MIN_THREADS'],
     652                   max_threads=DEFAULTS['MAX_THREADS'],
     653                   ):
     654
     655          if __debug__:
     656              log.debug("Initializing ThreadPool.")
     657
     658          self.check_for_dead_threads = 0
     659          self.active_queue = active_queue
     660
     661          self.worker_class = method
     662          self.min_threads = min_threads
     663          self.max_threads = max_threads
     664          self.monitor_queue = monitor_queue
     665          self.stop_server = False
     666
     667          # TODO - Optimize this based on some real-world usage data
     668          self.grow_threshold = int(max_threads/10) + 2
     669
     670          if not isinstance(app_info, dict):
     671              app_info = dict()
     672
     673          app_info.update(max_threads=max_threads,
     674                          min_threads=min_threads)
     675
     676          self.app_info = app_info
     677
     678          self.threads = set()
     679          for x in range(min_threads):
     680              worker = self.worker_class(app_info,
     681                                         self.active_queue,
     682                                         self.monitor_queue)
     683              self.threads.add(worker)
     684
     685 -    def start(self):
     686          self.stop_server = False
     687          if __debug__:
     688              log.debug("Starting threads.")
     689
     690          for thread in self.threads:
     691              thread.setDaemon(True)
     692              thread.start()
     693
     694 -    def stop(self):
     695          if __debug__:
     696              log.debug("Stopping threads.")
     697
     698          self.stop_server = True
     699
     700          # Prompt the threads to die
     701          for t in self.threads:
     702              self.active_queue.put(None)
     703
     704          # Give them the gun
     705          for t in self.threads:
     706              t.kill()
     707
     708          # Wait until they pull the trigger
     709          for t in self.threads:
     710              t.join()
     711
     712          # Clean up the mess
     713          self.bring_out_your_dead()
     714
     715 -    def bring_out_your_dead(self):
     716          # Remove dead threads from the pool
     717
     718          dead_threads = [t for t in self.threads if not t.isAlive()]
     719          for t in dead_threads:
     720              if __debug__:
     721                  log.debug("Removing dead thread: %s." % t.getName())
     722              try:
     723                  # Py2.4 complains here so we put it in a try block
     724                  self.threads.remove(t)
     725              except:
     726                  pass
     727          self.check_for_dead_threads -= len(dead_threads)
     728
     729 -    def grow(self, amount=None):
     730          if self.stop_server:
     731              return
     732
     733          if not amount:
     734              amount = self.max_threads
     735
     736          amount = min([amount, self.max_threads - len(self.threads)])
     737
     738          if __debug__:
     739              log.debug("Growing by %i." % amount)
     740
     741          for x in range(amount):
     742              worker = self.worker_class(self.app_info,
     743                                         self.active_queue,
     744                                         self.monitor_queue)
     745
     746              worker.setDaemon(True)
     747              self.threads.add(worker)
     748              worker.start()
     749
     750 -    def shrink(self, amount=1):
     751          if __debug__:
     752              log.debug("Shrinking by %i." % amount)
     753
     754          self.check_for_dead_threads += amount
     755
     756          for x in range(amount):
     757              self.active_queue.put(None)
     758
     759 -    def dynamic_resize(self):
     760          if (self.max_threads > self.min_threads or self.max_threads == 0):
     761              if self.check_for_dead_threads > 0:
     762                  self.bring_out_your_dead()
     763
     764              queueSize = self.active_queue.qsize()
     765              threadCount = len(self.threads)
     766
     767              if __debug__:
     768                  log.debug("Examining ThreadPool. %i threads and %i Q'd conxions"
     769                            % (threadCount, queueSize))
     770
     771              if queueSize == 0 and threadCount > self.min_threads:
     772                  self.shrink()
     773
     774              elif queueSize > self.grow_threshold:
     775
     776                  self.grow(queueSize)
     777
     778  # Monolithic build...end of module: rocket\threadpool.py
     779  # Monolithic build...start of module: rocket\worker.py
     780
     781  # Import System Modules
     782  import re
     783  import sys
     784  import socket
     785  import logging
     786  import traceback
     787  #from wsgiref.headers import Headers
     788  from threading import Thread
     789  from datetime import datetime
     790
     791  try:
     792      from urllib import unquote
     793  except ImportError:
     794      from urllib.parse import unquote
     795
     796  try:
     797      from io import StringIO
     798  except ImportError:
     799      try:
     800          from cStringIO import StringIO
     801      except ImportError:
     802          from StringIO import StringIO
     803
     804  try:
     805      from ssl import SSLError
     806  except ImportError:
     807 -    class SSLError(socket.error):
     808          pass
     809  # Import Package Modules
     810  # package imports removed in monolithic build
     811
     812
     813  # Define Constants
     814  re_SLASH = re.compile('%2F', re.IGNORECASE)
     815  re_REQUEST_LINE = re.compile(r"""^
     816  (?P<method>OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT)   # Request Method
     817  \                                                            # (single space)
     818  (
     819      (?P<scheme>[^:/]+)                                       # Scheme
     820      (://)  #
     821      (?P<host>[^/]+)                                          # Host
     822  )? #
     823  (?P<path>(\*|/[^ \?]*))                                      # Path
     824  (\? (?P<query_string>[^ ]+))?                                # Query String
     825  \                                                            # (single space)
     826  (?P<protocol>HTTPS?/1\.[01])                                 # Protocol
     827  $
     828  """, re.X)
     829  LOG_LINE = '%(client_ip)s - "%(request_line)s" - %(status)s %(size)s'
     830  RESPONSE = '''\
     831  HTTP/1.1 %s
     832  Content-Length: %i
     833  Content-Type: %s
     834
     835  %s
     836  '''
     837  if IS_JYTHON:
     838      HTTP_METHODS = set(['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT'])
     839
     840  ###
     841  # The Headers and FileWrapper classes are ripped straight from the Python
     842  # Standard Library. I've removed some docstrings and integrated my BUF_SIZE.
     843  # See the Python License here: http://docs.python.org/license.html
     844  ###
     845
     846  # Regular expression that matches `special' characters in parameters, the
     847  # existance of which force quoting of the parameter value.
     848  import re
     849  _tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
     850
     851 -def _formatparam(param, value=None, quote=1):
     852      """Convenience function to format and return a key=value pair.
     853
     854      This will quote the value if needed or if quote is true.
     855      """
     856      if value is not None and len(value) > 0:
     857          if quote or _tspecials.search(value):
     858              value = value.replace('\\', '\\\\').replace('"', r'\"')
     859              return '%s="%s"' % (param, value)
     860          else:
     861              return '%s=%s' % (param, value)
     862      else:
     863          return param
     864
     865 -class Headers:
     866 -    def __init__(self,headers):
     867          if type(headers) is not type([]):
     868              raise TypeError("Headers must be a list of name/value tuples")
     869          self._headers = headers
     870
     871 -    def __len__(self):
     872          return len(self._headers)
     873
     874 -    def __setitem__(self, name, val):
     875          del self[name]
     876          self._headers.append((name, val))
     877
     878 -    def __delitem__(self,name):
     879          name = name.lower()
     880          self._headers[:] = [kv for kv in self._headers if kv[0].lower() != name]
     881
     882 -    def __getitem__(self,name):
     883          return self.get(name)
     884
     885 -    def has_key(self, name):
     886          return self.get(name) is not None
     887
     888      __contains__ = has_key
     889
     890 -    def get_all(self, name):
     891          name = name.lower()
     892          return [kv[1] for kv in self._headers if kv[0].lower()==name]
     893
     894 -    def get(self,name,default=None):
     895          name = name.lower()
     896          for k,v in self._headers:
     897              if k.lower()==name:
     898                  return v
     899          return default
     900
     901 -    def keys(self):
     902          return [k for k, v in self._headers]
     903
     904 -    def values(self):
     905          return [v for k, v in self._headers]
     906
     907 -    def items(self):
     908          return self._headers[:]
     909
     910 -    def __repr__(self):
     911          return "Headers(%r)" % self._headers
     912
     913 -    def __str__(self):
     914          return '\r\n'.join(["%s: %s" % kv for kv in self._headers]+['',''])
     915
     916 -    def setdefault(self,name,value):
     917          result = self.get(name)
     918          if result is None:
     919              self._headers.append((name,value))
     920              return value
     921          else:
     922              return result
     923
     924
     925 -    def add_header(self, _name, _value, **_params):
     926          parts = []
     927          if _value is not None:
     928              parts.append(_value)
     929          for k, v in _params.items():
     930              if v is None:
     931                  parts.append(k.replace('_', '-'))
     932              else:
     933                  parts.append(_formatparam(k.replace('_', '-'), v))
     934          self._headers.append((_name, "; ".join(parts)))
     935
     936 -class FileWrapper:
     937      """Wrapper to convert file-like objects to iterables"""
     938
     939 -    def __init__(self, filelike, blksize=BUF_SIZE):
     940          self.filelike = filelike
     941          self.blksize = blksize
     942          if hasattr(filelike,'close'):
     943              self.close = filelike.close
     944
     945 -    def __getitem__(self,key):
     946          data = self.filelike.read(self.blksize)
     947          if data:
     948              return data
     949          raise IndexError
     950
     951 -    def __iter__(self):
     952          return self
     953
     954 -    def next(self):
     955          data = self.filelike.read(self.blksize)
     956          if data:
     957              return data
     958          raise StopIteration
     959
     960 -class Worker(Thread):
     961      """The Worker class is a base class responsible for receiving connections
     962      and (a subclass) will run an application to process the the connection """
     963
     964 -    def __init__(self,
     965                   app_info,
     966                   active_queue,
     967                   monitor_queue,
     968                   *args,
     969                   **kwargs):
     970
     971          Thread.__init__(self, *args, **kwargs)
     972
     973          # Instance Variables
     974          self.app_info = app_info
     975          self.active_queue = active_queue
     976          self.monitor_queue = monitor_queue
     977
     978          self.size = 0
     979          self.status = "200 OK"
     980          self.closeConnection = True
     981
     982          # Request Log
     983          self.req_log = logging.getLogger('Rocket.Requests')
     984          self.req_log.addHandler(NullHandler())
     985
     986          # Error Log
     987          self.err_log = logging.getLogger('Rocket.Errors.'+self.getName())
     988          self.err_log.addHandler(NullHandler())
     989
     990 -    def _handleError(self, typ, val, tb):
     991          if typ == SSLError:
     992              if 'timed out' in val.args[0]:
     993                  typ = SocketTimeout
     994          if typ == SocketTimeout:
     995              if __debug__:
     996                  self.err_log.debug('Socket timed out')
     997              self.monitor_queue.put(self.conn)
     998              return True
     999          if typ == SocketClosed:
    1000              self.closeConnection = True
    1001              if __debug__:
    1002                  self.err_log.debug('Client closed socket')
    1003              return False
    1004          if typ == BadRequest:
    1005              self.closeConnection = True
    1006              if __debug__:
    1007                  self.err_log.debug('Client sent a bad request')
    1008              return True
    1009          if typ == socket.error:
    1010              self.closeConnection = True
    1011              if val.args[0] in IGNORE_ERRORS_ON_CLOSE:
    1012                  if __debug__:
    1013                      self.err_log.debug('Ignorable socket Error received...'
    1014                                         'closing connection.')
    1015                  return False
    1016              else:
    1017                  self.status = "999 Utter Server Failure"
    1018                  tb_fmt = traceback.format_exception(typ, val, tb)
    1019                  self.err_log.error('Unhandled Error when serving '
    1020                                     'connection:\n' + '\n'.join(tb_fmt))
    1021                  return False
    1022
    1023          self.closeConnection = True
    1024          tb_fmt = traceback.format_exception(typ, val, tb)
    1025          self.err_log.error('\n'.join(tb_fmt))
    1026          self.send_response('500 Server Error')
    1027          return False
    1028
    1029 -    def run(self):
    1030          if __debug__:
    1031              self.err_log.debug('Entering main loop.')
    1032
    1033          # Enter thread main loop
    1034          while True:
    1035              conn = self.active_queue.get()
    1036
    1037              if not conn:
    1038                  # A non-client is a signal to die
    1039                  if __debug__:
    1040                      self.err_log.debug('Received a death threat.')
    1041                  return conn
    1042
    1043              if isinstance(conn, tuple):
    1044                  conn = Connection(*conn)
    1045
    1046              self.conn = conn
    1047
    1048              if conn.ssl != conn.secure:
    1049                  self.err_log.info('Received HTTP connection on HTTPS port.')
    1050                  self.send_response('400 Bad Request')
    1051                  self.closeConnection = True
    1052                  conn.close()
    1053                  continue
    1054              else:
    1055                  if __debug__:
    1056                      self.err_log.debug('Received a connection.')
    1057                  self.closeConnection = False
    1058
    1059              # Enter connection serve loop
    1060              while True:
    1061                  if __debug__:
    1062                      self.err_log.debug('Serving a request')
    1063                  try:
    1064                      self.run_app(conn)
    1065                      log_info = dict(client_ip = conn.client_addr,
    1066                                      time = datetime.now().strftime('%c'),
    1067                                      status = self.status.split(' ')[0],
    1068                                      size = self.size,
    1069                                      request_line = self.request_line)
    1070                      self.req_log.info(LOG_LINE % log_info)
    1071                  except:
    1072                      exc = sys.exc_info()
    1073                      handled = self._handleError(*exc)
    1074                      if handled:
    1075                          break
    1076                      else:
    1077                          if self.request_line:
    1078                              log_info = dict(client_ip = conn.client_addr,
    1079                                              time = datetime.now().strftime('%c'),
    1080                                              status = self.status.split(' ')[0],
    1081                                              size = self.size,
    1082                                              request_line = self.request_line + ' - not stopping')
    1083                              self.req_log.info(LOG_LINE % log_info)
    1084
    1085                  if self.closeConnection:
    1086                      try:
    1087                          conn.close()
    1088                      except:
    1089                          self.err_log.error(str(traceback.format_exc()))
    1090
    1091                      break
    1092
    1093 -    def run_app(self, conn):
    1094          # Must be overridden with a method reads the request from the socket
    1095          # and sends a response.
    1096          self.closeConnection = True
    1097          raise NotImplementedError('Overload this method!')
    1098
    1099 -    def send_response(self, status):
    1100          stat_msg = status.split(' ', 1)[1]
    1101          msg = RESPONSE % (status,
    1102                            len(stat_msg),
    1103                            'text/plain',
    1104                            stat_msg)
    1105          try:
    1106              self.conn.sendall(b(msg))
    1107          except socket.error:
    1108              self.closeConnection = True
    1109              self.err_log.error('Tried to send "%s" to client but received socket'
    1110                             ' error' % status)
    1111
    1112 -    def kill(self):
    1113          if self.isAlive() and hasattr(self, 'conn'):
    1114              try:
    1115                  self.conn.shutdown(socket.SHUT_RDWR)
    1116              except socket.error:
    1117                  info = sys.exc_info()
    1118                  if info[1].args[0] != socket.EBADF:
    1119                      self.err_log.debug('Error on shutdown: '+str(info))
    1120
    1121 -    def read_request_line(self, sock_file):
    1122          self.request_line = ''
    1123          try:
    1124              # Grab the request line
    1125              d = sock_file.readline()
    1126              if PY3K:
    1127                  d = d.decode('ISO-8859-1')
    1128
    1129              if d == '\r\n':
    1130                  # Allow an extra NEWLINE at the beginning per HTTP 1.1 spec
    1131                  if __debug__:
    1132                      self.err_log.debug('Client sent newline')
    1133
    1134                  d = sock_file.readline()
    1135                  if PY3K:
    1136                      d = d.decode('ISO-8859-1')
    1137          except socket.timeout:
    1138              raise SocketTimeout("Socket timed out before request.")
    1139
    1140          d = d.strip()
    1141
    1142          if not d:
    1143              if __debug__:
    1144                  self.err_log.debug('Client did not send a recognizable request.')
    1145              raise SocketClosed('Client closed socket.')
    1146
    1147          self.request_line = d
    1148
    1149          # NOTE: I've replaced the traditional method of procedurally breaking
    1150          # apart the request line with a (rather unsightly) regular expression.
    1151          # However, Java's regexp support sucks so bad that it actually takes
    1152          # longer in Jython to process the regexp than procedurally. So I've
    1153          # left the old code here for Jython's sake...for now.
    1154          if IS_JYTHON:
    1155              return self._read_request_line_jython(d)
    1156
    1157          match = re_REQUEST_LINE.match(d)
    1158
    1159          if not match:
    1160              self.send_response('400 Bad Request')
    1161              raise BadRequest
    1162
    1163          req = match.groupdict()
    1164          for k,v in req.items():
    1165              if not v:
    1166                  req[k] = ""
    1167              if k == 'path':
    1168                  req['path'] = r'%2F'.join([unquote(x) for x in re_SLASH.split(v)])
    1169
    1170          return req
    1171
    1172 -    def _read_request_line_jython(self, d):
    1173          d = d.strip()
    1174          try:
    1175              method, uri, proto = d.split(' ')
    1176              if not proto.startswith('HTTP') or \
    1177                 proto[-3:] not in ('1.0', '1.1') or \
    1178                 method not in HTTP_METHODS:
    1179                  self.send_response('400 Bad Request')
    1180                  raise BadRequest
    1181          except ValueError:
    1182              self.send_response('400 Bad Request')
    1183              raise BadRequest
    1184
    1185          req = dict(method=method, protocol = proto)
    1186          scheme = ''
    1187          host = ''
    1188          if uri == '*' or uri.startswith('/'):
    1189              path = uri
    1190          elif '://' in uri:
    1191              scheme, rest = uri.split('://')
    1192              host, path = rest.split('/', 1)
    1193              path = '/' + path
    1194          else:
    1195              self.send_response('400 Bad Request')
    1196              raise BadRequest
    1197
    1198          query_string = ''
    1199          if '?' in path:
    1200              path, query_string = path.split('?', 1)
    1201
    1202          path = r'%2F'.join([unquote(x) for x in re_SLASH.split(path)])
    1203
    1204          req.update(path=path,
    1205                     query_string=query_string,
    1206                     scheme=scheme.lower(),
    1207                     host=host)
    1208          return req
    1209
    1210
    1211 -    def read_headers(self, sock_file):
    1212          headers = dict()
    1213          l = sock_file.readline()
    1214
    1215          lname = None
    1216          lval = None
    1217          while True:
    1218              if PY3K:
    1219                  try:
    1220                      l = str(l, 'ISO-8859-1')
    1221                  except UnicodeDecodeError:
    1222                      self.err_log.warning('Client sent invalid header: ' + repr(l))
    1223
    1224              if l == '\r\n':
    1225                  break
    1226
    1227              if l[0] in ' \t' and lname:
    1228                  # Some headers take more than one line
    1229                  lval += ',' + l.strip()
    1230              else:
    1231                  # HTTP header values are latin-1 encoded
    1232                  l = l.split(':', 1)
    1233                  # HTTP header names are us-ascii encoded
    1234
    1235                  lname = l[0].strip().upper().replace('-', '_')
    1236                  lval = l[-1].strip()
    1237              headers[str(lname)] = str(lval)
    1238
    1239              l = sock_file.readline()
    1240          return headers
    1241
    1242 -class SocketTimeout(Exception):
    1243      "Exception for when a socket times out between requests."
    1244      pass
    1245
    1246 -class BadRequest(Exception):
    1247      "Exception for when a client sends an incomprehensible request."
    1248      pass
    1249
    1250 -class SocketClosed(Exception):
    1251      "Exception for when a socket is closed by the client."
    1252      pass
    1253
    1254 -class ChunkedReader(object):
    1255 -    def __init__(self, sock_file):
    1256          self.stream = sock_file
    1257          self.chunk_size = 0
    1258
    1259 -    def _read_header(self):
    1260          chunk_len = ""
    1261          try:
    1262              while "" == chunk_len:
    1263                  chunk_len = self.stream.readline().strip()
    1264              return int(chunk_len, 16)
    1265          except ValueError:
    1266              return 0
    1267
    1268 -    def read(self, size):
    1269          data = b('')
    1270          chunk_size = self.chunk_size
    1271          while size:
    1272              if not chunk_size:
    1273                  chunk_size = self._read_header()
    1274
    1275              if size < chunk_size:
    1276                  data += self.stream.read(size)
    1277                  chunk_size -= size
    1278                  break
    1279              else:
    1280                  if not chunk_size:
    1281                      break
    1282                  data += self.stream.read(chunk_size)
    1283                  size -= chunk_size
    1284                  chunk_size = 0
    1285
    1286          self.chunk_size = chunk_size
    1287          return data
    1288
    1289 -    def readline(self):
    1290          data = b('')
    1291          c = self.read(1)
    1292          while c and c != b('\n'):
    1293              data += c
    1294              c = self.read(1)
    1295          data += c
    1296          return data
    1297
    1298 -    def readlines(self):
    1299          yield self.readline()
    1300
    1301 -def get_method(method):
    1302
    1303      methods = dict(wsgi=WSGIWorker)
    1304      return methods[method.lower()]
    1305
    1306  # Monolithic build...end of module: rocket\worker.py
    1307  # Monolithic build...start of module: rocket\methods\__init__.py
    1308
    1309  # Monolithic build...end of module: rocket\methods\__init__.py
    1310  # Monolithic build...start of module: rocket\methods\wsgi.py
    1311
    1312  # Import System Modules
    1313  import sys
    1314  import socket
    1315  #from wsgiref.headers import Headers
    1316  #from wsgiref.util import FileWrapper
    1317  # Import Package Modules
    1318  # package imports removed in monolithic build
    1319
    1320
    1321  if PY3K:
    1322      from email.utils import formatdate
    1323  else:
    1324      # Caps Utils for Py2.4 compatibility
    1325      from email.Utils import formatdate
    1326
    1327  # Define Constants
    1328  NEWLINE = b('\r\n')
    1329  HEADER_RESPONSE = '''HTTP/1.1 %s\r\n%s'''
    1330  BASE_ENV = {'SERVER_NAME': SERVER_NAME,
    1331              'SCRIPT_NAME': '',  # Direct call WSGI does not need a name
    1332              'wsgi.errors': sys.stderr,
    1333              'wsgi.version': (1, 0),
    1334              'wsgi.multiprocess': False,
    1335              'wsgi.run_once': False,
    1336              'wsgi.file_wrapper': FileWrapper
    1337              }
    1338
    1339 -class WSGIWorker(Worker):
    1340 -    def __init__(self, *args, **kwargs):
    1341          """Builds some instance variables that will last the life of the
    1342          thread."""
    1343          Worker.__init__(self, *args, **kwargs)
    1344
    1345          if isinstance(self.app_info, dict):
    1346              multithreaded = self.app_info.get('max_threads') != 1
    1347          else:
    1348              multithreaded = False
    1349          self.base_environ = dict({'SERVER_SOFTWARE': self.app_info['server_software'],
    1350                                    'wsgi.multithread': multithreaded,
    1351                                    })
    1352          self.base_environ.update(BASE_ENV)
    1353          # Grab our application
    1354          self.app = self.app_info.get('wsgi_app')
    1355
    1356          if not hasattr(self.app, "__call__"):
    1357              raise TypeError("The wsgi_app specified (%s) is not a valid WSGI application." % repr(self.app))
    1358
    1359
    1360 -    def build_environ(self, sock_file, conn):
    1361          """ Build the execution environment. """
    1362          # Grab the request line
    1363          request = self.read_request_line(sock_file)
    1364
    1365          # Copy the Base Environment
    1366          environ = self.base_environ.copy()
    1367
    1368          # Grab the headers
    1369          for k, v in self.read_headers(sock_file).items():
    1370              environ[str('HTTP_'+k)] = v
    1371
    1372          # Add CGI Variables
    1373          environ['REQUEST_METHOD'] = request['method']
    1374          environ['PATH_INFO'] = request['path']
    1375          environ['SERVER_PROTOCOL'] = request['protocol']
    1376          environ['SERVER_PORT'] = str(conn.server_port)
    1377          environ['REMOTE_PORT'] = str(conn.client_port)
    1378          environ['REMOTE_ADDR'] = str(conn.client_addr)
    1379          environ['QUERY_STRING'] = request['query_string']
    1380          if 'HTTP_CONTENT_LENGTH' in environ:
    1381              environ['CONTENT_LENGTH'] = environ['HTTP_CONTENT_LENGTH']
    1382          if 'HTTP_CONTENT_TYPE' in environ:
    1383              environ['CONTENT_TYPE'] = environ['HTTP_CONTENT_TYPE']
    1384
    1385          # Save the request method for later
    1386          self.request_method = environ['REQUEST_METHOD']
    1387
    1388          # Add Dynamic WSGI Variables
    1389          if conn.ssl:
    1390              environ['wsgi.url_scheme'] = 'https'
    1391              environ['HTTPS'] = 'on'
    1392          else:
    1393              environ['wsgi.url_scheme'] = 'http'
    1394
    1395          if environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked':
    1396              environ['wsgi.input'] = ChunkedReader(sock_file)
    1397          else:
    1398              environ['wsgi.input'] = sock_file
    1399
    1400          return environ
    1401
    1402 -    def send_headers(self, data, sections):
    1403          h_set = self.header_set
    1404
    1405          # Does the app want us to send output chunked?
    1406          self.chunked = h_set.get('transfer-encoding', '').lower() == 'chunked'
    1407
    1408          # Add a Date header if it's not there already
    1409          if not 'date' in h_set:
    1410              h_set['Date'] = formatdate(usegmt=True)
    1411
    1412          # Add a Server header if it's not there already
    1413          if not 'server' in h_set:
    1414              h_set['Server'] = HTTP_SERVER_SOFTWARE
    1415
    1416          if 'content-length' in h_set:
    1417              self.size = int(h_set['content-length'])
    1418          else:
    1419              s = int(self.status.split(' ')[0])
    1420              if s < 200 or s not in (204, 205, 304):
    1421                  if not self.chunked:
    1422                      if sections == 1:
    1423                          # Add a Content-Length header if it's not there already
    1424                          h_set['Content-Length'] = str(len(data))
    1425                          self.size = len(data)
    1426                      else:
    1427                          # If they sent us more than one section, we blow chunks
    1428                          h_set['Transfer-Encoding'] = 'Chunked'
    1429                          self.chunked = True
    1430                          if __debug__:
    1431                              self.err_log.debug('Adding header...'
    1432                                                 'Transfer-Encoding: Chunked')
    1433
    1434          if 'connection' not in h_set:
    1435              # If the application did not provide a connection header, fill it in
    1436              client_conn = self.environ.get('HTTP_CONNECTION', '').lower()
    1437              if self.environ['SERVER_PROTOCOL'] == 'HTTP/1.1':
    1438                  # HTTP = 1.1 defaults to keep-alive connections
    1439                  if client_conn:
    1440                      h_set['Connection'] = client_conn
    1441                  else:
    1442                      h_set['Connection'] = 'keep-alive'
    1443              else:
    1444                  # HTTP < 1.1 supports keep-alive but it's quirky so we don't support it
    1445                  h_set['Connection'] = 'close'
    1446
    1447          # Close our connection if we need to.
    1448          self.closeConnection = h_set.get('connection', '').lower() == 'close'
    1449
    1450          # Build our output headers
    1451          header_data = HEADER_RESPONSE % (self.status, str(h_set))
    1452
    1453          # Send the headers
    1454          if __debug__:
    1455              self.err_log.debug('Sending Headers: %s' % repr(header_data))
    1456          self.conn.sendall(b(header_data))
    1457          self.headers_sent = True
    1458
    1459 -    def write_warning(self, data, sections=None):
    1460          self.err_log.warning('WSGI app called write method directly.  This is '
    1461                               'deprecated behavior.  Please update your app.')
    1462          return self.write(data, sections)
    1463
    1464 -    def write(self, data, sections=None):
    1465          """ Write the data to the output socket. """
    1466
    1467          if self.error[0]:
    1468              self.status = self.error[0]
    1469              data = b(self.error[1])
    1470
    1471          if not self.headers_sent:
    1472              self.send_headers(data, sections)
    1473
    1474          if self.request_method != 'HEAD':
    1475              try:
    1476                  if self.chunked:
    1477                      self.conn.sendall(b('%x\r\n%s\r\n' % (len(data), data)))
    1478                  else:
    1479                      self.conn.sendall(data)
    1480              except socket.error:
    1481                  # But some clients will close the connection before that
    1482                  # resulting in a socket error.
    1483                  self.closeConnection = True
    1484
    1485 -    def start_response(self, status, response_headers, exc_info=None):
    1486          """ Store the HTTP status and headers to be sent when self.write is
    1487          called. """
    1488          if exc_info:
    1489              try:
    1490                  if self.headers_sent:
    1491                      # Re-raise original exception if headers sent
    1492                      # because this violates WSGI specification.
    1493                      raise
    1494              finally:
    1495                  exc_info = None
    1496          elif self.header_set:
    1497              raise AssertionError("Headers already set!")
    1498
    1499          if PY3K and not isinstance(status, str):
    1500              self.status = str(status, 'ISO-8859-1')
    1501          else:
    1502              self.status = status
    1503          # Make sure headers are bytes objects
    1504          try:
    1505              self.header_set = Headers(response_headers)
    1506          except UnicodeDecodeError:
    1507              self.error = ('500 Internal Server Error',
    1508                            'HTTP Headers should be bytes')
    1509              self.err_log.error('Received HTTP Headers from client that contain'
    1510                                 ' invalid characters for Latin-1 encoding.')
    1511
    1512          return self.write_warning
    1513
    1514 -    def run_app(self, conn):
    1515          self.size = 0
    1516          self.header_set = Headers([])
    1517          self.headers_sent = False
    1518          self.error = (None, None)
    1519          self.chunked = False
    1520          sections = None
    1521          output = None
    1522
    1523          if __debug__:
    1524              self.err_log.debug('Getting sock_file')
    1525
    1526          # Build our file-like object
    1527          sock_file = conn.makefile('rb',BUF_SIZE)
    1528
    1529          try:
    1530              # Read the headers and build our WSGI environment
    1531              self.environ = environ = self.build_environ(sock_file, conn)
    1532
    1533              # Handle 100 Continue
    1534              if environ.get('HTTP_EXPECT', '') == '100-continue':
    1535                  res = environ['SERVER_PROTOCOL'] + ' 100 Continue\r\n\r\n'
    1536                  conn.sendall(b(res))
    1537
    1538              # Send it to our WSGI application
    1539              output = self.app(environ, self.start_response)
    1540
    1541              if not hasattr(output, '__len__') and not hasattr(output, '__iter__'):
    1542                  self.error = ('500 Internal Server Error',
    1543                                'WSGI applications must return a list or '
    1544                                'generator type.')
    1545
    1546              if hasattr(output, '__len__'):
    1547                  sections = len(output)
    1548
    1549              for data in output:
    1550                  # Don't send headers until body appears
    1551                  if data:
    1552                      self.write(data, sections)
    1553
    1554              if self.chunked:
    1555                  # If chunked, send our final chunk length
    1556                  self.conn.sendall(b('0\r\n\r\n'))
    1557              elif not self.headers_sent:
    1558                  # Send headers if the body was empty
    1559                  self.send_headers('', sections)
    1560
    1561          # Don't capture exceptions here.  The Worker class handles
    1562          # them appropriately.
    1563          finally:
    1564              if __debug__:
    1565                  self.err_log.debug('Finally closing output and sock_file')
    1566
    1567              if hasattr(output,'close'):
    1568                  output.close()
    1569
    1570              sock_file.close()
    1571
    1572  # Monolithic build...end of module: rocket\methods\wsgi.py
    1573
    1574  #
    1575  # the following code is not part of Rocket but was added in web2py for testing purposes
    1576  #
    1577
    1578 -def demo_app(environ, start_response):
    1579      global static_folder
    1580      import os
    1581      types = {'htm': 'text/html','html': 'text/html','gif': 'image/gif',
    1582               'jpg': 'image/jpeg','png': 'image/png','pdf': 'applications/pdf'}
    1583      if static_folder:
    1584          if not static_folder.startswith('/'):
    1585              static_folder = os.path.join(os.getcwd(),static_folder)
    1586          path = os.path.join(static_folder, environ['PATH_INFO'][1:] or 'index.html')
    1587          type = types.get(path.split('.')[-1],'text')
    1588          if os.path.exists(path):
    1589              try:
    1590                  pathfile = open(path,'rb')
    1591                  try:
    1592                      data = pathfile.read()
    1593                  finally:
    1594                      pathfile.close()
    1595                  start_response('200 OK', [('Content-Type', type)])
    1596              except IOError:
    1597                  start_response('404 NOT FOUND', [])
    1598                  data = '404 NOT FOUND'
    1599          else:
    1600              start_response('500 INTERNAL SERVER ERROR', [])
    1601              data = '500 INTERNAL SERVER ERROR'
    1602      else:
    1603          start_response('200 OK', [('Content-Type', 'text/html')])
    1604          data = '<html><body><h1>Hello from Rocket Web Server</h1></body></html>'
    1605      return [data]
    1606
    1607 -def demo():
    1608      from optparse import OptionParser
    1609      parser = OptionParser()
    1610      parser.add_option("-i", "--ip", dest="ip",default="127.0.0.1",
    1611                        help="ip address of the network interface")
    1612      parser.add_option("-p", "--port", dest="port",default="8000",
    1613                        help="post where to run web server")
    1614      parser.add_option("-s", "--static", dest="static",default=None,
    1615                        help="folder containing static files")
    1616      (options, args) = parser.parse_args()
    1617      global static_folder
    1618      static_folder = options.static
    1619      print 'Rocket running on %s:%s' % (options.ip, options.port)
    1620      r=Rocket((options.ip,int(options.port)),'wsgi', {'wsgi_app':demo_app})
    1621      r.start()
    1622
    1623  if __name__=='__main__':
    1624      demo()
    1625