Merge pull request #23765 from akhilnarang/http-307-redirect

feat: allow setting a custom http status code for redirects
This commit is contained in:
Ankush Menat 2023-12-21 13:53:51 +05:30 committed by GitHub
commit 36451b6951
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 55 additions and 14 deletions

View file

@ -64,7 +64,8 @@ class RequestToken(Exception):
class Redirect(Exception):
http_status_code = 301
def __init__(self, http_status_code: int = 301):
self.http_status_code = http_status_code
class CSRFTokenError(Exception):

View file

@ -170,10 +170,18 @@ class TestWebsite(FrappeTestCase):
dict(
source=r"/courses/course\?course=(.*)", target=r"/courses/\1", match_with_query_string=True
),
dict(
source="/test307",
target="/test",
redirect_http_status=307,
),
]
website_settings = frappe.get_doc("Website Settings")
website_settings.append("route_redirects", {"source": "/testsource", "target": "/testtarget"})
website_settings.append(
"route_redirects",
{"source": "/testsource", "target": "/testtarget", "redirect_http_status": 301},
)
website_settings.save()
set_request(method="GET", path="/testfrom")
@ -205,6 +213,16 @@ class TestWebsite(FrappeTestCase):
self.assertEqual(response.status_code, 301)
self.assertEqual(response.headers.get("Location"), "/courses/data")
set_request(method="GET", path="/test307")
response = get_response()
self.assertEqual(response.status_code, 307)
self.assertEqual(response.headers.get("Location"), "/test")
set_request(method="POST", path="/test307")
response = get_response()
self.assertEqual(response.status_code, 307)
self.assertEqual(response.headers.get("Location"), "/test")
delattr(frappe.hooks, "website_redirects")
frappe.cache.delete_key("app_hooks")

View file

@ -1,10 +1,12 @@
{
"actions": [],
"creation": "2019-05-07 11:08:35.889625",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"source",
"target"
"target",
"redirect_http_status"
],
"fields": [
{
@ -20,10 +22,19 @@
"in_list_view": 1,
"label": "Target",
"reqd": 1
},
{
"default": "301",
"fieldname": "redirect_http_status",
"fieldtype": "Int",
"label": "Redirect HTTP Status",
"options": "301\n302\n307\n308",
"reqd": 1
}
],
"istable": 1,
"modified": "2019-05-07 11:11:46.867684",
"links": [],
"modified": "2023-12-13 12:09:50.726082",
"modified_by": "Administrator",
"module": "Website",
"name": "Website Route Redirect",
@ -31,5 +42,6 @@
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "ASC"
"sort_order": "ASC",
"states": []
}

View file

@ -17,7 +17,9 @@ class WebsiteRouteRedirect(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
redirect_http_status: DF.Int
source: DF.SmallText
target: DF.SmallText
# end: auto-generated types
pass

View file

@ -14,7 +14,7 @@ class RedirectPage:
return build_response(
self.path,
"",
301,
self.http_status_code,
{
"Location": frappe.flags.redirect_location or (frappe.local.response or {}).get("location"),
"Cache-Control": "no-store, no-cache, must-revalidate",

View file

@ -35,8 +35,8 @@ class PathResolver:
try:
resolve_redirect(self.path, request.query_string)
except frappe.Redirect:
return frappe.flags.redirect_location, RedirectPage(self.path)
except frappe.Redirect as e:
return frappe.flags.redirect_location, RedirectPage(self.path, e.http_status_code)
endpoint = resolve_path(self.path)
@ -105,7 +105,9 @@ def resolve_redirect(path, query_string=None):
]
"""
redirects = frappe.get_hooks("website_redirects")
redirects += frappe.get_all("Website Route Redirect", ["source", "target"], order_by=None)
redirects += frappe.get_all(
"Website Route Redirect", ["source", "target", "redirect_http_status"], order_by=None
)
if not redirects:
return
@ -113,6 +115,9 @@ def resolve_redirect(path, query_string=None):
redirect_to = frappe.cache.hget("website_redirects", path)
if redirect_to:
if isinstance(redirect_to, dict):
frappe.flags.redirect_location = redirect_to["path"]
raise frappe.Redirect(redirect_to["status_code"])
frappe.flags.redirect_location = redirect_to
raise frappe.Redirect
@ -124,14 +129,17 @@ def resolve_redirect(path, query_string=None):
try:
match = re.match(pattern, path_to_match)
except re.error as e:
except re.error:
frappe.log_error("Broken Redirect: " + pattern)
if match:
redirect_to = re.sub(pattern, rule["target"], path_to_match)
frappe.flags.redirect_location = redirect_to
frappe.cache.hset("website_redirects", path_to_match, redirect_to)
raise frappe.Redirect
status_code = rule.get("redirect_http_status", 301)
frappe.cache.hset(
"website_redirects", path_to_match, {"path": redirect_to, "status_code": status_code}
)
raise frappe.Redirect(status_code)
def resolve_path(path):

View file

@ -16,8 +16,8 @@ def get_response(path=None, http_status_code=200):
path_resolver = PathResolver(path, http_status_code)
endpoint, renderer_instance = path_resolver.resolve()
response = renderer_instance.render()
except frappe.Redirect:
return RedirectPage(endpoint or path, http_status_code).render()
except frappe.Redirect as e:
return RedirectPage(endpoint or path, e.http_status_code).render()
except frappe.PermissionError as e:
response = NotPermittedPage(endpoint, http_status_code, exception=e).render()
except frappe.PageDoesNotExistError: