Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Pipeline css #1245

Merged
merged 29 commits into from
Aug 6, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
aec7c44
Convert gittip.css to a dynamic simplate
chadwhitacre Jul 25, 2013
316757c
Add a CSS_HREF config for finding the CSS file
chadwhitacre Jul 25, 2013
8d46a96
Provide for versioning in CSS_HREF
chadwhitacre Jul 25, 2013
589bc71
Add CSS_HREF to default_test.env
chadwhitacre Jul 25, 2013
a7d5652
Merge branch 'master' into pipeline-css
chadwhitacre Jul 25, 2013
0fa0214
Be strict about what asset versioning
chadwhitacre Jul 29, 2013
1a60bf8
Install sass using a post_compile hook
chadwhitacre Jul 30, 2013
44ca9e7
Merge branch 'master' into pipeline-css
chadwhitacre Jul 30, 2013
3ad83e0
Tweak version of CSS in dev
chadwhitacre Jul 30, 2013
24042ce
I guess we don't have indent here?
chadwhitacre Jul 30, 2013
642e14a
Bring back the cache_static module
chadwhitacre Jul 30, 2013
b23dbff
Let's add the indent function to post_compile
chadwhitacre Jul 30, 2013
eca80dc
Fix bug where non-versioned assets were 404
chadwhitacre Jul 30, 2013
2cf0c44
Update some docstrings for cache_static
chadwhitacre Jul 30, 2013
310b1f2
Constrain caching to /assets/ instead of /assets
chadwhitacre Jul 30, 2013
21a37b1
Standardize logic for inbound cache hook
chadwhitacre Jul 30, 2013
0414b5b
Only cache things under %version
chadwhitacre Jul 30, 2013
b88bcbc
Fix regressions in request API usage for caching
chadwhitacre Jul 30, 2013
f26ab69
Merge branch 'master' into pipeline-css
chadwhitacre Jul 30, 2013
0560bf4
Provide better error message when envvars missing
chadwhitacre Jul 30, 2013
28a2070
Start prefixing our envvars with GITTIP_
chadwhitacre Jul 30, 2013
1ada2ae
Add a GITTIP_CACHE_STATIC configuration knob
chadwhitacre Jul 30, 2013
dc9da66
Update README for GITTIP_CSS_HREF
chadwhitacre Jul 30, 2013
a0e29e7
Merge branch 'master' into pipeline-css
chadwhitacre Aug 3, 2013
ff644e0
Simplify some logic
chadwhitacre Aug 6, 2013
146797c
Separate dash case from bad version case
chadwhitacre Aug 6, 2013
011dafa
Tighten up caching while wyze debugs Chrome
chadwhitacre Aug 6, 2013
d3c9362
Punt on 304s for simplates
chadwhitacre Aug 6, 2013
68791e4
Merge branch 'master' into pipeline-css
chadwhitacre Aug 6, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ $(env_bin)/swaddle:
./$(env_bin)/pip install -e ./

clean:
rm -rf env *.egg *.egg-info tests/env gittip.css
rm -rf env *.egg *.egg-info tests/env
find . -name \*.pyc -delete

local.env:
Expand Down Expand Up @@ -79,7 +79,3 @@ tests/env:
echo "Creating a tests/env file ..."
echo
cp default_tests.env tests/env

