Skip to content
Browse files

Static serving code

  • Loading branch information...
1 parent 97aa473 commit 7618ea1950577901710a73478940b2ad3c9dd3d5 Nicholas Johnson committed Oct 5, 2009
Showing with 130 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +3 −0 .gitmodules
  3. +12 −0 app.yaml
  4. +4 −0 fix_path.py
  5. +11 −0 index.yaml
  6. +1 −0 lib/aetycoon
  7. +98 −0 static.py
View
1 .gitignore
@@ -0,0 +1 @@
+*.pyc
View
3 .gitmodules
@@ -0,0 +1,3 @@
+[submodule "lib/aetycoon"]
+ path = lib/aetycoon
+ url = git://github.com/Arachnid/aetycoon.git
View
12 app.yaml
@@ -0,0 +1,12 @@
+application: bloggart-demo
+version: live
+runtime: python
+api_version: 1
+
+handlers:
+- url: /remote_api
+ script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py
+ login: admin
+
+- url: /.*
+ script: static.py
View
4 fix_path.py
@@ -0,0 +1,4 @@
+import os
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
View
11 index.yaml
@@ -0,0 +1,11 @@
+indexes:
+
+# AUTOGENERATED
+
+# This index.yaml is automatically updated whenever the dev_appserver
+# detects that a new type of query is run. If you want to manage the
+# index.yaml file manually, remove the above marker line (the line
+# saying "# AUTOGENERATED"). If you want to manage some indexes
+# manually, move them above the marker line. The index.yaml file is
+# automatically uploaded to the admin console when you next deploy
+# your application using appcfg.py.
1 lib/aetycoon
@@ -0,0 +1 @@
+Subproject commit 4c6d3ce50199d8c267d39a99996fd238c4c6bbe2
View
98 static.py
@@ -0,0 +1,98 @@
+from google.appengine.ext import db
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import template
+from google.appengine.ext.webapp.util import run_wsgi_app
+
+import datetime
+import hashlib
+
+import fix_path
+import aetycoon
+
+
+HTTP_DATE_FMT = "%a, %d %b %Y %H:%M:%S GMT"
+
+
+class StaticContent(db.Model):
+ """Container for statically served content.
+
+ The serving path for content is provided in the key name.
+ """
+ body = db.BlobProperty(required=True)
+ content_type = db.StringProperty(required=True)
+ last_modified = db.DateTimeProperty(required=True, auto_now=True)
+ etag = aetycoon.DerivedProperty(lambda x: hashlib.sha1(x.body).hexdigest())
+
+
+def get(path):
+ """Returns the StaticContent object for the provided path.
+
+ Args:
+ path: The path to retrieve StaticContent for.
+ Returns:
+ A StaticContent object, or None if no content exists for this path.
+ """
+ return StaticContent.get_by_key_name(path)
+
+
+def set(path, body, content_type, **kwargs):
+ """Sets the StaticContent for the provided path.
+
+ Args:
+ path: The path to store the content against.
+ body: The data to serve for that path.
+ content_type: The MIME type to serve the content as.
+ **kwargs: Additional arguments to be passed to the StaticContent constructor
+ Returns:
+ A StaticContent object.
+ """
+ content = StaticContent(
+ key_name=path,
+ body=body,
+ content_type=content_type,
+ **kwargs)
+ content.put()
+ return content
+
+
+class StaticContentHandler(webapp.RequestHandler):
+ def output_content(self, content, serve=True):
+ self.response.headers['Content-Type'] = content.content_type
+ last_modified = content.last_modified.strftime(HTTP_DATE_FMT)
+ self.response.headers['Last-Modified'] = last_modified
+ self.response.headers['ETag'] = content.etag
+ if serve:
+ self.response.out.write(content.body)
+ else:
+ self.response.set_status(304)
+
+ def get(self, path):
+ content = get(path)
+ if not content:
+ self.error(404)
+ return
+
+ serve = True
+ if 'If-Modified-Since' in self.request.headers:
+ last_seen = datetime.datetime.strptime(
+ self.request.headers['If-Modified-Since'],
+ HTTP_DATE_FMT)
+ if last_seen >= content.last_modified.replace(microsecond=0):
+ serve = False
+ if 'If-None-Match' in self.request.headers:
+ etags = [x.strip()
+ for x in self.request.headers['If-None-Match'].split(',')]
+ if content.etag in etags:
+ serve = False
+ self.output_content(content, serve)
+
+
+application = webapp.WSGIApplication([('(/.*)', StaticContentHandler)])
+
+
+def main():
+ run_wsgi_app(application)
+
+
+if __name__ == '__main__':
+ main()

0 comments on commit 7618ea1

Please sign in to comment.
Something went wrong with that request. Please try again.