mirror of
https://github.com/fastapi/fastapi.git
synced 2026-09-13 05:50:15 -04:00
🐛 Fix frontend fallback support for doted paths like /users/john.doe (#16011)
This commit is contained in:
1 parent
9b8410bdc9
commit
eb75fd078e
3 files changed
+19
-23
No files matched your search
@@ -52,7 +52,7 @@ For that, use `fallback="index.html"`:
|
||||
|
||||
{* ../../docs_src/frontend/tutorial002_py310.py hl[5] *}
|
||||
|
||||
**FastAPI** uses this fallback only for `GET` and `HEAD` requests that look like browser navigation. Missing files like JavaScript, CSS, and images still return `404`.
|
||||
**FastAPI** uses this fallback only for `GET` and `HEAD` requests that explicitly accept HTML with `Accept: text/html` or `Accept: application/xhtml+xml`, as browser navigation requests normally do. Missing files like JavaScript, CSS, and images still return `404`.
|
||||
|
||||
Requests with other methods, like `POST` or `PUT`, to paths that only match the frontend fallback also return `404`. Regular **FastAPI** *path operations* still have higher priority than frontend routes.
|
||||
|
||||
|
||||
+3
-14
@@ -1984,24 +1984,13 @@ def _iter_accept_media_types(accept: str) -> Iterator[tuple[str, float]]:
|
||||
|
||||
|
||||
def _is_frontend_navigation_request(scope: Scope) -> bool:
|
||||
route_path = get_route_path(scope)
|
||||
final_segment = route_path.rsplit("/", 1)[-1]
|
||||
if os.path.splitext(final_segment)[1]:
|
||||
return False
|
||||
request = Request(scope)
|
||||
wildcard_accepted = False
|
||||
html_rejected = False
|
||||
for media_type, quality in _iter_accept_media_types(
|
||||
request.headers.get("accept", "")
|
||||
):
|
||||
if media_type in {"text/html", "application/xhtml+xml"}:
|
||||
if quality == 0:
|
||||
html_rejected = True
|
||||
else:
|
||||
return True
|
||||
elif media_type == "*/*" and quality != 0:
|
||||
wildcard_accepted = True
|
||||
return wildcard_accepted and not html_rejected
|
||||
if media_type in {"text/html", "application/xhtml+xml"} and quality != 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class _FrontendRoute(BaseRoute):
|
||||
|
||||
+15
-8
@@ -32,7 +32,7 @@ def test_frontend_exact_prefix_path_serves_index(tmp_path: Path):
|
||||
app = FastAPI()
|
||||
app.frontend("/", directory=dist)
|
||||
|
||||
response = TestClient(app).get("/app")
|
||||
response = TestClient(app).get("/app", headers={"accept": "text/html"})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.text == "app"
|
||||
@@ -617,15 +617,21 @@ def test_api_route_404_is_not_replaced_by_frontend_fallback(tmp_path: Path):
|
||||
assert response.json() == {"detail": "api missing"}
|
||||
|
||||
|
||||
def test_index_fallback_for_navigation_request(tmp_path: Path):
|
||||
@pytest.mark.parametrize(
|
||||
("path", "accept"),
|
||||
[
|
||||
("/dashboard/settings", "text/html"),
|
||||
("/users/jane.doe", "text/html"),
|
||||
("/v/1.2.3", "application/xhtml+xml"),
|
||||
],
|
||||
)
|
||||
def test_index_fallback_for_navigation_request(tmp_path: Path, path: str, accept: str):
|
||||
dist = tmp_path / "dist"
|
||||
write_file(dist / "index.html", "app shell")
|
||||
app = FastAPI()
|
||||
app.frontend("/", directory=dist, fallback="index.html")
|
||||
|
||||
response = TestClient(app).get(
|
||||
"/dashboard/settings", headers={"accept": "text/html"}
|
||||
)
|
||||
response = TestClient(app).get(path, headers={"accept": accept})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.text == "app shell"
|
||||
@@ -638,7 +644,8 @@ def test_index_fallback_parses_accept_parameters(tmp_path: Path):
|
||||
app.frontend("/", directory=dist, fallback="index.html")
|
||||
|
||||
response = TestClient(app).get(
|
||||
"/dashboard/settings", headers={"accept": "text/html; q=0.8"}
|
||||
"/dashboard/settings",
|
||||
headers={"accept": "text/html; charset=utf-8; q=0.8"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -697,10 +704,10 @@ def test_index_fallback_respects_explicit_xhtml_rejection_with_wildcard(
|
||||
("/assets/missing.css", "text/css"),
|
||||
("/assets/missing.png", "image/png"),
|
||||
("/api/missing", "application/json"),
|
||||
("/users/jane.doe", "text/html"),
|
||||
("/dashboard/settings", ""),
|
||||
],
|
||||
)
|
||||
def test_index_fallback_does_not_handle_asset_like_or_non_html_requests(
|
||||
def test_index_fallback_requires_explicit_html_acceptance(
|
||||
tmp_path: Path, path: str, accept: str
|
||||
):
|
||||
dist = tmp_path / "dist"
|
||||
|
||||
Reference in new issue
Block a user