Repository: encode/starlette

Issue:
Use "308 Permanent Redirect" for redirect slashes behavior.
Hi,
I stumbled upon a quirk in starlette that is not properly documented. It seems like all of my HTTP request to a route without trailing slash are being redirected to route with trailing slashes. Say I am hitting `http://hostname/mountpoint/api` it will be redirected (302) to `http://hostname/mountpoint/api/`. This messed up your api calls; if you call POST to `http://hostname/mountpoint/api` it will be redirected to GET `http://hostname/mountpoint/api/`.

I dig on the source and see this redirect_slashes flag and setting it to False fix this. I feel this behavior (the auto redirection) should be documented.


Discussion:
Alternatively it can raise an exception like Django does.
I'm new to fastapi, and I think I'm running into this issue as well. If I forget a necessary slash, I'm redirected, which has no side effects for `GET`, but `POST` loses its data and is changed to a `GET`.

I'm new to this tool, but I've used Django for a decade, as well as django-rest-framework. Django's `APPEND_SLASH=True` breaks non-`GET` requests. django-rest-framework provides routers for their viewsets, but you have to override the `__init__` method to make trailing slashes optional. Even then, `@action` subresources must have trailing slashes. In my opinion, that functionality has always seemed broken.

Instead of modifying the docs to clarify this, what if the routes were forgiving of slash confusion? Instead of 404ing or even redirecting, `compile_path` could just ensure there's a `/?` instead of `/` or no slash at all. I suggest this because I've made incorrectly-slashed requests hundreds of times over the years, and doing this with POST requests can result in confusion or even lost data. In contrast, it seems unlikely that someone would miss the ability to specify routes _only_ with trailing slashes, or _only when there is no trailing slash.

Let me know what you think or if I can help.
Right, then we're using the wrong redirect status code. 308 preserves the method.
It sounds like a good idea to be able to configure Starlette to be trailing-slash-agnostic in one way or the other. Particularly after I just [wasted](https://github.com/tiangolo/fastapi/issues/244#issuecomment-494840792) several hours on trailing-slash issue :)
I think changing to 308 is the best solution. Will put up a PR if I got the time.
@tibudiyanto Absolutely agreed. No particular need for us to allow it to be configurable either - it's clearly the wrong code as it stands.

Resolving patch:
diff --git a/starlette/middleware/httpsredirect.py b/starlette/middleware/httpsredirect.py
--- a/starlette/middleware/httpsredirect.py
+++ b/starlette/middleware/httpsredirect.py
@@ -13,7 +13,7 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
             redirect_scheme = {"http": "https", "ws": "wss"}[url.scheme]
             netloc = url.hostname if url.port in (80, 443) else url.netloc
             url = url.replace(scheme=redirect_scheme, netloc=netloc)
-            response = RedirectResponse(url, status_code=301)
+            response = RedirectResponse(url, status_code=308)
             await response(scope, receive, send)
         else:
             await self.app(scope, receive, send)
diff --git a/starlette/responses.py b/starlette/responses.py
--- a/starlette/responses.py
+++ b/starlette/responses.py
@@ -161,7 +161,7 @@ def render(self, content: typing.Any) -> bytes:
 
 class RedirectResponse(Response):
     def __init__(
-        self, url: typing.Union[str, URL], status_code: int = 302, headers: dict = None
+        self, url: typing.Union[str, URL], status_code: int = 307, headers: dict = None
     ) -> None:
         super().__init__(content=b"", status_code=status_code, headers=headers)
         self.headers["location"] = quote_plus(str(url), safe=":/%#?&=@[]!$&'()*+,;")


Test patch:
diff --git a/tests/middleware/test_https_redirect.py b/tests/middleware/test_https_redirect.py
--- a/tests/middleware/test_https_redirect.py
+++ b/tests/middleware/test_https_redirect.py
@@ -19,20 +19,20 @@ def homepage(request):
 
     client = TestClient(app)
     response = client.get("/", allow_redirects=False)
-    assert response.status_code == 301
+    assert response.status_code == 308
     assert response.headers["location"] == "https://testserver/"
 
     client = TestClient(app, base_url="http://testserver:80")
     response = client.get("/", allow_redirects=False)
-    assert response.status_code == 301
+    assert response.status_code == 308
     assert response.headers["location"] == "https://testserver/"
 
     client = TestClient(app, base_url="http://testserver:443")
     response = client.get("/", allow_redirects=False)
-    assert response.status_code == 301
+    assert response.status_code == 308
     assert response.headers["location"] == "https://testserver/"
 
     client = TestClient(app, base_url="http://testserver:123")
     response = client.get("/", allow_redirects=False)
-    assert response.status_code == 301
+    assert response.status_code == 308
     assert response.headers["location"] == "https://testserver:123/"