Skip to content

Commit d9711e8

Browse files
committed
Proxito V2
1 parent d2eadec commit d9711e8

File tree

2 files changed

+136
-1
lines changed

2 files changed

+136
-1
lines changed

readthedocs/projects/models.py

+5
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,7 @@ def add_features(sender, **kwargs):
18241824
DISABLE_PAGEVIEWS = "disable_pageviews"
18251825
DISABLE_SPHINX_DOMAINS = "disable_sphinx_domains"
18261826
RESOLVE_PROJECT_FROM_HEADER = "resolve_project_from_header"
1827+
USE_UNRESOLVER_WITH_PROXITO = "use_unresolver_with_proxito"
18271828

18281829
# Versions sync related features
18291830
SKIP_SYNC_TAGS = 'skip_sync_tags'
@@ -1939,6 +1940,10 @@ def add_features(sender, **kwargs):
19391940
RESOLVE_PROJECT_FROM_HEADER,
19401941
_("Allow usage of the X-RTD-Slug header"),
19411942
),
1943+
(
1944+
USE_UNRESOLVER_WITH_PROXITO,
1945+
_("Use new unresolver implementation for serving documentation files."),
1946+
),
19421947

19431948
# Versions sync related features
19441949
(

readthedocs/proxito/views/serve.py

+131-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
from readthedocs.builds.models import Version
1818
from readthedocs.core.mixins import CDNCacheControlMixin
1919
from readthedocs.core.resolver import resolve_path
20+
from readthedocs.core.unresolver import (
21+
InvalidExternalVersionError,
22+
TranslationNotFoundError,
23+
UnresolvedPathError,
24+
VersionNotFoundError,
25+
unresolver,
26+
)
2027
from readthedocs.core.utils.extend import SettingsOverrideObject
2128
from readthedocs.projects import constants
2229
from readthedocs.projects.models import Domain, Feature
@@ -82,13 +89,17 @@ def get(self,
8289
lang_slug=None,
8390
version_slug=None,
8491
filename='',
85-
): # noqa
92+
):
8693
"""
8794
Take the incoming parsed URL's and figure out what file to serve.
8895
8996
``subproject_slash`` is used to determine if the subproject URL has a slash,
9097
so that we can decide if we need to serve docs or add a /.
9198
"""
99+
unresolved_domain = request.unresolved_domain
100+
if unresolved_domain.project.has_feature(Feature.USE_UNRESOLVER_WITH_PROXITO):
101+
return self.get_using_unresolver(request)
102+
92103
version_slug = self.get_version_from_host(request, version_slug)
93104
final_project, lang_slug, version_slug, filename = _get_project_data_from_request( # noqa
94105
request,
@@ -274,6 +285,125 @@ def _get_canonical_redirect_type(self, request):
274285

275286
return None
276287

288+
def get_using_unresolver(self, request):
289+
unresolved_domain = request.unresolved_domain
290+
# TODO: capture this path in the URL.
291+
path = request.path_info
292+
try:
293+
unresolved = unresolver.unresolve_path(
294+
unresolved_domain=unresolved_domain,
295+
path=path,
296+
append_indexhtml=False,
297+
)
298+
except (VersionNotFoundError, TranslationNotFoundError):
299+
raise Http404
300+
except InvalidExternalVersionError:
301+
raise Http404
302+
except UnresolvedPathError as exc:
303+
# This exception happends if the project didn't have an explict version,
304+
# this is a ``/ -> /en/latest`` or
305+
# ``/projects/subproject/ -> /projects/subproject/en/latest/`` redirect.
306+
project = exc.project
307+
# A system redirect can be cached if we don't have information
308+
# about the version, since the final URL will check for authz.
309+
if self._is_cache_enabled(project):
310+
self.cache_request = True
311+
312+
if unresolved_domain.is_from_external_domain:
313+
version_slug = unresolved_domain.external_version_slug
314+
else:
315+
version_slug = None
316+
317+
return self.system_redirect(
318+
request=request,
319+
final_project=project,
320+
version_slug=version_slug,
321+
filename=exc.path,
322+
is_external_version=unresolved_domain.is_from_external_domain,
323+
)
324+
325+
project = unresolved.project
326+
version = unresolved.version
327+
filename = unresolved.filename
328+
329+
if not version.active:
330+
log.warning("Version is not active.")
331+
raise Http404("Version is not active.")
332+
333+
if self._is_cache_enabled(project) and version and not version.is_private:
334+
# All public versions can be cached.
335+
self.cache_request = True
336+
337+
log.bind(cache_request=self.cache_request)
338+
log.debug("Serving docs.")
339+
340+
# Verify if the project is marked as spam and return a 401 in that case
341+
spam_response = self._spam_response(request, project)
342+
if spam_response:
343+
return spam_response
344+
345+
# Handle requests that need canonicalizing (eg. HTTP -> HTTPS, redirect to canonical domain)
346+
redirect_type = self._get_canonical_redirect_type(request)
347+
if redirect_type:
348+
# A canonical redirect can be cached, if we don't have information
349+
# about the version, since the final URL will check for authz.
350+
if not version and self._is_cache_enabled(project):
351+
self.cache_request = True
352+
try:
353+
return self.canonical_redirect(
354+
request=request,
355+
final_project=project,
356+
version_slug=version.slug,
357+
filename=filename,
358+
redirect_type=redirect_type,
359+
is_external_version=unresolved.external,
360+
)
361+
except InfiniteRedirectException:
362+
# Don't redirect in this case, since it would break things
363+
pass
364+
365+
# Trailing slash redirect.
366+
if filename == "/" and not path.endswith("/"):
367+
return self.system_redirect(
368+
request=request,
369+
final_project=project,
370+
version_slug=version.slug,
371+
filename=filename,
372+
is_external_version=unresolved_domain.is_from_external_domain,
373+
)
374+
375+
redirect_path, http_status = self.get_redirect(
376+
project=project,
377+
lang_slug=project.language,
378+
version_slug=version.slug,
379+
filename=unresolved.filename,
380+
full_path=request.path,
381+
forced_only=True,
382+
)
383+
if redirect_path and http_status:
384+
log.bind(forced_redirect=True)
385+
try:
386+
return self.get_redirect_response(
387+
request=request,
388+
redirect_path=redirect_path,
389+
proxito_path=request.path,
390+
http_status=http_status,
391+
)
392+
except InfiniteRedirectException:
393+
# Continue with our normal serve.
394+
pass
395+
396+
# Check user permissions and return an unauthed response if needed
397+
if not self.allowed_user(request, project, version.slug):
398+
return self.get_unauthed_response(request, project)
399+
400+
return self._serve_docs(
401+
request=request,
402+
project=project,
403+
version=version,
404+
filename=unresolved.filename,
405+
)
406+
277407

278408
class ServeDocs(SettingsOverrideObject):
279409
_default_class = ServeDocsBase

0 commit comments

Comments
 (0)