We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `starlette/middleware/httpsredirect.py`
Content:
```
1 from starlette.datastructures import URL
2 from starlette.responses import RedirectResponse
3 from starlette.types import ASGIApp, Receive, Scope, Send
4 
5 
6 class HTTPSRedirectMiddleware:
7     def __init__(self, app: ASGIApp) -> None:
8         self.app = app
9 
10     async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
11         if scope["type"] in ("http", "websocket") and scope["scheme"] in ("http", "ws"):
12             url = URL(scope=scope)
13             redirect_scheme = {"http": "https", "ws": "wss"}[url.scheme]
14             netloc = url.hostname if url.port in (80, 443) else url.netloc
15             url = url.replace(scheme=redirect_scheme, netloc=netloc)
16             response = RedirectResponse(url, status_code=301)
17             await response(scope, receive, send)
18         else:
19             await self.app(scope, receive, send)
20
```

Path: `starlette/responses.py`
Content:
```
1 import hashlib
2 import http.cookies
3 import inspect
4 import json
5 import os
6 import stat
7 import typing
8 from email.utils import formatdate
9 from mimetypes import guess_type
10 from urllib.parse import quote_plus
11 
12 from starlette.background import BackgroundTask
13 from starlette.concurrency import iterate_in_threadpool
14 from starlette.datastructures import URL, MutableHeaders
15 from starlette.types import Receive, Scope, Send
16 
17 try:
18     import aiofiles
19     from aiofiles.os import stat as aio_stat
20 except ImportError:  # pragma: nocover
21     aiofiles = None  # type: ignore
22     aio_stat = None  # type: ignore
23 
24 try:
25     import ujson
26 except ImportError:  # pragma: nocover
27     ujson = None  # type: ignore
28 
29 
30 class Response:
31     media_type = None
32     charset = "utf-8"
33 
34     def __init__(
35         self,
36         content: typing.Any = None,
37         status_code: int = 200,
38         headers: dict = None,
39         media_type: str = None,
40         background: BackgroundTask = None,
41     ) -> None:
42         self.body = self.render(content)
43         self.status_code = status_code
44         if media_type is not None:
45             self.media_type = media_type
46         self.background = background
47         self.init_headers(headers)
48 
49     def render(self, content: typing.Any) -> bytes:
50         if content is None:
51             return b""
52         if isinstance(content, bytes):
53             return content
54         return content.encode(self.charset)
55 
56     def init_headers(self, headers: typing.Mapping[str, str] = None) -> None:
57         if headers is None:
58             raw_headers = []  # type: typing.List[typing.Tuple[bytes, bytes]]
59             populate_content_length = True
60             populate_content_type = True
61         else:
62             raw_headers = [
63                 (k.lower().encode("latin-1"), v.encode("latin-1"))
64                 for k, v in headers.items()
65             ]
66             keys = [h[0] for h in raw_headers]
67             populate_content_length = b"content-length" not in keys
68             populate_content_type = b"content-type" not in keys
69 
70         body = getattr(self, "body", b"")
71         if body and populate_content_length:
72             content_length = str(len(body))
73             raw_headers.append((b"content-length", content_length.encode("latin-1")))
74 
75         content_type = self.media_type
76         if content_type is not None and populate_content_type:
77             if content_type.startswith("text/"):
78                 content_type += "; charset=" + self.charset
79             raw_headers.append((b"content-type", content_type.encode("latin-1")))
80 
81         self.raw_headers = raw_headers
82 
83     @property
84     def headers(self) -> MutableHeaders:
85         if not hasattr(self, "_headers"):
86             self._headers = MutableHeaders(raw=self.raw_headers)
87         return self._headers
88 
89     def set_cookie(
90         self,
91         key: str,
92         value: str = "",
93         max_age: int = None,
94         expires: int = None,
95         path: str = "/",
96         domain: str = None,
97         secure: bool = False,
98         httponly: bool = False,
99     ) -> None:
100         cookie = http.cookies.SimpleCookie()
101         cookie[key] = value
102         if max_age is not None:
103             cookie[key]["max-age"] = max_age  # type: ignore
104         if expires is not None:
105             cookie[key]["expires"] = expires  # type: ignore
106         if path is not None:
107             cookie[key]["path"] = path
108         if domain is not None:
109             cookie[key]["domain"] = domain
110         if secure:
111             cookie[key]["secure"] = True  # type: ignore
112         if httponly:
113             cookie[key]["httponly"] = True  # type: ignore
114         cookie_val = cookie.output(header="").strip()
115         self.raw_headers.append((b"set-cookie", cookie_val.encode("latin-1")))
116 
117     def delete_cookie(self, key: str, path: str = "/", domain: str = None) -> None:
118         self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain)
119 
120     async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
121         await send(
122             {
123                 "type": "http.response.start",
124                 "status": self.status_code,
125                 "headers": self.raw_headers,
126             }
127         )
128         await send({"type": "http.response.body", "body": self.body})
129 
130         if self.background is not None:
131             await self.background()
132 
133 
134 class HTMLResponse(Response):
135     media_type = "text/html"
136 
137 
138 class PlainTextResponse(Response):
139     media_type = "text/plain"
140 
141 
142 class JSONResponse(Response):
143     media_type = "application/json"
144 
145     def render(self, content: typing.Any) -> bytes:
146         return json.dumps(
147             content,
148             ensure_ascii=False,
149             allow_nan=False,
150             indent=None,
151             separators=(",", ":"),
152         ).encode("utf-8")
153 
154 
155 class UJSONResponse(JSONResponse):
156     media_type = "application/json"
157 
158     def render(self, content: typing.Any) -> bytes:
159         return ujson.dumps(content, ensure_ascii=False).encode("utf-8")
160 
161 
162 class RedirectResponse(Response):
163     def __init__(
164         self, url: typing.Union[str, URL], status_code: int = 302, headers: dict = None
165     ) -> None:
166         super().__init__(content=b"", status_code=status_code, headers=headers)
167         self.headers["location"] = quote_plus(str(url), safe=":/%#?&=@[]!$&'()*+,;")
168 
169 
170 class StreamingResponse(Response):
171     def __init__(
172         self,
173         content: typing.Any,
174         status_code: int = 200,
175         headers: dict = None,
176         media_type: str = None,
177         background: BackgroundTask = None,
178     ) -> None:
179         if inspect.isasyncgen(content):
180             self.body_iterator = content
181         else:
182             self.body_iterator = iterate_in_threadpool(content)
183         self.status_code = status_code
184         self.media_type = self.media_type if media_type is None else media_type
185         self.background = background
186         self.init_headers(headers)
187 
188     async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
189         await send(
190             {
191                 "type": "http.response.start",
192                 "status": self.status_code,
193                 "headers": self.raw_headers,
194             }
195         )
196         async for chunk in self.body_iterator:
197             if not isinstance(chunk, bytes):
198                 chunk = chunk.encode(self.charset)
199             await send({"type": "http.response.body", "body": chunk, "more_body": True})
200         await send({"type": "http.response.body", "body": b"", "more_body": False})
201 
202         if self.background is not None:
203             await self.background()
204 
205 
206 class FileResponse(Response):
207     chunk_size = 4096
208 
209     def __init__(
210         self,
211         path: str,
212         status_code: int = 200,
213         headers: dict = None,
214         media_type: str = None,
215         background: BackgroundTask = None,
216         filename: str = None,
217         stat_result: os.stat_result = None,
218         method: str = None,
219     ) -> None:
220         assert aiofiles is not None, "'aiofiles' must be installed to use FileResponse"
221         self.path = path
222         self.status_code = status_code
223         self.filename = filename
224         self.send_header_only = method is not None and method.upper() == "HEAD"
225         if media_type is None:
226             media_type = guess_type(filename or path)[0] or "text/plain"
227         self.media_type = media_type
228         self.background = background
229         self.init_headers(headers)
230         if self.filename is not None:
231             content_disposition = 'attachment; filename="{}"'.format(self.filename)
232             self.headers.setdefault("content-disposition", content_disposition)
233         self.stat_result = stat_result
234         if stat_result is not None:
235             self.set_stat_headers(stat_result)
236 
237     def set_stat_headers(self, stat_result: os.stat_result) -> None:
238         content_length = str(stat_result.st_size)
239         last_modified = formatdate(stat_result.st_mtime, usegmt=True)
240         etag_base = str(stat_result.st_mtime) + "-" + str(stat_result.st_size)
241         etag = hashlib.md5(etag_base.encode()).hexdigest()
242 
243         self.headers.setdefault("content-length", content_length)
244         self.headers.setdefault("last-modified", last_modified)
245         self.headers.setdefault("etag", etag)
246 
247     async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
248         if self.stat_result is None:
249             try:
250                 stat_result = await aio_stat(self.path)
251                 self.set_stat_headers(stat_result)
252             except FileNotFoundError:
253                 raise RuntimeError(f"File at path {self.path} does not exist.")
254             else:
255                 mode = stat_result.st_mode
256                 if not stat.S_ISREG(mode):
257                     raise RuntimeError(f"File at path {self.path} is not a file.")
258         await send(
259             {
260                 "type": "http.response.start",
261                 "status": self.status_code,
262                 "headers": self.raw_headers,
263             }
264         )
265         if self.send_header_only:
266             await send({"type": "http.response.body"})
267         else:
268             async with aiofiles.open(self.path, mode="rb") as file:
269                 more_body = True
270                 while more_body:
271                     chunk = await file.read(self.chunk_size)
272                     more_body = len(chunk) == self.chunk_size
273                     await send(
274                         {
275                             "type": "http.response.body",
276                             "body": chunk,
277                             "more_body": more_body,
278                         }
279                     )
280         if self.background is not None:
281             await self.background()
282
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
             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 @@
 
 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=":/%#?&=@[]!$&'()*+,;")

{"golden_diff": "diff --git a/starlette/middleware/httpsredirect.py b/starlette/middleware/httpsredirect.py\n--- a/starlette/middleware/httpsredirect.py\n+++ b/starlette/middleware/httpsredirect.py\n@@ -13,7 +13,7 @@\n             redirect_scheme = {\"http\": \"https\", \"ws\": \"wss\"}[url.scheme]\n             netloc = url.hostname if url.port in (80, 443) else url.netloc\n             url = url.replace(scheme=redirect_scheme, netloc=netloc)\n-            response = RedirectResponse(url, status_code=301)\n+            response = RedirectResponse(url, status_code=308)\n             await response(scope, receive, send)\n         else:\n             await self.app(scope, receive, send)\ndiff --git a/starlette/responses.py b/starlette/responses.py\n--- a/starlette/responses.py\n+++ b/starlette/responses.py\n@@ -161,7 +161,7 @@\n \n class RedirectResponse(Response):\n     def __init__(\n-        self, url: typing.Union[str, URL], status_code: int = 302, headers: dict = None\n+        self, url: typing.Union[str, URL], status_code: int = 307, headers: dict = None\n     ) -> None:\n         super().__init__(content=b\"\", status_code=status_code, headers=headers)\n         self.headers[\"location\"] = quote_plus(str(url), safe=\":/%#?&=@[]!$&'()*+,;\")\n", "issue": "Use \"308 Permanent Redirect\" for redirect slashes behavior.\nHi,\r\nI 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/`.\r\n\r\nI 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.\n", "before_files": [{"content": "from starlette.datastructures import URL\nfrom starlette.responses import RedirectResponse\nfrom starlette.types import ASGIApp, Receive, Scope, Send\n\n\nclass HTTPSRedirectMiddleware:\n    def __init__(self, app: ASGIApp) -> None:\n        self.app = app\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] in (\"http\", \"websocket\") and scope[\"scheme\"] in (\"http\", \"ws\"):\n            url = URL(scope=scope)\n            redirect_scheme = {\"http\": \"https\", \"ws\": \"wss\"}[url.scheme]\n            netloc = url.hostname if url.port in (80, 443) else url.netloc\n            url = url.replace(scheme=redirect_scheme, netloc=netloc)\n            response = RedirectResponse(url, status_code=301)\n            await response(scope, receive, send)\n        else:\n            await self.app(scope, receive, send)\n", "path": "starlette/middleware/httpsredirect.py"}, {"content": "import hashlib\nimport http.cookies\nimport inspect\nimport json\nimport os\nimport stat\nimport typing\nfrom email.utils import formatdate\nfrom mimetypes import guess_type\nfrom urllib.parse import quote_plus\n\nfrom starlette.background import BackgroundTask\nfrom starlette.concurrency import iterate_in_threadpool\nfrom starlette.datastructures import URL, MutableHeaders\nfrom starlette.types import Receive, Scope, Send\n\ntry:\n    import aiofiles\n    from aiofiles.os import stat as aio_stat\nexcept ImportError:  # pragma: nocover\n    aiofiles = None  # type: ignore\n    aio_stat = None  # type: ignore\n\ntry:\n    import ujson\nexcept ImportError:  # pragma: nocover\n    ujson = None  # type: ignore\n\n\nclass Response:\n    media_type = None\n    charset = \"utf-8\"\n\n    def __init__(\n        self,\n        content: typing.Any = None,\n        status_code: int = 200,\n        headers: dict = None,\n        media_type: str = None,\n        background: BackgroundTask = None,\n    ) -> None:\n        self.body = self.render(content)\n        self.status_code = status_code\n        if media_type is not None:\n            self.media_type = media_type\n        self.background = background\n        self.init_headers(headers)\n\n    def render(self, content: typing.Any) -> bytes:\n        if content is None:\n            return b\"\"\n        if isinstance(content, bytes):\n            return content\n        return content.encode(self.charset)\n\n    def init_headers(self, headers: typing.Mapping[str, str] = None) -> None:\n        if headers is None:\n            raw_headers = []  # type: typing.List[typing.Tuple[bytes, bytes]]\n            populate_content_length = True\n            populate_content_type = True\n        else:\n            raw_headers = [\n                (k.lower().encode(\"latin-1\"), v.encode(\"latin-1\"))\n                for k, v in headers.items()\n            ]\n            keys = [h[0] for h in raw_headers]\n            populate_content_length = b\"content-length\" not in keys\n            populate_content_type = b\"content-type\" not in keys\n\n        body = getattr(self, \"body\", b\"\")\n        if body and populate_content_length:\n            content_length = str(len(body))\n            raw_headers.append((b\"content-length\", content_length.encode(\"latin-1\")))\n\n        content_type = self.media_type\n        if content_type is not None and populate_content_type:\n            if content_type.startswith(\"text/\"):\n                content_type += \"; charset=\" + self.charset\n            raw_headers.append((b\"content-type\", content_type.encode(\"latin-1\")))\n\n        self.raw_headers = raw_headers\n\n    @property\n    def headers(self) -> MutableHeaders:\n        if not hasattr(self, \"_headers\"):\n            self._headers = MutableHeaders(raw=self.raw_headers)\n        return self._headers\n\n    def set_cookie(\n        self,\n        key: str,\n        value: str = \"\",\n        max_age: int = None,\n        expires: int = None,\n        path: str = \"/\",\n        domain: str = None,\n        secure: bool = False,\n        httponly: bool = False,\n    ) -> None:\n        cookie = http.cookies.SimpleCookie()\n        cookie[key] = value\n        if max_age is not None:\n            cookie[key][\"max-age\"] = max_age  # type: ignore\n        if expires is not None:\n            cookie[key][\"expires\"] = expires  # type: ignore\n        if path is not None:\n            cookie[key][\"path\"] = path\n        if domain is not None:\n            cookie[key][\"domain\"] = domain\n        if secure:\n            cookie[key][\"secure\"] = True  # type: ignore\n        if httponly:\n            cookie[key][\"httponly\"] = True  # type: ignore\n        cookie_val = cookie.output(header=\"\").strip()\n        self.raw_headers.append((b\"set-cookie\", cookie_val.encode(\"latin-1\")))\n\n    def delete_cookie(self, key: str, path: str = \"/\", domain: str = None) -> None:\n        self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain)\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        await send(\n            {\n                \"type\": \"http.response.start\",\n                \"status\": self.status_code,\n                \"headers\": self.raw_headers,\n            }\n        )\n        await send({\"type\": \"http.response.body\", \"body\": self.body})\n\n        if self.background is not None:\n            await self.background()\n\n\nclass HTMLResponse(Response):\n    media_type = \"text/html\"\n\n\nclass PlainTextResponse(Response):\n    media_type = \"text/plain\"\n\n\nclass JSONResponse(Response):\n    media_type = \"application/json\"\n\n    def render(self, content: typing.Any) -> bytes:\n        return json.dumps(\n            content,\n            ensure_ascii=False,\n            allow_nan=False,\n            indent=None,\n            separators=(\",\", \":\"),\n        ).encode(\"utf-8\")\n\n\nclass UJSONResponse(JSONResponse):\n    media_type = \"application/json\"\n\n    def render(self, content: typing.Any) -> bytes:\n        return ujson.dumps(content, ensure_ascii=False).encode(\"utf-8\")\n\n\nclass RedirectResponse(Response):\n    def __init__(\n        self, url: typing.Union[str, URL], status_code: int = 302, headers: dict = None\n    ) -> None:\n        super().__init__(content=b\"\", status_code=status_code, headers=headers)\n        self.headers[\"location\"] = quote_plus(str(url), safe=\":/%#?&=@[]!$&'()*+,;\")\n\n\nclass StreamingResponse(Response):\n    def __init__(\n        self,\n        content: typing.Any,\n        status_code: int = 200,\n        headers: dict = None,\n        media_type: str = None,\n        background: BackgroundTask = None,\n    ) -> None:\n        if inspect.isasyncgen(content):\n            self.body_iterator = content\n        else:\n            self.body_iterator = iterate_in_threadpool(content)\n        self.status_code = status_code\n        self.media_type = self.media_type if media_type is None else media_type\n        self.background = background\n        self.init_headers(headers)\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        await send(\n            {\n                \"type\": \"http.response.start\",\n                \"status\": self.status_code,\n                \"headers\": self.raw_headers,\n            }\n        )\n        async for chunk in self.body_iterator:\n            if not isinstance(chunk, bytes):\n                chunk = chunk.encode(self.charset)\n            await send({\"type\": \"http.response.body\", \"body\": chunk, \"more_body\": True})\n        await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n        if self.background is not None:\n            await self.background()\n\n\nclass FileResponse(Response):\n    chunk_size = 4096\n\n    def __init__(\n        self,\n        path: str,\n        status_code: int = 200,\n        headers: dict = None,\n        media_type: str = None,\n        background: BackgroundTask = None,\n        filename: str = None,\n        stat_result: os.stat_result = None,\n        method: str = None,\n    ) -> None:\n        assert aiofiles is not None, \"'aiofiles' must be installed to use FileResponse\"\n        self.path = path\n        self.status_code = status_code\n        self.filename = filename\n        self.send_header_only = method is not None and method.upper() == \"HEAD\"\n        if media_type is None:\n            media_type = guess_type(filename or path)[0] or \"text/plain\"\n        self.media_type = media_type\n        self.background = background\n        self.init_headers(headers)\n        if self.filename is not None:\n            content_disposition = 'attachment; filename=\"{}\"'.format(self.filename)\n            self.headers.setdefault(\"content-disposition\", content_disposition)\n        self.stat_result = stat_result\n        if stat_result is not None:\n            self.set_stat_headers(stat_result)\n\n    def set_stat_headers(self, stat_result: os.stat_result) -> None:\n        content_length = str(stat_result.st_size)\n        last_modified = formatdate(stat_result.st_mtime, usegmt=True)\n        etag_base = str(stat_result.st_mtime) + \"-\" + str(stat_result.st_size)\n        etag = hashlib.md5(etag_base.encode()).hexdigest()\n\n        self.headers.setdefault(\"content-length\", content_length)\n        self.headers.setdefault(\"last-modified\", last_modified)\n        self.headers.setdefault(\"etag\", etag)\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if self.stat_result is None:\n            try:\n                stat_result = await aio_stat(self.path)\n                self.set_stat_headers(stat_result)\n            except FileNotFoundError:\n                raise RuntimeError(f\"File at path {self.path} does not exist.\")\n            else:\n                mode = stat_result.st_mode\n                if not stat.S_ISREG(mode):\n                    raise RuntimeError(f\"File at path {self.path} is not a file.\")\n        await send(\n            {\n                \"type\": \"http.response.start\",\n                \"status\": self.status_code,\n                \"headers\": self.raw_headers,\n            }\n        )\n        if self.send_header_only:\n            await send({\"type\": \"http.response.body\"})\n        else:\n            async with aiofiles.open(self.path, mode=\"rb\") as file:\n                more_body = True\n                while more_body:\n                    chunk = await file.read(self.chunk_size)\n                    more_body = len(chunk) == self.chunk_size\n                    await send(\n                        {\n                            \"type\": \"http.response.body\",\n                            \"body\": chunk,\n                            \"more_body\": more_body,\n                        }\n                    )\n        if self.background is not None:\n            await self.background()\n", "path": "starlette/responses.py"}], "after_files": [{"content": "from starlette.datastructures import URL\nfrom starlette.responses import RedirectResponse\nfrom starlette.types import ASGIApp, Receive, Scope, Send\n\n\nclass HTTPSRedirectMiddleware:\n    def __init__(self, app: ASGIApp) -> None:\n        self.app = app\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] in (\"http\", \"websocket\") and scope[\"scheme\"] in (\"http\", \"ws\"):\n            url = URL(scope=scope)\n            redirect_scheme = {\"http\": \"https\", \"ws\": \"wss\"}[url.scheme]\n            netloc = url.hostname if url.port in (80, 443) else url.netloc\n            url = url.replace(scheme=redirect_scheme, netloc=netloc)\n            response = RedirectResponse(url, status_code=308)\n            await response(scope, receive, send)\n        else:\n            await self.app(scope, receive, send)\n", "path": "starlette/middleware/httpsredirect.py"}, {"content": "import hashlib\nimport http.cookies\nimport inspect\nimport json\nimport os\nimport stat\nimport typing\nfrom email.utils import formatdate\nfrom mimetypes import guess_type\nfrom urllib.parse import quote_plus\n\nfrom starlette.background import BackgroundTask\nfrom starlette.concurrency import iterate_in_threadpool\nfrom starlette.datastructures import URL, MutableHeaders\nfrom starlette.types import Receive, Scope, Send\n\ntry:\n    import aiofiles\n    from aiofiles.os import stat as aio_stat\nexcept ImportError:  # pragma: nocover\n    aiofiles = None  # type: ignore\n    aio_stat = None  # type: ignore\n\ntry:\n    import ujson\nexcept ImportError:  # pragma: nocover\n    ujson = None  # type: ignore\n\n\nclass Response:\n    media_type = None\n    charset = \"utf-8\"\n\n    def __init__(\n        self,\n        content: typing.Any = None,\n        status_code: int = 200,\n        headers: dict = None,\n        media_type: str = None,\n        background: BackgroundTask = None,\n    ) -> None:\n        self.body = self.render(content)\n        self.status_code = status_code\n        if media_type is not None:\n            self.media_type = media_type\n        self.background = background\n        self.init_headers(headers)\n\n    def render(self, content: typing.Any) -> bytes:\n        if content is None:\n            return b\"\"\n        if isinstance(content, bytes):\n            return content\n        return content.encode(self.charset)\n\n    def init_headers(self, headers: typing.Mapping[str, str] = None) -> None:\n        if headers is None:\n            raw_headers = []  # type: typing.List[typing.Tuple[bytes, bytes]]\n            populate_content_length = True\n            populate_content_type = True\n        else:\n            raw_headers = [\n                (k.lower().encode(\"latin-1\"), v.encode(\"latin-1\"))\n                for k, v in headers.items()\n            ]\n            keys = [h[0] for h in raw_headers]\n            populate_content_length = b\"content-length\" not in keys\n            populate_content_type = b\"content-type\" not in keys\n\n        body = getattr(self, \"body\", b\"\")\n        if body and populate_content_length:\n            content_length = str(len(body))\n            raw_headers.append((b\"content-length\", content_length.encode(\"latin-1\")))\n\n        content_type = self.media_type\n        if content_type is not None and populate_content_type:\n            if content_type.startswith(\"text/\"):\n                content_type += \"; charset=\" + self.charset\n            raw_headers.append((b\"content-type\", content_type.encode(\"latin-1\")))\n\n        self.raw_headers = raw_headers\n\n    @property\n    def headers(self) -> MutableHeaders:\n        if not hasattr(self, \"_headers\"):\n            self._headers = MutableHeaders(raw=self.raw_headers)\n        return self._headers\n\n    def set_cookie(\n        self,\n        key: str,\n        value: str = \"\",\n        max_age: int = None,\n        expires: int = None,\n        path: str = \"/\",\n        domain: str = None,\n        secure: bool = False,\n        httponly: bool = False,\n    ) -> None:\n        cookie = http.cookies.SimpleCookie()\n        cookie[key] = value\n        if max_age is not None:\n            cookie[key][\"max-age\"] = max_age  # type: ignore\n        if expires is not None:\n            cookie[key][\"expires\"] = expires  # type: ignore\n        if path is not None:\n            cookie[key][\"path\"] = path\n        if domain is not None:\n            cookie[key][\"domain\"] = domain\n        if secure:\n            cookie[key][\"secure\"] = True  # type: ignore\n        if httponly:\n            cookie[key][\"httponly\"] = True  # type: ignore\n        cookie_val = cookie.output(header=\"\").strip()\n        self.raw_headers.append((b\"set-cookie\", cookie_val.encode(\"latin-1\")))\n\n    def delete_cookie(self, key: str, path: str = \"/\", domain: str = None) -> None:\n        self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain)\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        await send(\n            {\n                \"type\": \"http.response.start\",\n                \"status\": self.status_code,\n                \"headers\": self.raw_headers,\n            }\n        )\n        await send({\"type\": \"http.response.body\", \"body\": self.body})\n\n        if self.background is not None:\n            await self.background()\n\n\nclass HTMLResponse(Response):\n    media_type = \"text/html\"\n\n\nclass PlainTextResponse(Response):\n    media_type = \"text/plain\"\n\n\nclass JSONResponse(Response):\n    media_type = \"application/json\"\n\n    def render(self, content: typing.Any) -> bytes:\n        return json.dumps(\n            content,\n            ensure_ascii=False,\n            allow_nan=False,\n            indent=None,\n            separators=(\",\", \":\"),\n        ).encode(\"utf-8\")\n\n\nclass UJSONResponse(JSONResponse):\n    media_type = \"application/json\"\n\n    def render(self, content: typing.Any) -> bytes:\n        return ujson.dumps(content, ensure_ascii=False).encode(\"utf-8\")\n\n\nclass RedirectResponse(Response):\n    def __init__(\n        self, url: typing.Union[str, URL], status_code: int = 307, headers: dict = None\n    ) -> None:\n        super().__init__(content=b\"\", status_code=status_code, headers=headers)\n        self.headers[\"location\"] = quote_plus(str(url), safe=\":/%#?&=@[]!$&'()*+,;\")\n\n\nclass StreamingResponse(Response):\n    def __init__(\n        self,\n        content: typing.Any,\n        status_code: int = 200,\n        headers: dict = None,\n        media_type: str = None,\n        background: BackgroundTask = None,\n    ) -> None:\n        if inspect.isasyncgen(content):\n            self.body_iterator = content\n        else:\n            self.body_iterator = iterate_in_threadpool(content)\n        self.status_code = status_code\n        self.media_type = self.media_type if media_type is None else media_type\n        self.background = background\n        self.init_headers(headers)\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        await send(\n            {\n                \"type\": \"http.response.start\",\n                \"status\": self.status_code,\n                \"headers\": self.raw_headers,\n            }\n        )\n        async for chunk in self.body_iterator:\n            if not isinstance(chunk, bytes):\n                chunk = chunk.encode(self.charset)\n            await send({\"type\": \"http.response.body\", \"body\": chunk, \"more_body\": True})\n        await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n        if self.background is not None:\n            await self.background()\n\n\nclass FileResponse(Response):\n    chunk_size = 4096\n\n    def __init__(\n        self,\n        path: str,\n        status_code: int = 200,\n        headers: dict = None,\n        media_type: str = None,\n        background: BackgroundTask = None,\n        filename: str = None,\n        stat_result: os.stat_result = None,\n        method: str = None,\n    ) -> None:\n        assert aiofiles is not None, \"'aiofiles' must be installed to use FileResponse\"\n        self.path = path\n        self.status_code = status_code\n        self.filename = filename\n        self.send_header_only = method is not None and method.upper() == \"HEAD\"\n        if media_type is None:\n            media_type = guess_type(filename or path)[0] or \"text/plain\"\n        self.media_type = media_type\n        self.background = background\n        self.init_headers(headers)\n        if self.filename is not None:\n            content_disposition = 'attachment; filename=\"{}\"'.format(self.filename)\n            self.headers.setdefault(\"content-disposition\", content_disposition)\n        self.stat_result = stat_result\n        if stat_result is not None:\n            self.set_stat_headers(stat_result)\n\n    def set_stat_headers(self, stat_result: os.stat_result) -> None:\n        content_length = str(stat_result.st_size)\n        last_modified = formatdate(stat_result.st_mtime, usegmt=True)\n        etag_base = str(stat_result.st_mtime) + \"-\" + str(stat_result.st_size)\n        etag = hashlib.md5(etag_base.encode()).hexdigest()\n\n        self.headers.setdefault(\"content-length\", content_length)\n        self.headers.setdefault(\"last-modified\", last_modified)\n        self.headers.setdefault(\"etag\", etag)\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if self.stat_result is None:\n            try:\n                stat_result = await aio_stat(self.path)\n                self.set_stat_headers(stat_result)\n            except FileNotFoundError:\n                raise RuntimeError(f\"File at path {self.path} does not exist.\")\n            else:\n                mode = stat_result.st_mode\n                if not stat.S_ISREG(mode):\n                    raise RuntimeError(f\"File at path {self.path} is not a file.\")\n        await send(\n            {\n                \"type\": \"http.response.start\",\n                \"status\": self.status_code,\n                \"headers\": self.raw_headers,\n            }\n        )\n        if self.send_header_only:\n            await send({\"type\": \"http.response.body\"})\n        else:\n            async with aiofiles.open(self.path, mode=\"rb\") as file:\n                more_body = True\n                while more_body:\n                    chunk = await file.read(self.chunk_size)\n                    more_body = len(chunk) == self.chunk_size\n                    await send(\n                        {\n                            \"type\": \"http.response.body\",\n                            \"body\": chunk,\n                            \"more_body\": more_body,\n                        }\n                    )\n        if self.background is not None:\n            await self.background()\n", "path": "starlette/responses.py"}]}