Skip to content

Commit

Permalink
Merge pull request #343 from camptocamp/cache_options
Browse files Browse the repository at this point in the history
Make the CORS pre-flight requests cacheable
  • Loading branch information
Patrick Valsecchi authored Jun 18, 2019
2 parents dd04dab + 79476c0 commit ac1fad4
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 1 deletion.
15 changes: 15 additions & 0 deletions acceptance_tests/tests/tests/test_cors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from c2cwsgiutils.acceptance.connection import Connection, CacheExpected


def test_pre_flight(app_connection: Connection):
r = app_connection.options("hello", cache_expected=CacheExpected.YES, headers={
"Origin": "http://example.com",
"Access-Control-Request-Method": "POST",
"Access-Control-Request-Headers": "X-PINGOTHER, Content-Type"
})
assert r.headers['Access-Control-Allow-Origin'] == 'http://example.com'
assert set(r.headers['Access-Control-Allow-Methods'].split(",")) == {'GET', 'HEAD', 'PUT',
'POST', 'OPTIONS'}
assert set(r.headers['Access-Control-Allow-Headers'].split(",")) == {'X-PINGOTHER', 'Content-Type'}
assert r.headers['Access-Control-Max-Age'] == '86400'
assert set(r.headers['Vary'].split(',')) == {'Origin'}
15 changes: 14 additions & 1 deletion c2cwsgiutils/acceptance/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ def delete(self, url: str, expected_status: int = 204, headers: Optional[Mapping
self._check_cors(cors, r)
return r

def options(self, url: str, expected_status: int = 200, headers: Optional[Mapping[str, str]] = None,
cache_expected: CacheExpected = CacheExpected.NO, **kwargs: Any) \
-> requests.Response:
"""
get the given URL (relative to the root of API).
"""
with self.session.options(self.base_url + url, headers=self._merge_headers(headers, False),
**kwargs) as r:
check_response(r, expected_status, cache_expected=cache_expected)
return r

def _cors_headers(self, cors: bool) -> Mapping[str, str]:
if cors:
return {
Expand Down Expand Up @@ -166,7 +177,9 @@ def check_response(r: requests.Response, expected_status: int = 200,
assert 'no-cache' in cache_control
elif cache_expected == CacheExpected.YES:
assert 'Cache-Control' in r.headers
assert 'max-age' in r.headers['Cache-Control']
assert 'max-age=' in r.headers['Cache-Control']
assert 'max-age=0' not in r.headers['Cache-Control']
assert 'no-cache' not in r.headers['Cache-Control']


def _get_json(r: requests.Response) -> Any:
Expand Down
25 changes: 25 additions & 0 deletions c2cwsgiutils/services.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from typing import Any

from cornice import Service
import logging
from pyramid.request import Request
from pyramid.response import Response

LOG = logging.getLogger(__name__)


def create(name: str, path: str, *args: Any, **kwargs: Any) -> Service:
Expand All @@ -12,4 +17,24 @@ def create(name: str, path: str, *args: Any, **kwargs: Any) -> Service:
kwargs.setdefault('depth', 2) # to make venusian find the good frame
kwargs.setdefault('http_cache', 0) # disable client side and proxy caching by default
kwargs.setdefault('renderer', 'fast_json')
kwargs.setdefault('filters', []).append(_cache_cors)
return Service(name, path, *args, **kwargs)


def _cache_cors(response: Response, request: Request) -> Response:
"""
Cornice filter that fixes the Cache-Control header for pre-flight requests (OPTIONS)
"""
try:
if request.method == 'OPTIONS' and 'Access-Control-Max-Age' in response.headers:
response.cache_control = {'max-age': int(response.headers['Access-Control-Max-Age'])}
if response.vary is None:
response.vary = ['Origin']
elif 'Origin' not in response.vary:
response.vary.append('Origin')
except Exception:
# cornice catches exceptions from filters, and tries call back the filter with only the request.
# This leads to a useless message in case of error...
LOG.error('Failed fixing cache headers for CORS', exc_info=True)
raise
return response

0 comments on commit ac1fad4

Please sign in to comment.