From 50a5daa60c1888daff22cdcc587d68abe82cc727 Mon Sep 17 00:00:00 2001 From: Neil Williams Date: Wed, 19 Oct 2011 08:57:23 -0700 Subject: [PATCH] Allow static files to be served from other domains. --- r2/example.ini | 10 +++- r2/r2/lib/app_globals.py | 2 + r2/r2/lib/js.py | 8 ++- r2/r2/lib/template_helpers.py | 91 +++++++++++++++++++++++++++-------- 4 files changed, 86 insertions(+), 25 deletions(-) diff --git a/r2/example.ini b/r2/example.ini index bf834c941..6df25e203 100755 --- a/r2/example.ini +++ b/r2/example.ini @@ -64,7 +64,7 @@ admins = reddit # the default subreddit for submissions default_sr = reddit.com # default header image url -default_header_url = /static/reddit.com.header.png +default_header_url = reddit.com.header.png # time for the page cache (for unlogged in users) page_cache_time = 90 @@ -285,6 +285,14 @@ stylesheet = reddit.css stylesheet_rtl = reddit-rtl.css # location of the static directory static_path = /static/ +# if set, these are the domains used for static files served over http and https +# if not set, no domain will be specified +static_domain = +static_secure_domain = +# if this is true, append .gz to CSS and JS files served from the static 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 # make frontpage 100% dart frontpage_dart = false diff --git a/r2/r2/lib/app_globals.py b/r2/r2/lib/app_globals.py index f8d6dc485..575eb1163 100755 --- a/r2/r2/lib/app_globals.py +++ b/r2/r2/lib/app_globals.py @@ -95,6 +95,8 @@ class Globals(object): 's3_media_direct', 'disable_captcha', 'disable_ads', + 'static_pre_gzipped', + 'static_secure_pre_gzipped', ] tuple_props = ['stalecaches', diff --git a/r2/r2/lib/js.py b/r2/r2/lib/js.py index e166a4b6d..c8de7aae5 100755 --- a/r2/r2/lib/js.py +++ b/r2/r2/lib/js.py @@ -91,8 +91,7 @@ class Module(Source): if g.uncompressedJS: return "".join(source.use() for source in self.sources) else: - url = os.path.join(g.static_path, self.name) - return script_tag.format(src=static(url)) + return script_tag.format(src=static(self.name)) class StringsSource(Source): """A virtual source consisting of localized strings from r2.lib.strings.""" @@ -157,7 +156,7 @@ class LocalizedModule(Module): return embed + StringsSource().use() else: name, ext = os.path.splitext(self.name) - url = os.path.join(g.static_path, name + "." + get_lang()[0] + ext) + url = name + "." + get_lang()[0] + ext return script_tag.format(src=static(url)) class JQuery(Module): @@ -171,8 +170,7 @@ class JQuery(Module): def use(self): from r2.lib.template_helpers import static if c.secure or c.user.pref_local_js: - path = os.path.join(g.static_path, self.name) - return script_tag.format(src=static(path)) + return script_tag.format(src=static(self.name)) else: ext = ".js" if g.uncompressedJS else ".min.js" return script_tag.format(src=self.cdn_src+ext) diff --git a/r2/r2/lib/template_helpers.py b/r2/r2/lib/template_helpers.py index 2e82f92fc..efeb68f38 100644 --- a/r2/r2/lib/template_helpers.py +++ b/r2/r2/lib/template_helpers.py @@ -30,10 +30,36 @@ import simplejson import os.path from copy import copy import random +import urlparse from pylons import g, c from pylons.i18n import _, ungettext -def static(path): +def is_encoding_acceptable(encoding_to_check): + """"Check if a content encoding is acceptable to the user agent. + + An encoding is determined acceptable if the encoding (or the special '*' encoding) + is specified in the Accept-Encoding header with a quality value > 0. + """ + header = request.headers.get('Accept-Encoding', '') + encodings = header.split(',') + + for value in encodings: + if ';' not in value: + name = value.strip() + else: + coding_name, quality = value.split(';') + if '=' not in quality: + continue + q, value = quality.split('=') + if float(value) == 0: + continue + name = coding_name.strip() + + if name in (encoding_to_check, '*'): + return True + return False + +def static(path, allow_gzip=True): """ Simple static file maintainer which automatically paths and versions files being served out of static. @@ -42,29 +68,56 @@ def static(path): version of the file is set to be random to prevent caching and it mangles the path to point to the uncompressed versions. """ - - dirname, fname = os.path.split(path) - fname = fname.split('?')[0] - query = "" + dirname, filename = os.path.split(path) + extension = os.path.splitext(filename)[1] + is_text = extension in ('.js', '.css') + can_gzip = is_text and is_encoding_acceptable('gzip') + should_gzip = allow_gzip and can_gzip - # if uncompressed, randomize a url query to bust caches - if g.uncompressedJS: - query = "?v=" + str(random.random()).split(".")[-1] + path_components = [] + actual_filename = None + + if not c.secure and g.static_domain: + scheme = 'http' + domain = g.static_domain + query = None + suffix = '.gz' if should_gzip and g.static_pre_gzipped else '' + elif c.secure and g.static_secure_domain: + scheme = 'https' + domain = g.static_secure_domain + query = None + suffix = '.gz' if should_gzip and g.static_secure_pre_gzipped else '' else: - if fname in g.static_names: - fname = g.static_names[fname] - path = os.path.join(dirname, fname) + path_components.append(c.site.static_path) + query = None - # don't mangle paths - if dirname: - return path + query + if g.uncompressedJS: + query = 'v=' + str(random.randint(1, 1000000)) - if g.uncompressedJS: - extension = path.split(".")[1:] - if extension and extension[-1] in ("js", "css"): - return os.path.join(c.site.static_path, extension[-1], path) + query + # unminified static files are in type-specific subdirectories + if not dirname and is_text: + path_components.append(extension[1:]) + + actual_filename = filename + + scheme = None + domain = None + suffix = '' + + path_components.append(dirname) + if not actual_filename: + actual_filename = g.static_names.get(filename, filename) + path_components.append(actual_filename + suffix) + + actual_path = os.path.join(*path_components) + return urlparse.urlunsplit(( + scheme, + domain, + actual_path, + query, + None + )) - return os.path.join(c.site.static_path, path) + query def s3_https_if_secure(url): # In the event that more media sources (other than s3) are added, this function should be corrected