[web] set Vary response header to "Accept-Language" when using content negotiation

This is slightly annoying because the response actually only varies based on the language we decide to send, which has much fewer possible values than Accept-Language, but that's not in the request, so we can't easily use it. Deployments using varnish or similar and controlling the set of available languages will likely want to override this to allow reasonable amounts of caching.

Closes #2105812

authorJulien Cristau <julien.cristau@logilab.fr>
changeset6bcb460826cc
branchdefault
phasepublic
hiddenno
parent revision#586d0e527052 [web/cors] don't overwrite other Vary headers
child revision#acad42456767 [utils] Remove function-in-the-middle call
files modified by this revision
web/request.py
web/test/unittest_web.py
# HG changeset patch
# User Julien Cristau <julien.cristau@logilab.fr>
# Date 1406641504 -7200
# Tue Jul 29 15:45:04 2014 +0200
# Node ID 6bcb460826ccb098ffa28cbf2b09b57ce36a0ac1
# Parent 586d0e5270520e0ec7fa1cf67913717b013b9c6a
[web] set Vary response header to "Accept-Language" when using content negotiation

This is slightly annoying because the response actually only varies
based on the language we decide to send, which has much fewer possible
values than Accept-Language, but that's not in the request, so we can't
easily use it. Deployments using varnish or similar and controlling the
set of available languages will likely want to override this to allow
reasonable amounts of caching.

Closes #2105812

diff --git a/web/request.py b/web/request.py
@@ -137,10 +137,15 @@
1                  self._headers_in.addRawHeader(k, v)
2          #: form parameters
3          self.setup_params(form)
4          #: received body
5          self.content = StringIO()
6 +        # prepare output header
7 +        #: Header used for the final response
8 +        self.headers_out = Headers()
9 +        #: HTTP status use by the final response
10 +        self.status_out  = 200
11          # set up language based on request headers or site default (we don't
12          # have a user yet, and might not get one)
13          self.set_user_language(None)
14          #: dictionary that may be used to store request data that has to be
15          #: shared among various components used to publish the request (views,
@@ -150,15 +155,10 @@
16          #:  to create a relation with another)
17          self.search_state = ('normal',)
18          #: page id, set by htmlheader template
19          self.pageid = None
20          self._set_pageid()
21 -        # prepare output header
22 -        #: Header used for the final response
23 -        self.headers_out = Headers()
24 -        #: HTTP status use by the final response
25 -        self.status_out  = 200
26 
27      def _set_pageid(self):
28          """initialize self.pageid
29          if req.form provides a specific pageid, use it, otherwise build a
30          new one.
@@ -999,10 +999,11 @@
31                  return
32              except KeyError:
33                  pass
34          if vreg.config.get('language-negociation', False):
35              # 2. http accept-language
36 +            self.headers_out.addHeader('Vary', 'Accept-Language')
37              for lang in self.header_accept_language():
38                  if lang in self.translations:
39                      self.set_language(lang)
40                      return
41          # 3. site's default language
diff --git a/web/test/unittest_web.py b/web/test/unittest_web.py
@@ -99,13 +99,17 @@
42 
43      def test_language_neg(self):
44          headers = {'Accept-Language': 'fr'}
45          webreq = self.web_request(headers=headers)
46          self.assertIn('lang="fr"', webreq.read())
47 +        vary = [h.lower().strip() for h in webreq.getheader('Vary').split(',')]
48 +        self.assertIn('accept-language', vary)
49          headers = {'Accept-Language': 'en'}
50          webreq = self.web_request(headers=headers)
51          self.assertIn('lang="en"', webreq.read())
52 +        vary = [h.lower().strip() for h in webreq.getheader('Vary').split(',')]
53 +        self.assertIn('accept-language', vary)
54 
55      def test_response_codes(self):
56          with self.admin_access.client_cnx() as cnx:
57              admin_eid = cnx.user.eid
58          # guest can't see admin