Skip to content

Commit

Permalink
Issue webcompat#609 - Implement Cache-Policy decorator
Browse files Browse the repository at this point in the history
Issue webcompat#609 - Removes code dust
  • Loading branch information
karlcow committed Feb 17, 2017
1 parent 815cb73 commit bab6f6d
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 4 deletions.
69 changes: 69 additions & 0 deletions tests/test_http_caching.py
@@ -0,0 +1,69 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

'''Tests for HTTP Caching on webcompat resources.'''

import os.path
import sys
import unittest

# Add webcompat module to import path
sys.path.append(os.path.realpath(os.pardir))
import webcompat # nopep8

# Any request that depends on parsing HTTP Headers (basically anything
# on the index route, will need to include the following: environ_base=headers
html_headers = {
'HTTP_USER_AGENT': ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; '
'rv:53.0) Gecko/20100101 Firefox/53.0'),
'HTTP_ACCEPT': 'text/html'}


class TestHTTPCaching(unittest.TestCase):
def setUp(self):
webcompat.app.config['TESTING'] = True
self.app = webcompat.app.test_client()

def tearDown(self):
pass

def test_issue_has_etag(self):
'''Check ETAG for issues.'''
rv = self.app.get('/issues/100', environ_base=html_headers)
response_headers = rv.headers
self.assertIn('etag', response_headers)
self.assertIsNotNone(response_headers['etag'])

def test_cache_control(self):
'''Check Cache-Control for issues.'''
rv = self.app.get('/issues/100', environ_base=html_headers)
response_headers = rv.headers
self.assertIn('cache-control', response_headers)
self.assertEqual(response_headers['cache-control'],
'private, max-age=86400')

def test_not_modified_status(self):
'''Checks if we receive a 304 Not Modified.'''
for uri in ['/about',
'/contributors',
'/issues',
'/issues/100',
'/privacy']:
rv = self.app.get(uri, environ_base=html_headers)
response_headers = rv.headers
etag = response_headers['etag']
rv2 = self.app.get(uri,
environ_base=html_headers,
headers={'If-None-Match': etag})
self.assertEqual(rv2.status_code, 304)
self.assertEqual(rv2.data, '')
self.assertIn('cache-control', response_headers)
self.assertEqual(response_headers['cache-control'],
'private, max-age=86400')


if __name__ == '__main__':
unittest.main()
1 change: 0 additions & 1 deletion webcompat/api/endpoints.py
Expand Up @@ -9,7 +9,6 @@
This is used to make API calls to GitHub, either via a logged-in users
credentials or as a proxy on behalf of anonymous or unauthenticated users.'''

import json

from flask import abort
from flask import Blueprint
Expand Down
33 changes: 32 additions & 1 deletion webcompat/helpers.py
Expand Up @@ -5,6 +5,8 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

from datetime import datetime
from functools import wraps
from functools import update_wrapper
import hashlib
import json
import math
Expand All @@ -16,10 +18,10 @@
from babel.dates import format_timedelta
from flask import abort
from flask import g
from flask import make_response
from flask import request
from flask import session
from form import IssueForm
from functools import wraps
from ua_parser import user_agent_parser

from webcompat import app
Expand Down Expand Up @@ -456,3 +458,32 @@ def api_request(method, path, params=None, data=None):
get_response_headers(resource))
else:
abort(404)


def cache_policy(private=True, uri_max_age=86400):
'''Implements a HTTP Cache Decorator.
Adds Cache-Control headers.
* max-age has a 1 day default (86400s)
* and makes it private by default
Adds Etag based on HTTP Body.
Sends a 304 Not Modified in case of If-None-Match.
'''
def set_policy(view):
@wraps(view)
def policy(*args, **kwargs):
response = make_response(view(*args, **kwargs))
# we choose if the resource is private or public
if private:
response.cache_control.private = True
else:
response.cache_control.public = True
# Instructs how long the Cache should keep the resource
response.cache_control.max_age = uri_max_age
# Etag is based on the HTTP body
response.add_etag(response.data)
# to send a 304 Not Modified instead of a full HTTP response
response.make_conditional(request)
return response
return update_wrapper(policy, view)
return set_policy
13 changes: 11 additions & 2 deletions webcompat/views.py
Expand Up @@ -7,11 +7,11 @@
import json
import logging
import os
import urllib

from flask import abort
from flask import flash
from flask import g
from flask import make_response
from flask import redirect
from flask import render_template
from flask import request
Expand All @@ -21,6 +21,7 @@

from form import AUTH_REPORT
from form import PROXY_REPORT
from helpers import cache_policy
from helpers import get_browser_name
from helpers import get_form
from helpers import get_referer
Expand Down Expand Up @@ -143,6 +144,7 @@ def index():


@app.route('/issues')
@cache_policy(private=True, uri_max_age=86400)
def show_issues():
'''Route to display global issues view.'''
if g.user:
Expand All @@ -152,6 +154,7 @@ def show_issues():


@app.route('/issues/new', methods=['GET', 'POST'])
@cache_policy(private=True, uri_max_age=86400)
def create_issue():
"""Creates a new issue.
Expand Down Expand Up @@ -201,14 +204,17 @@ def create_issue():


@app.route('/issues/<int:number>')
@cache_policy(private=True, uri_max_age=86400)
def show_issue(number):
'''Route to display a single issue.'''
if g.user:
get_user_info()
if session.get('show_thanks'):
flash(number, 'thanks')
session.pop('show_thanks')
return render_template('issue.html', number=number)
content = render_template('issue.html', number=number)
response = make_response(content)
return response


@app.route('/me')
Expand Down Expand Up @@ -279,6 +285,7 @@ def get_test_helper(filename):


@app.route('/about')
@cache_policy(private=True, uri_max_age=86400)
def about():
'''Route to display about page.'''
if g.user:
Expand All @@ -287,6 +294,7 @@ def about():


@app.route('/privacy')
@cache_policy(private=True, uri_max_age=86400)
def privacy():
'''Route to display privacy page.'''
if g.user:
Expand All @@ -295,6 +303,7 @@ def privacy():


@app.route('/contributors')
@cache_policy(private=True, uri_max_age=86400)
def contributors():
'''Route to display contributors page.'''
if g.user:
Expand Down

0 comments on commit bab6f6d

Please sign in to comment.