Commits

Chris Lee-Messer  committed d1cbcc0

add more wsgi utils with logging

  • Participants
  • Parent commits 8d0d096

Comments (0)

Files changed (7)

 
 What's New
 ----------
-version 0.8.0beta is updated for django 1.5 and now includes autoreloading of files when then change and integration with django's static files application.
+
+version 0.8.0beta is updated for use with django 1.5. It now includes
+autoreloading of files when they change and integration with django's static
+files application.
 
 Summary
 -------
 
    $ manage.py runwsgiserver
 
-and reload my browser page and the problem is fixed.  It's also useful to see if
-some weird effect is being caused by runserver's process of loading the settings
-twice.
+and reload my browser page and the problem is fixed.
+
+It's also useful to see if some weird effect is being caused by runserver's
+process of loading the settings twice.::
+
+  $ manage.py runwsgiserver autoload=False
+
 
 This project is a slight modification of code form Peter Baumgartner code (see `Peter's
 blog post`_) Peter and others did the work of creating a management command.

File django_wsgiserver/management/commands/runwsgiserver.py

 """
 
 
-import logging, sys, os, signal, time, errno
+import sys, os, signal, time, errno
 from datetime import datetime
 from pprint import (pprint, pformat)
-logger = logging.getLogger('django')
+import logging
+logger = logging.getLogger('django_wsgiserver')
+logger.setLevel(logging.WARNING)
+logger.addHandler(logging.StreamHandler())
+
 from socket import gethostname
 from django.core.management.base import BaseCommand
 import django_wsgiserver.mediahandler
 'staticserve' : True, # serve static
 'servestaticdirs': True, # will use STATICFILES_DIRS and actuall serve each directory so
                           # you don't need to collect them all in development
+'verbose' : 0,    
 }
 
 def change_uid_gid(uid, gid=None):
 
         #logger.info("launching wsgiserver with the following options")
         #logger.info(pformat(options))
-        self.stdout.write("launching with the following options:\n")
-        self.stdout.write(pformat(options))
+        if int(options['verbose']) >= 2:
+            self.stdout.write("launching with the following options:\n")
+            self.stdout.write(pformat(options))
 
         if options['daemonize'] and options['server_user'] and options['server_group']:
             #ensure the that the daemon runs as specified user
         #     if settings.__dict__.has_key('ADMIN_MEDIA_PREFIX'):
         #         import django.contrib.admin
 
-        #         path[settings.ADMIN_MEDIA_PREFIX] = django_wsgiserver.mediahandler.MediaHandler(        
+        #         path[settings.ADMIN_MEDIA_PREFIX] = django_wsgiserver.mediahandler.StaticFileWSGIApplication(        
         #             os.path.join( django.contrib.admin.__path__[0], 'media'))
         #     else:
         #         print "Warning adminserve was selected BUT ADMIN_MEDIA_PREFIX was not defined"
             except AttributeError, msg:
                 logger.error(msg)
                 logger.error("****")
-                logger.error("STATIC_URL and STATIC_ROOT  must be set in settings file")
+                logger.error("STATIC_URL and STATIC_ROOT  must be set in settings file for staticserve option to work in django_wsgiserver")
                 logger.error("****")
                 raise
 
                         full_static_url = os.path.join(settings.STATIC_URL,app_url)
                         full_dir_location = os.path.join(val.location,app_url)
                         logger.debug(full_static_url, full_dir_location)
-                        path[full_static_url] = django_wsgiserver.mediahandler.MediaHandler(full_dir_location)
+                        path[full_static_url] = django_wsgiserver.wsgiutil.StaticFileWSGIApplication(full_dir_location)
 
 
             if options['servestaticdirs'] and hasattr(settings, 'STATICFILES_DIRS'):
                 # debug !!!
                 logger.debug("staticlocations::"); logger.debug(pformat(staticlocations))
                 for urlprefix, root in staticlocations:
-                    path[os.path.join(settings.STATIC_URL, urlprefix)] =  django_wsgiserver.mediahandler.MediaHandler(root)
+                    path[os.path.join(settings.STATIC_URL, urlprefix)] =  django_wsgiserver.wsgiutil.StaticFileWSGIApplication(root)
 
             # One important thing is that there are two different ways to serve the static files
             # 1. convenient: serve each app's static files (assuming they follow convention)
 
             if options['staticserve'] == 'collectstatic':
                 # and serve the root of the STATIC_URL ? hmm !!!
-                path[settings.STATIC_URL] = django_wsgiserver.mediahandler.MediaHandler(settings.STATIC_ROOT)
+                path[settings.STATIC_URL] = django_wsgiserver.wsgiutil.StaticFileWSGIApplication(settings.STATIC_ROOT)
                 logger.warning("serving all static files from %s. *** Make sure you have done a fresh collectstatic operation ***" % settings.STATIC_ROOT)
 
         # debug
         dispatcher =  WSGIPathInfoDispatcher( path )
         logger.debug("apps:", pformat(dispatcher.apps))
 
+        if options['verbose'] == '1':
+            from django_wsgiserver.wsgiutil import WSGIRequestLoggerMiddleware
+            dispatcher = WSGIRequestLoggerMiddleware(dispatcher)
+            logger.setLevel(10)
+            
+        if int(options['verbose']) >= 2:
+            from django_wsgiserver.wsgiutil import WSGIRequestLoggerMiddleware
+            dispatcher = WSGIRequestLoggerMiddleware(dispatcher)
+            logger.setLevel(logging.INFO)
+
+            
         server = CherryPyWSGIServer(
             (options['host'], int(options['port'])),
             dispatcher,

File django_wsgiserver/mediahandler.py

-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# fix these up
-import os, stat, mimetypes
-import django
-from django.utils.http import http_date
-from django.conf import settings
-
-class BlockIterator(object):
-    # Vlada Macek Says:
-    # September 29th, 2009 at 14:42
-    # You’re handing the static files by
-
-    #                output = [fp.read()]
-    #                fp.close()
-
-    # which causes entire content is loaded to the memory (and not only once
-    # by my observation). This is unacceptable for large files. I found this
-    # to be much more memory & CPU efficient:
-
-    def __init__(self, fp):
-        self.fp = fp
-        
-    def __iter__(self):
-           return self
-
-    def next(self):
-        chunk = self.fp.read(20*1024)
-        if chunk:
-            return chunk
-        self.fp.close()
-        raise StopIteration
-
-class MediaHandler( object ):
-
-    def __init__( self, media_root ):
-        self.media_root = media_root
-
-    def __call__( self, environ, start_response ):
-
-        def done( status, headers, output ):
-            # a good place for debug loging 
-            # e.g. logger.debug('done from self.media_root: %s' %self.media_root, status, headers)
-            start_response( status, headers.items() )
-            return output
-
-        path_info = environ['PATH_INFO']
-        if path_info[0] == '/':
-            path_info = path_info[1:]
-        file_path = os.path.normpath( os.path.join( self.media_root, path_info ) )
-
-        # prevent escaping out of paths below media root (e.g. via "..")
-        if not file_path.startswith( self.media_root ):
-            status = '401 UNAUTHORIZED'
-            headers = {'Content-type': 'text/plain'}
-            output = ['Permission denied: %s' % file_path]
-            return done( status, headers, output )
-
-        if not os.path.exists( file_path ):
-            status = '404 NOT FOUND'
-            headers = {'Content-type': 'text/plain'}
-            output = ['Page not found: %s' % file_path]
-            return done( status, headers, output )
-
-        try:
-            fp = open( file_path, 'rb' )
-        except IOError:
-            status = '401 UNAUTHORIZED'
-            headers = {'Content-type': 'text/plain'}
-            output = ['Permission denied: %s' % file_path]
-            return done( status, headers, output )
-
-        # This is a very simple implementation of conditional GET with
-        # the Last-Modified header. It makes media files a bit speedier
-        # because the files are only read off disk for the first request
-        # (assuming the browser/client supports conditional GET).
-
-        # mtime needs to be ascii not unicode as django is all unicode need to do conversion
-        mtime = http_date( os.stat(file_path)[stat.ST_MTIME] ).encode('ascii', 'ignore') 
-        headers = {'Last-Modified': mtime}
-        if environ.get('HTTP_IF_MODIFIED_SINCE', None) == mtime:
-            status = '304 NOT MODIFIED'
-            output = []
-        else:
-            status = '200 OK'
-            mime_type = mimetypes.guess_type(file_path)[0]
-            if mime_type:
-                headers['Content-Type'] = mime_type
-
-            # naive version with whole file read as a string place in a list
-            # output = [fp.read()]
-            # fp.close()
-            # use BlockIterator for larger file/network efficiency
-            output = BlockIterator(fp)
-            
-        return done( status, headers, output )
-
-

File django_wsgiserver/wsgiutil.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# fix these up
+import os, stat, mimetypes
+import django
+from django.utils.http import http_date
+from django.conf import settings
+
+import logging
+logger = logging.getLogger('django_wsgiserver')
+
+class BlockIteratorResponse(object):
+    status_code = 200
+    # Vlada Macek Says:
+    # September 29th, 2009 at 14:42
+    # You’re handing the static files by
+
+    #                output = [fp.read()]
+    #                fp.close()
+
+    # which causes entire content is loaded to the memory (and not only once
+    # by my observation). This is unacceptable for large files. I found this
+    # to be much more memory & CPU efficient:
+
+    def __init__(self, fp):
+        self.fp = fp
+        
+    def __iter__(self):
+           return self
+
+    def next(self):
+        chunk = self.fp.read(20*1024)
+        if chunk:
+            return chunk
+        self.fp.close()
+        raise StopIteration
+
+class SimpleResponse(list):
+    # you know I could just use the code in django.http.response e.g. HttpResponse object -clm
+    # this has already defined responses for codes 301,302,304, 400, 403, 404, 405, 410, 500
+    """allow for simple responses in WSGI stacks
+        use like a list but set the status_code of the response"""
+    status_code = 0
+
+    
+class StaticFileWSGIApplication( object ):
+
+    def __init__( self, static_file_root ):
+        self.static_file_root = os.path.normpath(static_file_root)
+
+    def __call__( self, environ, start_response ):
+
+        def done( status, headers, output ):
+            # a good place for debug loging 
+            # e.g. logger.debug('done from self.static_file_root: %s' %self.static_file_root, status, headers)
+            start_response( status, headers.items() )
+            return output
+
+        path_info = environ['PATH_INFO']
+        if path_info[0] == '/':
+            path_info = path_info[1:]
+        file_path = os.path.normpath( os.path.join( self.static_file_root, path_info ) )
+
+        # prevent escaping out of paths below media root (e.g. via "..")
+        if not file_path.startswith( self.static_file_root ):
+            status = '401 UNAUTHORIZED'
+            headers = {'Content-type': 'text/plain'}
+            output = ['Permission denied. illegal path'] 
+            return done( status, headers, output )
+
+        # only allow GET or HEAD requests e.g. not PUT, DELETE, POST, etc.
+        if not (environ['REQUEST_METHOD'] == 'GET' or environ['REQUEST_METHOD'] == 'HEAD'):
+            status = '405 METHOD NOT ALLOWED'
+            headers = {'Content-type': 'text/plain'}
+            output = SimpleResponse(['405 method not allowed'])
+            output.status_code = 405
+            return done(status, headers, output)
+        
+        if not os.path.exists( file_path ):
+            status = '404 NOT FOUND'
+            headers = {'Content-type': 'text/plain'}
+            output = SimpleResponse(['Page not found: %s' % file_path])
+            output.status_code = 404
+            return done( status, headers, output )
+
+        try:
+            fp = open( file_path, 'rb' )
+        except IOError:
+            status = '401 UNAUTHORIZED'
+            headers = {'Content-type': 'text/plain'}
+            output = SimpleResponse(['Permission denied: %s' % file_path])
+            output.status_code = 401
+            return done( status, headers, output )
+
+        # This is a very simple implementation of conditional GET with
+        # the Last-Modified header. It makes media files a bit speedier
+        # because the files are only read off disk for the first request
+        # (assuming the browser/client supports conditional GET).
+
+        # mtime needs to be ascii not unicode as django is all unicode need to do conversion
+        mtime = http_date( os.stat(file_path)[stat.ST_MTIME] ).encode('ascii', 'ignore') 
+        headers = {'Last-Modified': mtime}
+        if environ.get('HTTP_IF_MODIFIED_SINCE', None) == mtime:
+            status = '304 NOT MODIFIED'
+            output = SimpleResponse() 
+            output.status_code = 304
+        else:
+            status = '200 OK'
+            mime_type = mimetypes.guess_type(file_path)[0]
+            if mime_type:
+                headers['Content-Type'] = mime_type
+
+            # naive version with whole file read as a string place in a list
+            # output = [fp.read()]
+            # fp.close()
+            # use BlockIteratorResponse for larger file/network efficiency
+            output = BlockIteratorResponse(fp)
+            
+        return done( status, headers, output )
+
+
+class WSGIRequestLoggerMiddleware(object):
+    # based upon class WSGIPathInfoDispatcher(object):
+    """
+
+    A WSGI middle ware to printout/log the requests and response codes
+
+    """
+
+    def __init__(self, app):
+        self.wsgiapp = app
+
+    def __call__(self, environ, start_response):
+        output = self.wsgiapp(environ, start_response)
+        #pprint(environ)
+        if hasattr(output, 'status_code'):
+            logger.log(10, "[%s] %s %s" % (environ['REQUEST_METHOD'], environ['REQUEST_URI'], output.status_code))
+        else:
+            logger.log(10, "[%s] %s %s" % (environ['REQUEST_METHOD'], environ['REQUEST_URI'], repr(output) ))
+        return output
+
+

File docs/staticfiles_plan.rst

     from django.contrib.staticfiles.urls import staticfiles_urlpatterns
     urlpatterns += staticfiles_urlpatterns()
 
+Other approaches
+----------------
+There are quite a few examples of wsgi applications which serve static files
 
+bottle http://bottlepy.org/
+luke/static https://bitbucket.org/luke/static/
 
+cherrypy has static serving 
 
 
 
 
+

File tests/testdjangoproject/testdjango/manage.py

File contents unchanged.

File tests/testdjangoproject/testdjango/testdjango/settings/local.py

 """Development settings and globals."""
 
-
 from os.path import join, normpath
 
 from base import *