--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/thirdparty/google_appengine/google/appengine/ext/zipserve/__init__.py Tue Sep 16 02:28:33 2008 +0000
@@ -0,0 +1,173 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""Serve static files from a zipfile.
+
+This is a solution for apps that want to serve 1000s of small static
+files while staying withing the 1000 file limit.
+
+The simplest use case is driven purely from the handlers section in
+app.yaml, e.g.:
+
+ - url: /images/.*
+ script: $PYTHON_LIB/google/appengine/ext/zipserve
+
+This would invoke a main() within zipserve/__init__.py. This code
+would then take the URL path, and look for a .zip file under the first
+component of the path, in this case "images.zip" in the app's working
+directory. If found, it will then serve any matching paths below that
+from the zip file. In other words, /images/foo/icon.gif would map to
+foo/icon.gif in the zip file images.zip.
+
+You can also customize the behavior by adding a custom line to your
+WSGIApplication() invocation:
+
+ def main():
+ app = webapp.WSGIApplication(
+ [('/', MainPage),
+ ('/static/(.*)', zipserve.make_zip_handler('staticfiles.zip')),
+ ])
+
+You can pass max_age=N to the make_zip_handler() call to override the
+expiration time in seconds, which defaults to 600.
+
+To customize the behavior even more, you can subclass ZipHandler and
+override the get() method, or override it and call ServeFromZipFile()
+directly.
+
+Note that by default, a Cache-control is added that makes these pages
+cacheable even if they require authentication. If this is not what
+you want, override ZipHandler.SetCachingHeaders().
+"""
+
+
+import email.Utils
+import logging
+import mimetypes
+import time
+import zipfile
+
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import util
+
+
+def make_zip_handler(zipfilename, max_age=None, public=None):
+ """Factory function to construct a custom ZipHandler instance.
+
+ Args:
+ zipfilename: The filename of a zipfile.
+ max_age: Optional expiration time; defaults to ZipHandler.MAX_AGE.
+ public: Optional public flag; defaults to ZipHandler.PUBLIC.
+
+ Returns:
+ A ZipHandler subclass.
+ """
+ class CustomZipHandler(ZipHandler):
+ def get(self, name):
+ self.ServeFromZipFile(self.ZIPFILENAME, name)
+ ZIPFILENAME = zipfilename
+ if max_age is not None:
+ MAX_AGE = max_age
+ if public is not None:
+ PUBLIC = public
+
+ return CustomZipHandler
+
+
+class ZipHandler(webapp.RequestHandler):
+ """Request handler serving static files from zipfiles."""
+
+ zipfile_cache = {}
+
+ def get(self, prefix, name):
+ """GET request handler.
+
+ Typically the arguments are passed from the matching groups in the
+ URL pattern passed to WSGIApplication().
+
+ Args:
+ prefix: The zipfilename without the .zip suffix.
+ name: The name within the zipfile.
+ """
+ self.ServeFromZipFile(prefix + '.zip', name)
+
+ def ServeFromZipFile(self, zipfilename, name):
+ """Helper for the GET request handler.
+
+ This serves the contents of file 'name' from zipfile
+ 'zipfilename', logging a message and returning a 404 response if
+ either the zipfile cannot be opened or the named file cannot be
+ read from it.
+
+ Args:
+ zipfilename: The name of the zipfile.
+ name: The name within the zipfile.
+ """
+ zipfile_object = self.zipfile_cache.get(zipfilename)
+ if zipfile_object is None:
+ try:
+ zipfile_object = zipfile.ZipFile(zipfilename)
+ except (IOError, RuntimeError), err:
+ logging.error('Can\'t open zipfile %s: %s', zipfilename, err)
+ zipfile_object = ''
+ self.zipfile_cache[zipfilename] = zipfile_object
+ if zipfile_object == '':
+ self.error(404)
+ self.response.out.write('Not found')
+ return
+ try:
+ data = zipfile_object.read(name)
+ except (KeyError, RuntimeError), err:
+ self.error(404)
+ self.response.out.write('Not found')
+ return
+ content_type, encoding = mimetypes.guess_type(name)
+ if content_type:
+ self.response.headers['Content-Type'] = content_type
+ self.SetCachingHeaders()
+ self.response.out.write(data)
+
+ MAX_AGE = 600
+
+ PUBLIC = True
+
+ def SetCachingHeaders(self):
+ """Helper to set the caching headers.
+
+ Override this to customize the headers beyond setting MAX_AGE.
+ """
+ max_age = self.MAX_AGE
+ self.response.headers['Expires'] = email.Utils.formatdate(
+ time.time() + max_age, usegmt=True)
+ cache_control = []
+ if self.PUBLIC:
+ cache_control.append('public')
+ cache_control.append('max-age=%d' % max_age)
+ self.response.headers['Cache-Control'] = ', '.join(cache_control)
+
+
+def main():
+ """Main program.
+
+ This is invoked when this package is referenced from app.yaml.
+ """
+ application = webapp.WSGIApplication([('/([^/]+)/(.*)', ZipHandler)])
+ util.run_wsgi_app(application)
+
+
+if __name__ == '__main__':
+ main()