Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 148 lines (118 sloc) 4.281 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
import datetime
import hashlib

from google.appengine.api.labs import taskqueue
from google.appengine.ext import db
from google.appengine.ext import deferred
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app

import fix_path
import aetycoon
import utils


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()
  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())
  indexed = db.BooleanProperty(required=True, default=True)


def _get_all_paths():
  keys = []
  cur = StaticContent.all(keys_only=True).filter('indexed', True).fetch(1000)
  while len(cur) == 1000:
    keys.extend(cur)
    q = StaticContent.all(keys_only=True)
    q.filter('indexed', True)
    q.filter('__key__ >', cur[-1])
    cur = q.fetch(1000)
  keys.extend(cur)
  return [x.name() for x in keys]


def _regenerate_sitemap():
  paths = _get_all_paths()
  rendered = utils.render_template('sitemap.xml', {'paths': paths})
  set('/sitemap.xml', rendered, 'application/xml', False)


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, indexed=True, **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.
indexed: Index this page in the sitemap?
**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,
      indexed=indexed,
      **kwargs)
  content.put()
  try:
    now = datetime.datetime.now().replace(second=0, microsecond=0)
    eta = now.replace(second=0, microsecond=0) + datetime.timedelta(seconds=65)
    if indexed:
      deferred.defer(
          _regenerate_sitemap,
          _name='sitemap-%s' % (now.strftime('%Y%m%d%H%M'),),
          _eta=eta)
  except (taskqueue.TaskAlreadyExistsError, taskqueue.TombstonedTaskError), e:
    pass
  return content

def add(path, body, content_type, indexed=True, **kwargs):
  """Adds a new StaticContent and returns it.
Args:
As per set().
Returns:
A StaticContent object, or None if one already exists at the given path.
"""
  def _tx():
    if StaticContent.get_by_key_name(path):
      return None
    return set(path, body, content_type, indexed, **kwargs)
  return db.run_in_transaction(_tx)

  
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'] = '"%s"' % (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()
Something went wrong with that request. Please try again.