Skip to content

Commit ed5825b

Browse files
[FEATURE] Avoid redirections when requesting without trailing slash (#4963)
<!-- Thanks for your contribution! As part of our Community Growers initiative 🌱, we're donating Justdiggit bunds in your name to reforest sub-Saharan Africa. To claim your Community Growers certificate, please contact David Berenstein in our Slack community or fill in this form https://tally.so/r/n9XrxK once your PR has been merged. --> # Description When requesting `http://localhost:6900/datasets`, the server will redirect to `http://localhost:6900/datasets/`. We can skip this redirection and return the static content in the same request. **Type of change** (Please delete options that are not relevant. Remember to title the PR according to the type of change) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) **How Has This Been Tested** (Please describe the tests that you ran to verify your changes. And ideally, reference `tests`) - [ ] Test A - [ ] Test B **Checklist** - [ ] I followed the style guidelines of this project - [ ] I did a self-review of my code - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I filled out [the contributor form](https://tally.so/r/n9XrxK) (see text above) - [ ] I have added relevant notes to the `CHANGELOG.md` file (See https://keepachangelog.com/) --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 9c2d409 commit ed5825b

File tree

3 files changed

+46
-4
lines changed

3 files changed

+46
-4
lines changed

argilla-server/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,5 @@ cython_debug/
165165
# Misc
166166
.DS_Store
167167

168-
169168
# Generated static files
170169
src/argilla_server/static/

argilla-server/src/argilla_server/_app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def create_server_app() -> FastAPI:
5151
description="Argilla API",
5252
docs_url=None,
5353
redoc_url=None,
54+
redirect_slashes=False,
5455
version=str(argilla_version),
5556
)
5657

@@ -77,7 +78,7 @@ async def redirect_api():
7778
# This if-else clause is needed to simplify the test dependencies setup. Otherwise we cannot override dependencies
7879
# easily. We can review this once we have separate fastapi application for the api and the webapp.
7980
if settings.base_url and settings.base_url != "/":
80-
_app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
81+
_app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None, redirect_slashes=False)
8182
_app.mount(settings.base_url, app)
8283
return _app
8384
else:

argilla-server/src/argilla_server/static_rewrite.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
15+
import os
16+
import stat
1517
from typing import Union
1618

19+
import anyio
1720
from fastapi.staticfiles import StaticFiles
1821
from starlette.exceptions import HTTPException
19-
from starlette.responses import Response
22+
from starlette.responses import FileResponse, RedirectResponse, Response
2023
from starlette.types import Scope
2124

2225

@@ -25,7 +28,9 @@ class RewriteStaticFiles(StaticFiles):
2528

2629
async def get_response(self, path: str, scope: Scope) -> Response:
2730
try:
28-
response = await super().get_response(path, scope)
31+
response = await self._get_response_internal(path, scope)
32+
if response.status_code == 307:
33+
return RedirectResponse(url=f"{scope['path']}/")
2934
return await self._handle_response(response, scope)
3035
except HTTPException as ex:
3136
return await self._handle_response(ex, scope)
@@ -40,3 +45,40 @@ async def _handle_response(
4045
if isinstance(response_or_error, HTTPException):
4146
raise response_or_error
4247
return response_or_error
48+
49+
async def _get_response_internal(self, path: str, scope: Scope) -> Response:
50+
"""
51+
Returns an HTTP response, given the incoming path, method and request headers.
52+
53+
This method is the same as the one in the parent class, but it handles folder path
54+
without trailing slash differently. Instead of returning a 307 response, it returns
55+
a 200 response with the content of the folder index file.
56+
"""
57+
if scope["method"] not in ("GET", "HEAD"):
58+
raise HTTPException(status_code=405)
59+
60+
try:
61+
full_path, stat_result = await anyio.to_thread.run_sync(self.lookup_path, path)
62+
except PermissionError:
63+
raise HTTPException(status_code=401)
64+
except OSError:
65+
raise
66+
67+
if stat_result and stat.S_ISREG(stat_result.st_mode):
68+
# We have a static file to serve.
69+
return self.file_response(full_path, stat_result, scope)
70+
71+
elif stat_result and stat.S_ISDIR(stat_result.st_mode) and self.html:
72+
# We're in HTML mode, and have got a directory URL.
73+
# Check if we have 'index.html' file to serve.
74+
index_path = os.path.join(path, "index.html")
75+
full_path, stat_result = await anyio.to_thread.run_sync(self.lookup_path, index_path)
76+
if stat_result is not None and stat.S_ISREG(stat_result.st_mode):
77+
return self.file_response(full_path, stat_result, scope)
78+
79+
if self.html:
80+
# Check for '404.html' if we're in HTML mode.
81+
full_path, stat_result = await anyio.to_thread.run_sync(self.lookup_path, "404.html")
82+
if stat_result and stat.S_ISREG(stat_result.st_mode):
83+
return FileResponse(full_path, stat_result=stat_result, status_code=404)
84+
raise HTTPException(status_code=404)

0 commit comments

Comments
 (0)