css:
scss -t compressed scss/gittip.scss gittip.css
mv gittip.css www/assets/%version/gittip.css
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Table of Contents
- [Building and Launching](#building-and-launching)
- [Help!](#help)
- [Configuration](#configuration)
- [Modifying CSS](#modifying-css)
- [Testing](#testing-)
- [Setting up a Database](#local-database-setup)
- [API](#api)
Expand Down Expand Up @@ -195,6 +196,21 @@ should change the `DATABASE_URL` using the following format:
DATABASE_URL=postgres://<username>@localhost/<database name>


Modifying CSS
=============

We use SCSS, with files stored in `scss/`. Out of the box, your Gittip
installation will use the stylesheet from production, per the `GITTIP_CSS_HREF`
setting in `local.env`. If you want to modify styles then you should install
[sass](http://sass-lang.com/) and change `GITTIP_CSS_HREF` in your `local.env`
to `/assets/-/gittip.css`. That will route to
`www/assets/%version/gittip.css.spt`, which is a simplate that shells out to
`sass` to dynamically generate the stylesheet on each request. The `-` prevents
HTTP caching. Sass does its own caching on disk so it's performant enough for
development (in production we route through a CDN so the origin only gets hit
once per new version).


Testing [![Testing](https://secure.travis-ci.org/gittip/www.gittip.com.png)](http://travis-ci.org/gittip/www.gittip.com)
=======

Expand Down Expand Up @@ -329,6 +345,7 @@ take effect. Once restarted, the test suite should pass for you. These changes
will not persist after a reboot, so you will have to set these again after
a reboot.


API
===

Expand Down
32 changes: 32 additions & 0 deletions bin/post_compile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash

# This script gets run by heroku-buildpack-python during deployment.


indent() {
sed -u 's/^/ /'
}



# Install SASS.
# =============
# Adapted from https://github.com/abhishekmunie/heroku-buildpack-static-css/blo
# b/d5879d74e615bb25db0ed3d780dba71def661b0b/bin/compile

echo "-----> Installing SASS..."
mkdir /app/.gem
gem install sass --no-rdoc --no-ri --install-dir /app/.gem | indent


# Set up environment variables.
# =============================
# This is done with a script in .profile.d/:
#
# https://devcenter.heroku.com/articles/profiled

mkdir -p .profile.d
cat << EOF > .profile.d/gittip-sass.sh
export PATH=/app/.gem/bin:$PATH
export GEM_PATH=/app/.gem:$GEM_PATH
EOF
31 changes: 9 additions & 22 deletions configure-aspen.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
from aspen import log_dammit


version_file = os.path.join(website.www_root, 'version.txt')
__version__ = open(version_file).read().strip()
website.version = os.environ['__VERSION__'] = __version__


website.renderer_default = "tornado"


Expand All @@ -23,33 +28,15 @@
gittip.wireup.mixpanel(website)
gittip.wireup.nanswers()
gittip.wireup.nmembers(website)


website.bitbucket_consumer_key = os.environ['BITBUCKET_CONSUMER_KEY'].decode('ASCII')
website.bitbucket_consumer_secret = os.environ['BITBUCKET_CONSUMER_SECRET'].decode('ASCII')
website.bitbucket_callback = os.environ['BITBUCKET_CALLBACK'].decode('ASCII')

website.github_client_id = os.environ['GITHUB_CLIENT_ID'].decode('ASCII')
website.github_client_secret = os.environ['GITHUB_CLIENT_SECRET'].decode('ASCII')
website.github_callback = os.environ['GITHUB_CALLBACK'].decode('ASCII')

website.twitter_consumer_key = os.environ['TWITTER_CONSUMER_KEY'].decode('ASCII')
website.twitter_consumer_secret = os.environ['TWITTER_CONSUMER_SECRET'].decode('ASCII')
website.twitter_access_token = os.environ['TWITTER_ACCESS_TOKEN'].decode('ASCII')
website.twitter_access_token_secret = os.environ['TWITTER_ACCESS_TOKEN_SECRET'].decode('ASCII')
website.twitter_callback = os.environ['TWITTER_CALLBACK'].decode('ASCII')

website.bountysource_www_host = os.environ['BOUNTYSOURCE_WWW_HOST'].decode('ASCII')
website.bountysource_api_host = os.environ['BOUNTYSOURCE_API_HOST'].decode('ASCII')
website.bountysource_api_secret = os.environ['BOUNTYSOURCE_API_SECRET'].decode('ASCII')
website.bountysource_callback = os.environ['BOUNTYSOURCE_CALLBACK'].decode('ASCII')
gittip.wireup.envvars(website)


# Up the threadpool size: https://github.com/gittip/www.gittip.com/issues/1098
def up_minthreads(website):
# Discovered the following API by inspecting in pdb and browsing source.
# This requires network_engine.bind to have already been called.
website.network_engine.cheroot_server.requests.min = int(os.environ['MIN_THREADS'])
website.network_engine.cheroot_server.requests.min = \
int(os.environ['MIN_THREADS'])

website.hooks.startup.insert(0, up_minthreads)

Expand All @@ -60,7 +47,7 @@ def up_minthreads(website):
, gittip.csrf.inbound
]

#website.hooks.inbound_core += [gittip.cache_static.inbound]
website.hooks.inbound_core += [gittip.cache_static.inbound]

website.hooks.outbound += [ gittip.authentication.outbound
, gittip.csrf.outbound
Expand Down
2 changes: 2 additions & 0 deletions default_local.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ CANONICAL_SCHEME=http
MIN_THREADS=10
DATABASE_URL=
DATABASE_MAXCONN=10
GITTIP_CSS_HREF=https://www.gittip.com/assets/-/gittip.css
GITTIP_CACHE_STATIC=no
STRIPE_SECRET_API_KEY=1
STRIPE_PUBLISHABLE_API_KEY=1
BALANCED_API_SECRET=90bb3648ca0a11e1a977026ba7e239a9
Expand Down
2 changes: 2 additions & 0 deletions default_tests.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ CANONICAL_SCHEME=http
MIN_THREADS=10
DATABASE_URL=
DATABASE_MAXCONN=10
GITTIP_CSS_HREF=
GITTIP_CACHE_STATIC=no
STRIPE_SECRET_API_KEY=1
STRIPE_PUBLISHABLE_API_KEY=1
BALANCED_API_SECRET=90bb3648ca0a11e1a977026ba7e239a9
Expand Down
126 changes: 99 additions & 27 deletions gittip/cache_static.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,128 @@
"""
Handles caching of static resources.
"""
from os import path
import os
from calendar import timegm
from email.utils import parsedate
from wsgiref.handlers import format_date_time

from aspen import Response


def inbound(request):
def version_is_available(request):
"""Return a boolean, whether we have the version they asked for.
"""
path = request.line.uri.path
version = request.context['__version__']
return path['version'] == version if 'version' in path else True


def version_is_dash(request):
"""Return a boolean, whether the version they asked for is -.
"""
return request.line.uri.path.get('version') == '-'


def get_last_modified(fs_path):
"""Get the last modified time, as int, of the file pointed to by fs_path.
"""
Checks the last modified time of a file against
an If-Modified-Since header and responds with
a 304 if appropriate.
return int(os.path.getctime(fs_path))


def inbound(request):
"""Try to serve a 304 for resources under assets/.
"""
uri = request.line.uri
version = request.context['__version__']

if not uri.startswith('/assets'):
if not uri.startswith('/assets/'):

# Only apply to the assets/ directory.

return request
elif not version.endswith('dev') and version in uri:
# These will be cached indefinitely in the outbound hook

if version_is_dash(request):

# Special-case a version of '-' to never 304/404 here.

return request

if not version_is_available(request):

# Don't serve one version of a file as if it were another.

raise Response(404)

ims = request.headers.get('If-Modified-Since')
last_modified = int(path.getctime(request.fs))
if not ims:

# This client doesn't care about when the file was modified.

return request

if request.fs.endswith('.spt'):

if ims:
# This is a requests for a dynamic resource. Perhaps in the future
# we'll delegate to such resources to compute a sensible Last-Modified
# or E-Tag, but for now we punt. This is okay, because we expect to
# put our dynamic assets behind a CDN in production.

return request


try:
ims = timegm(parsedate(ims))
if ims >= last_modified:
raise Response(304, headers={
'Last-Modified': format_date_time(last_modified),
'Cache-Control': 'no-cache'
})
except:

# Malformed If-Modified-Since header. Proceed with the request.

return request

last_modified = get_last_modified(request.fs)
if ims < last_modified:

# The file has been modified since. Serve the whole thing.

return request


# Huzzah!
# =======
# We can serve a 304! :D

response = Response(304)
response.headers['Last-Modified'] = format_date_time(last_modified)
response.headers['Cache-Control'] = 'no-cache'
raise response


def outbound(response):
"""Set caching headers for resources under assets/.
"""
request = response.request
website = request.website
uri = request.line.uri
if not uri.startswith('/assets'):
return response

version = request.context['__version__']
if not version.endswith('dev') and version in uri:
# This specific asset is versioned, so
# it's fine to cache this forever
response.headers['Expires'] = 'Sun, 17 Jan 2038 19:14:07 GMT'
response.headers['X-Gittip-Version'] = version

if not uri.startswith('/assets/'):
return response

response.headers.cookie.clear()

if response.code == 304:
return response

if website.cache_static:

# https://developers.google.com/speed/docs/best-practices/caching
response.headers['Cache-Control'] = 'public'
response.headers['Vary'] = 'accept-encoding'

else:
# Asset is not versioned or dev version is running.
last_modified = int(path.getctime(request.fs))
response.headers['Last-Modified'] = format_date_time(last_modified)
response.headers['Cache-Control'] = 'no-cache'
if 'version' in uri.path:
# This specific asset is versioned, so it's fine to cache it.
response.headers['Expires'] = 'Sun, 17 Jan 2038 19:14:07 GMT'
else:
# Asset is not versioned. Don't cache it, but set Last-Modified.
last_modified = get_last_modified(request.fs)
response.headers['Last-Modified'] = format_date_time(last_modified)
60 changes: 60 additions & 0 deletions gittip/wireup.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,63 @@ def nmembers(website):
from gittip.models import community
community.NMEMBERS_THRESHOLD = int(os.environ['NMEMBERS_THRESHOLD'])
website.NMEMBERS_THRESHOLD = community.NMEMBERS_THRESHOLD

def envvars(website):

missing_keys = []

def envvar(key):
if key not in os.environ:
missing_keys.append(key)
return ""
return os.environ[key].decode('ASCII')

def is_yesish(val):
return val.lower() in ('1', 'true', 'yes')

website.bitbucket_consumer_key = envvar('BITBUCKET_CONSUMER_KEY')
website.bitbucket_consumer_secret = envvar('BITBUCKET_CONSUMER_SECRET')
website.bitbucket_callback = envvar('BITBUCKET_CALLBACK')

website.github_client_id = envvar('GITHUB_CLIENT_ID')
website.github_client_secret = envvar('GITHUB_CLIENT_SECRET')
website.github_callback = envvar('GITHUB_CALLBACK')

website.twitter_consumer_key = envvar('TWITTER_CONSUMER_KEY')
website.twitter_consumer_secret = envvar('TWITTER_CONSUMER_SECRET')
website.twitter_access_token = envvar('TWITTER_ACCESS_TOKEN')
website.twitter_access_token_secret = envvar('TWITTER_ACCESS_TOKEN_SECRET')
website.twitter_callback = envvar('TWITTER_CALLBACK')

website.bountysource_www_host = envvar('BOUNTYSOURCE_WWW_HOST')
website.bountysource_api_host = envvar('BOUNTYSOURCE_API_HOST')
website.bountysource_api_secret = envvar('BOUNTYSOURCE_API_SECRET')
website.bountysource_callback = envvar('BOUNTYSOURCE_CALLBACK')

website.css_href = envvar('GITTIP_CSS_HREF') \
.replace('%version', website.version)
website.cache_static = is_yesish(envvar('GITTIP_CACHE_STATIC'))

if missing_keys:
missing_keys.sort()
these = len(missing_keys) != 1 and 'these' or 'this'
plural = len(missing_keys) != 1 and 's' or ''
aspen.log_dammit("=" * 42)
aspen.log_dammit( "Oh no! Gittip.com needs %s missing " % these
, "environment variable%s:" % plural
)
aspen.log_dammit(" ")
for key in missing_keys:
aspen.log_dammit(" " + key)
aspen.log_dammit(" ")
aspen.log_dammit( "(Sorry, we must've started looking for "
, "%s since you last updated Gittip!)" % these
)
aspen.log_dammit(" ")
aspen.log_dammit("Running Gittip locally? Edit ./local.env.")
aspen.log_dammit("Running the test suite? Edit ./tests/env.")
aspen.log_dammit(" ")
aspen.log_dammit("See ./default_local.env for hints.")

aspen.log_dammit("=" * 42)
raise SystemExit
2 changes: 1 addition & 1 deletion templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/assets/reset.css"
type="text/css" charset="utf-8" />
<link rel="stylesheet" href="/assets/{{ __version__ }}/gittip.css"
<link rel="stylesheet" href="{{ website.css_href }}"
type="text/css" charset="utf-8" />

<!-- http://css-tricks.com/snippets/jquery/fallback-for-cdn-hosted-jquery/ -->
Expand Down
1 change: 0 additions & 1 deletion www/assets/%version/gittip.css

This file was deleted.

Loading