web.web.httpserver

Package web :: Package web :: Module httpserver |
[hide private]
[frames] | no frames]

# Source Code for Module web.web.httpserver

      1  __all__ = ["runsimple"]
      2
      3  import sys, os
      4  from SimpleHTTPServer import SimpleHTTPRequestHandler
      5
      6  import webapi as web
      7  import net
      8  import utils
      9
     10 -def runbasic(func, server_address=("0.0.0.0", 8080)):
     11      """
     12      Runs a simple HTTP server hosting WSGI app `func`. The directory `static/`
     13      is hosted statically.
     14
     15      Based on [WsgiServer][ws] from [Colin Stewart][cs].
     16
     17    [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html
     18    [cs]: http://www.owlfish.com/
     19      """
     20      # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
     21      # Modified somewhat for simplicity
     22      # Used under the modified BSD license:
     23      # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
     24
     25      import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse
     26      import socket, errno
     27      import traceback
     28
     29      class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
     30          def run_wsgi_app(self):
     31              protocol, host, path, parameters, query, fragment = \
     32                  urlparse.urlparse('http://dummyhost%s' % self.path)
     33
     34              # we only use path, query
     35              env = {'wsgi.version': (1, 0)
     36                     ,'wsgi.url_scheme': 'http'
     37                     ,'wsgi.input': self.rfile
     38                     ,'wsgi.errors': sys.stderr
     39                     ,'wsgi.multithread': 1
     40                     ,'wsgi.multiprocess': 0
     41                     ,'wsgi.run_once': 0
     42                     ,'REQUEST_METHOD': self.command
     43                     ,'REQUEST_URI': self.path
     44                     ,'PATH_INFO': path
     45                     ,'QUERY_STRING': query
     46                     ,'CONTENT_TYPE': self.headers.get('Content-Type', '')
     47                     ,'CONTENT_LENGTH': self.headers.get('Content-Length', '')
     48                     ,'REMOTE_ADDR': self.client_address[0]
     49                     ,'SERVER_NAME': self.server.server_address[0]
     50                     ,'SERVER_PORT': str(self.server.server_address[1])
     51                     ,'SERVER_PROTOCOL': self.request_version
     52                     }
     53
     54              for http_header, http_value in self.headers.items():
     55                  env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \
     56                      http_value
     57
     58              # Setup the state
     59              self.wsgi_sent_headers = 0
     60              self.wsgi_headers = []
     61
     62              try:
     63                  # We have there environment, now invoke the application
     64                  result = self.server.app(env, self.wsgi_start_response)
     65                  try:
     66                      try:
     67                          for data in result:
     68                              if data:
     69                                  self.wsgi_write_data(data)
     70                      finally:
     71                          if hasattr(result, 'close'):
     72                              result.close()
     73                  except socket.error, socket_err:
     74                      # Catch common network errors and suppress them
     75                      if (socket_err.args[0] in \
     76                         (errno.ECONNABORTED, errno.EPIPE)):
     77                          return
     78                  except socket.timeout, socket_timeout:
     79                      return
     80              except:
     81                  print >> web.debug, traceback.format_exc(),
     82
     83              if (not self.wsgi_sent_headers):
     84                  # We must write out something!
     85                  self.wsgi_write_data(" ")
     86              return
     87
     88          do_POST = run_wsgi_app
     89          do_PUT = run_wsgi_app
     90          do_DELETE = run_wsgi_app
     91
     92          def do_GET(self):
     93              if self.path.startswith('/static/'):
     94                  SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
     95              else:
     96                  self.run_wsgi_app()
     97
     98          def wsgi_start_response(self, response_status, response_headers,
     99                                exc_info=None):
    100              if (self.wsgi_sent_headers):
    101                  raise Exception \
    102                        ("Headers already sent and start_response called again!")
    103              # Should really take a copy to avoid changes in the application....
    104              self.wsgi_headers = (response_status, response_headers)
    105              return self.wsgi_write_data
    106
    107          def wsgi_write_data(self, data):
    108              if (not self.wsgi_sent_headers):
    109                  status, headers = self.wsgi_headers
    110                  # Need to send header prior to data
    111                  status_code = status[:status.find(' ')]
    112                  status_msg = status[status.find(' ') + 1:]
    113                  self.send_response(int(status_code), status_msg)
    114                  for header, value in headers:
    115                      self.send_header(header, value)
    116                  self.end_headers()
    117                  self.wsgi_sent_headers = 1
    118              # Send the data
    119              self.wfile.write(data)
    120
    121      class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
    122          def __init__(self, func, server_address):
    123              BaseHTTPServer.HTTPServer.__init__(self,
    124                                                 server_address,
    125                                                 WSGIHandler)
    126              self.app = func
    127              self.serverShuttingDown = 0
    128
    129      print "http://%s:%d/" % server_address
    130      WSGIServer(func, server_address).serve_forever()
    131
    132 -def runsimple(func, server_address=("0.0.0.0", 8080)):
    133      """
    134      Runs [CherryPy][cp] WSGI server hosting WSGI app `func`.
    135      The directory `static/` is hosted statically.
    136
    137      [cp]: http://www.cherrypy.org
    138      """
    139      func = StaticMiddleware(func)
    140      func = LogMiddleware(func)
    141
    142      server = WSGIServer(server_address, func)
    143
    144      print "http://%s:%d/" % server_address
    145      try:
    146          server.start()
    147      except KeyboardInterrupt:
    148          server.stop()
    149
    150 -def WSGIServer(server_address, wsgi_app):
    151      """Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`.
    152      This function can be overwritten to customize the webserver or use a different webserver.
    153      """
    154      from wsgiserver import CherryPyWSGIServer
    155      return CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
    156
    157 -class StaticApp(SimpleHTTPRequestHandler):
    158      """WSGI application for serving static files."""
    159 -    def __init__(self, environ, start_response):
    160          self.headers = []
    161          self.environ = environ
    162          self.start_response = start_response
    163
    164 -    def send_response(self, status, msg=""):
    165          self.status = str(status) + " " + msg
    166
    167 -    def send_header(self, name, value):
    168          self.headers.append((name, value))
    169
    170 -    def end_headers(self):
    171          pass
    172
    173 -    def log_message(*a): pass
    174
    175 -    def __iter__(self):
    176          environ = self.environ
    177
    178          self.path = environ.get('PATH_INFO', '')
    179          self.client_address = environ.get('REMOTE_ADDR','-'), \
    180                                environ.get('REMOTE_PORT','-')
    181          self.command = environ.get('REQUEST_METHOD', '-')
    182
    183          from cStringIO import StringIO
    184          self.wfile = StringIO() # for capturing error
    185
    186          try:
    187              path = self.translate_path(self.path)
    188              etag = '"%s"' % os.path.getmtime(path)
    189              client_etag = environ.get('HTTP_IF_NONE_MATCH')
    190              self.send_header('ETag', etag)
    191              if etag == client_etag:
    192                  self.send_response(304, "Not Modified")
    193                  self.start_response(self.status, self.headers)
    194                  raise StopIteration
    195          except OSError:
    196              pass # Probably a 404
    197
    198          f = self.send_head()
    199          self.start_response(self.status, self.headers)
    200
    201          if f:
    202              block_size = 16 * 1024
    203              while True:
    204                  buf = f.read(block_size)
    205                  if not buf:
    206                      break
    207                  yield buf
    208              f.close()
    209          else:
    210              value = self.wfile.getvalue()
    211              yield value
    212
    213 -class StaticMiddleware:
    214      """WSGI middleware for serving static files."""
    215 -    def __init__(self, app, prefix='/static/'):
    216          self.app = app
    217          self.prefix = prefix
    218
    219 -    def __call__(self, environ, start_response):
    220          path = environ.get('PATH_INFO', '')
    221          if path.startswith(self.prefix):
    222              return StaticApp(environ, start_response)
    223          else:
    224              return self.app(environ, start_response)
    225
    226 -class LogMiddleware:
    227      """WSGI middleware for logging the status."""
    228 -    def __init__(self, app):
    229          self.app = app
    230          self.format = '%s - - [%s] "%s %s %s" - %s'
    231
    232          from BaseHTTPServer import BaseHTTPRequestHandler
    233          import StringIO
    234          f = StringIO.StringIO()
    235
    236          class FakeSocket:
    237              def makefile(self, *a):
    238                  return f
    239
    240          # take log_date_time_string method from BaseHTTPRequestHandler
    241          self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string
    242
    243 -    def __call__(self, environ, start_response):
    244          def xstart_response(status, response_headers, *args):
    245              out = start_response(status, response_headers, *args)
    246              self.log(status, environ)
    247              return out
    248
    249          return self.app(environ, xstart_response)
    250
    251 -    def log(self, status, environ):
    252          outfile = environ.get('wsgi.errors', web.debug)
    253          req = environ.get('PATH_INFO', '_')
    254          protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
    255          method = environ.get('REQUEST_METHOD', '-')
    256          host = "%s:%s" % (environ.get('REMOTE_ADDR','-'),
    257                            environ.get('REMOTE_PORT','-'))
    258
    259          time = self.log_date_time_string()
    260
    261          msg = self.format % (host, time, protocol, method, req, status)
    262          print >> outfile, utils.safestr(msg)
    263