From b2ad131cbbcf78bddc80900bcc9298170dd4ebb6 Mon Sep 17 00:00:00 2001 From: Neil Williams Date: Tue, 18 Sep 2012 22:17:54 -0700 Subject: [PATCH] Subreddit CSS: Store subreddit CSS on S3 and minify it. This means that stylesheets can have all the advantages of other static files, such as not having session cookies in the request. In addition, it also means that subreddit objects are drastically smaller in memcache which saves internal bandwidth and increases cache capacity. --- r2/example.ini | 3 ++ r2/r2/controllers/front.py | 14 +++++++-- r2/r2/models/subreddit.py | 63 +++++++++++++++++++++++++++++++++---- r2/r2/templates/reddit.html | 20 +++--------- 4 files changed, 77 insertions(+), 23 deletions(-) diff --git a/r2/example.ini b/r2/example.ini index 9344ac160..2757f99e9 100644 --- a/r2/example.ini +++ b/r2/example.ini @@ -352,6 +352,9 @@ static_secure_domain = # this is for hosts that don't do on-the-fly gzipping (e.g. s3) static_pre_gzipped = false static_secure_pre_gzipped = false +# which s3 bucket to place subreddit styles on (when empty, stylesheets will be served +# from the local database instead. +static_stylesheet_bucket = # -- translator UI -- # enable/disable access to the translation UI in /admin/i18n diff --git a/r2/r2/controllers/front.py b/r2/r2/controllers/front.py index cb1e2f5e1..716ec927c 100755 --- a/r2/r2/controllers/front.py +++ b/r2/r2/controllers/front.py @@ -22,6 +22,7 @@ from validator import * from pylons.i18n import _, ungettext +from pylons.controllers.util import redirect_to from reddit_base import RedditController, base_listing, paginated_listing, prevent_framing_and_css from r2 import config from r2.models import * @@ -379,17 +380,26 @@ class FrontController(RedditController): return res def GET_stylesheet(self): + if g.css_killswitch: + self.abort404() + # de-stale the subreddit object so we don't poison nginx's cache if not isinstance(c.site, FakeSubreddit): c.site = Subreddit._byID(c.site._id, data=True, stale=False) - if hasattr(c.site,'stylesheet_contents') and not g.css_killswitch: + if c.site.stylesheet_is_static: + # TODO: X-Private-Subreddit? + return redirect_to(c.site.stylesheet_url) + else: + stylesheet_contents = c.site.stylesheet_contents + + if stylesheet_contents: c.allow_loggedin_cache = True self.check_modified(c.site,'stylesheet_contents', private=False, max_age=7*24*60*60, must_revalidate=False) c.response_content_type = 'text/css' - c.response.content = c.site.stylesheet_contents + c.response.content = stylesheet_contents if c.site.type == 'private': c.response.headers['X-Private-Subreddit'] = 'private' return c.response diff --git a/r2/r2/models/subreddit.py b/r2/r2/models/subreddit.py index 2e1ed278d..a9a08733c 100644 --- a/r2/r2/models/subreddit.py +++ b/r2/r2/models/subreddit.py @@ -22,6 +22,9 @@ from __future__ import with_statement +import base64 +import hashlib + from pylons import c, g from pylons.i18n import _ @@ -40,12 +43,13 @@ from r2.lib.db import tdb_cassandra from r2.models.wiki import WikiPage from r2.lib.merge import ConflictException from r2.lib.cache import CL_ONE +from r2.lib.contrib.rcssmin import cssmin +from r2.lib import s3cp import math from r2.lib.utils import set_last_modified from r2.models.wiki import WikiPage -from md5 import md5 import os.path import random @@ -59,7 +63,7 @@ class Subreddit(Thing, Printable): stylesheet = None, stylesheet_rtl = None, stylesheet_contents = '', - stylesheet_hash = '0', + stylesheet_hash = '', firsttext = strings.firsttext, header = None, header_size = None, @@ -211,6 +215,30 @@ class Subreddit(Thing, Printable): def moderators(self): return self.moderator_ids() + @property + def stylesheet_is_static(self): + """Is the subreddit using the newer static file based stylesheets?""" + return g.static_stylesheet_bucket and len(self.stylesheet_hash) == 27 + + static_stylesheet_prefix = "subreddit-stylesheet/" + + @property + def static_stylesheet_name(self): + return "".join((self.static_stylesheet_prefix, + self.stylesheet_hash, + ".css")) + + @property + def stylesheet_url(self): + from r2.lib.template_helpers import static, get_domain + + if self.stylesheet_is_static: + return static(self.static_stylesheet_name) + else: + return "http://%s/stylesheet.css?v=%s" % (get_domain(cname=False, + subreddit=True), + self.stylesheet_hash) + @property def stylesheet_contents_user(self): try: @@ -340,10 +368,33 @@ class Subreddit(Thing, Printable): except tdb_cassandra.NotFound: wiki = WikiPage.create(self, 'config/stylesheet') wr = wiki.revise(content, previous=prev, author=author, reason=reason, force=force) - self.stylesheet_contents = parsed - self.stylesheet_hash = md5(parsed).hexdigest() - set_last_modified(self, 'stylesheet_contents') - c.site._commit() + + minified = cssmin(parsed) + if minified: + if g.static_stylesheet_bucket: + digest = hashlib.sha1(minified).digest() + self.stylesheet_hash = (base64.urlsafe_b64encode(digest) + .rstrip("=")) + + s3cp.send_file(g.static_stylesheet_bucket, + self.static_stylesheet_name, + minified, + content_type="text/css", + never_expire=True, + replace=False, + ) + + self.stylesheet_contents = "" + else: + self.stylesheet_hash = hashlib.md5(minified).hexdigest() + self.stylesheet_contents = minified + set_last_modified(self, 'stylesheet_contents') + else: + self.stylesheet_contents = "" + self.stylesheet_hash = "" + self.stylesheet_contents_user = "" # reads from wiki; ensure pg clean + self._commit() + ModAction.create(self, c.user, action='wikirevise', details='Updated subreddit stylesheet') return wr diff --git a/r2/r2/templates/reddit.html b/r2/r2/templates/reddit.html index f811d5adc..93818dd50 100644 --- a/r2/r2/templates/reddit.html +++ b/r2/r2/templates/reddit.html @@ -82,21 +82,11 @@ %endif %endif - %if c.can_apply_styles and c.allow_styles and c.site.stylesheet_contents: - <% inline_stylesheet = ( - len(c.site.stylesheet_contents) < 1024 - and '<' not in c.site.stylesheet_contents) %> - %if inline_stylesheet: - ## for very simple stylesheets, we can just include them inline - - %else: - - %endif + %if c.can_apply_styles and c.allow_styles and c.site.stylesheet_hash: + %endif %if getattr(thing, "additional_css", None):