Skip to content

Commit 73a05d2

Browse files
authored
Proxito: actually cache robots.txt and sitemap.xml (#10123)
Forgot to actually inherit from the CDNCacheControlMixin, so caching for these views isn't actually working on .com.
1 parent b986929 commit 73a05d2

File tree

3 files changed

+92
-6
lines changed

3 files changed

+92
-6
lines changed

readthedocs/proxito/tests/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def setUp(self):
3333
users=[self.eric],
3434
main_language_project=None,
3535
)
36-
self.project.versions.update(privacy_level=PUBLIC)
36+
self.project.versions.update(privacy_level=PUBLIC, built=True, active=True)
3737
self.version = self.project.versions.get(slug=LATEST)
3838

3939
self.subproject = fixture.get(

readthedocs/proxito/tests/test_headers.py

+28
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,34 @@ def test_cache_headers_public_version_with_private_projects_allowed(self):
145145
self.assertEqual(r.status_code, 200)
146146
self.assertEqual(r["CDN-Cache-Control"], "public")
147147

148+
@override_settings(ALLOW_PRIVATE_REPOS=False)
149+
def test_cache_headers_robots_txt_with_private_projects_not_allowed(self):
150+
r = self.client.get("/robots.txt", HTTP_HOST="project.dev.readthedocs.io")
151+
self.assertEqual(r.status_code, 200)
152+
self.assertEqual(r["CDN-Cache-Control"], "public")
153+
self.assertEqual(r["Cache-Tag"], "project,project:robots.txt")
154+
155+
@override_settings(ALLOW_PRIVATE_REPOS=True)
156+
def test_cache_headers_robots_txt_with_private_projects_allowed(self):
157+
r = self.client.get("/robots.txt", HTTP_HOST="project.dev.readthedocs.io")
158+
self.assertEqual(r.status_code, 200)
159+
self.assertEqual(r["CDN-Cache-Control"], "public")
160+
self.assertEqual(r["Cache-Tag"], "project,project:robots.txt")
161+
162+
@override_settings(ALLOW_PRIVATE_REPOS=False)
163+
def test_cache_headers_robots_txt_with_private_projects_not_allowed(self):
164+
r = self.client.get("/sitemap.xml", HTTP_HOST="project.dev.readthedocs.io")
165+
self.assertEqual(r.status_code, 200)
166+
self.assertEqual(r["CDN-Cache-Control"], "public")
167+
self.assertEqual(r["Cache-Tag"], "project,project:sitemap.xml")
168+
169+
@override_settings(ALLOW_PRIVATE_REPOS=True)
170+
def test_cache_headers_robots_txt_with_private_projects_allowed(self):
171+
r = self.client.get("/sitemap.xml", HTTP_HOST="project.dev.readthedocs.io")
172+
self.assertEqual(r.status_code, 200)
173+
self.assertEqual(r["CDN-Cache-Control"], "public")
174+
self.assertEqual(r["Cache-Tag"], "project,project:sitemap.xml")
175+
148176

149177
class ProxitoV2HeaderTests(ProxitoHeaderTests):
150178
# TODO: remove this class once the new implementation is the default.

readthedocs/proxito/views/serve.py

+63-5
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,19 @@
4242

4343

4444
class ServePageRedirect(CDNCacheControlMixin, ServeRedirectMixin, ServeDocsMixin, View):
45+
46+
"""
47+
Page redirect view.
48+
49+
This allows users to redirec to the default version of a project.
50+
For example:
51+
52+
- /page/api/index.html -> /en/latest/api/index.html
53+
- /projects/subproject/page/index.html -> /projects/subproject/en/latest/api/index.html
54+
"""
55+
4556
def get(self, request, subproject_slug=None, filename=""):
57+
"""Handle all page redirects."""
4658

4759
unresolved_domain = request.unresolved_domain
4860
project = unresolved_domain.project
@@ -73,6 +85,14 @@ def get(self, request, subproject_slug=None, filename=""):
7385

7486

7587
class ServeDocsBase(CDNCacheControlMixin, ServeRedirectMixin, ServeDocsMixin, View):
88+
89+
"""
90+
Serve docs view.
91+
92+
This view serves all the documentation pages,
93+
and handles canonical redirects.
94+
"""
95+
7696
def get(
7797
self,
7898
request,
@@ -89,6 +109,7 @@ def get(
89109
``subproject_slash`` is used to determine if the subproject URL has a slash,
90110
so that we can decide if we need to serve docs or add a /.
91111
"""
112+
# pylint: disable=too-many-locals
92113
unresolved_domain = request.unresolved_domain
93114
# Handle requests that need canonicalizing first,
94115
# e.g. HTTP -> HTTPS, redirect to canonical domain, etc.
@@ -282,6 +303,7 @@ def _get_canonical_redirect_type(self, request):
282303
.exists()
283304
)
284305
# For .com we need to check if the project supports custom domains.
306+
# pylint: disable=protected-access
285307
if canonical_domain and resolver._use_cname(project):
286308
log.debug(
287309
"Proxito Public Domain -> Canonical Domain Redirect.",
@@ -442,9 +464,15 @@ class ServeDocs(SettingsOverrideObject):
442464
_default_class = ServeDocsBase
443465

444466

445-
class ServeError404Base(ServeRedirectMixin, ServeDocsMixin, View):
467+
class ServeError404Base(CDNCacheControlMixin, ServeRedirectMixin, ServeDocsMixin, View):
468+
469+
"""
470+
Proxito handler for 404 pages.
446471
447-
def get(self, request, proxito_path, template_name='404.html'):
472+
This view is called by an internal nginx redirect when there is a 404.
473+
"""
474+
475+
def get(self, request, proxito_path):
448476
"""
449477
Handler for 404 pages on subdomains.
450478
@@ -460,6 +488,7 @@ def get(self, request, proxito_path, template_name='404.html'):
460488
with the default version and finally, if none of them are found, the Read
461489
the Docs default page (Maze Found) is rendered by Django and served.
462490
"""
491+
# pylint: disable=too-many-locals
463492
log.bind(proxito_path=proxito_path)
464493
log.debug('Executing 404 handler.')
465494

@@ -798,10 +827,14 @@ class ServeError404(SettingsOverrideObject):
798827
_default_class = ServeError404Base
799828

800829

801-
class ServeRobotsTXTBase(ServeDocsMixin, View):
830+
class ServeRobotsTXTBase(CDNCacheControlMixin, CDNCacheTagsMixin, ServeDocsMixin, View):
831+
832+
"""Serve robots.txt from the domain's root."""
802833

803834
# Always cache this view, since it's the same for all users.
804835
cache_response = True
836+
# Extra cache tag to invalidate only this view if needed.
837+
project_cache_tag = "robots.txt"
805838

806839
def get(self, request):
807840
"""
@@ -822,7 +855,7 @@ def get(self, request):
822855
)
823856

824857
# Verify if the project is marked as spam and return a custom robots.txt
825-
elif "readthedocsext.spamfighting" in settings.INSTALLED_APPS:
858+
if "readthedocsext.spamfighting" in settings.INSTALLED_APPS:
826859
from readthedocsext.spamfighting.utils import is_robotstxt_denied # noqa
827860
if is_robotstxt_denied(project):
828861
return render(
@@ -894,15 +927,30 @@ def _get_hidden_paths(self, project):
894927
]
895928
return hidden_paths
896929

930+
def _get_project(self):
931+
# Method used by the CDNCacheTagsMixin class.
932+
return self.request.unresolved_domain.project
933+
934+
def _get_version(self):
935+
# Method used by the CDNCacheTagsMixin class.
936+
# This view isn't explicitly mapped to a version,
937+
# but it can be when we serve a custom robots.txt file.
938+
# TODO: refactor how we set cache tags to avoid this.
939+
return None
940+
897941

898942
class ServeRobotsTXT(SettingsOverrideObject):
899943
_default_class = ServeRobotsTXTBase
900944

901945

902-
class ServeSitemapXMLBase(View):
946+
class ServeSitemapXMLBase(CDNCacheControlMixin, CDNCacheTagsMixin, View):
947+
948+
"""Serve sitemap.xml from the domain's root."""
903949

904950
# Always cache this view, since it's the same for all users.
905951
cache_response = True
952+
# Extra cache tag to invalidate only this view if needed.
953+
project_cache_tag = "sitemap.xml"
906954

907955
def get(self, request):
908956
"""
@@ -1034,6 +1082,16 @@ def changefreqs_generator():
10341082
content_type='application/xml',
10351083
)
10361084

1085+
def _get_project(self):
1086+
# Method used by the CDNCacheTagsMixin class.
1087+
return self.request.unresolved_domain.project
1088+
1089+
def _get_version(self):
1090+
# Method used by the CDNCacheTagsMixin class.
1091+
# This view isn't explicitly mapped to a version,
1092+
# TODO: refactor how we set cache tags to avoid this.
1093+
return None
1094+
10371095

10381096
class ServeSitemapXML(SettingsOverrideObject):
10391097
_default_class = ServeSitemapXMLBase

0 commit comments

Comments
 (0)