File: response.py

package info (click to toggle)
python-falcon 4.0.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,172 kB
  • sloc: python: 33,608; javascript: 92; sh: 50; makefile: 50
file content (1453 lines) | stat: -rw-r--r-- 55,995 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
# Copyright 2013 by Rackspace Hosting, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Response class."""

from __future__ import annotations

from datetime import datetime
from datetime import timezone
import functools
import mimetypes
from typing import (
    Any,
    ClassVar,
    Dict,
    Iterable,
    List,
    Mapping,
    NoReturn,
    Optional,
    overload,
    Tuple,
    Type,
    TYPE_CHECKING,
    Union,
)

from falcon._typing import _UNSET
from falcon._typing import RangeSetHeader
from falcon._typing import UnsetOr
from falcon.constants import _DEFAULT_STATIC_MEDIA_TYPES
from falcon.constants import DEFAULT_MEDIA_TYPE
from falcon.errors import HeaderNotSupported
from falcon.media import Handlers
from falcon.response_helpers import _format_content_disposition
from falcon.response_helpers import _format_etag_header
from falcon.response_helpers import _format_header_value_list
from falcon.response_helpers import _format_range
from falcon.response_helpers import _header_property
from falcon.response_helpers import _is_ascii_encodable
from falcon.typing import Headers
from falcon.typing import ReadableIO
from falcon.util import dt_to_http
from falcon.util import http_cookies
from falcon.util import http_status_to_code
from falcon.util import structures
from falcon.util.deprecation import AttributeRemovedError
from falcon.util.uri import encode_check_escaped as uri_encode
from falcon.util.uri import encode_value_check_escaped as uri_encode_value

if TYPE_CHECKING:
    import http


_RESERVED_CROSSORIGIN_VALUES = frozenset({'anonymous', 'use-credentials'})
_RESERVED_SAMESITE_VALUES = frozenset({'lax', 'strict', 'none'})


class Response:
    """Represents an HTTP response to a client request.

    Note:
        ``Response`` is not meant to be instantiated directly by responders.

    Keyword Arguments:
        options (ResponseOptions): Set of global options passed from the App handler.
    """

    __slots__ = (
        'text',
        'context',
        'options',
        'status',
        'stream',
        '_cookies',
        '_data',
        '_extra_headers',
        '_headers',
        '_media',
        '_media_rendered',
        '__dict__',
    )

    _cookies: Optional[http_cookies.SimpleCookie]
    _data: Optional[bytes]
    _extra_headers: Optional[List[Tuple[str, str]]]
    _headers: Headers
    _media: Optional[Any]
    _media_rendered: UnsetOr[bytes]

    # Child classes may override this
    context_type: ClassVar[Type[structures.Context]] = structures.Context
    """Class variable that determines the factory or
    type to use for initializing the `context` attribute. By default,
    the framework will instantiate bare objects (instances of the bare
    :class:`falcon.Context` class). However, you may override this
    behavior by creating a custom child class of
    :class:`falcon.Response`, and then passing that new class to
    ``falcon.App()`` by way of the latter's `response_type` parameter.

    Note:
        When overriding `context_type` with a factory function (as
        opposed to a class), the function is called like a method of
        the current Response instance. Therefore the first argument is
        the Response instance itself (self).
    """

    # Attribute declaration
    complete: bool = False
    """Set to ``True`` from within a middleware method to signal to the framework that
    request processing should be short-circuited (see also
    :ref:`Middleware <middleware>`).
    """
    status: Union[str, int, http.HTTPStatus]
    """HTTP status code or line (e.g., ``'200 OK'``).

    This may be set to a member of :class:`http.HTTPStatus`, an HTTP status line
    string (e.g., ``'200 OK'``), or an ``int``.

    Note:
        The Falcon framework itself provides a number of constants for
        common status codes. They all start with the ``HTTP_`` prefix,
        as in: ``falcon.HTTP_204``. (See also: :ref:`status`.)
    """
    text: Optional[str]
    """String representing response content.

    Note:
        Falcon will encode the given text as UTF-8 in the response. If the content
        is already a byte string, use the :attr:`data` attribute instead (it's faster).
    """
    stream: Union[ReadableIO, Iterable[bytes], None]
    """Either a file-like object with a `read()` method that takes an optional size
    argument and returns a block of bytes, or an iterable object, representing response
    content, and yielding blocks as byte strings. Falcon will use *wsgi.file_wrapper*,
    if provided by the WSGI server, in order to efficiently serve file-like objects.

    Note:
        If the stream is set to an iterable object that requires
        resource cleanup, it can implement a close() method to do so.
        The close() method will be called upon completion of the request.
    """
    context: structures.Context
    """Empty object to hold any data (in its attributes) about the response which is
    specific to your app (e.g. session object).
    Falcon itself will not interact with this attribute after it has been initialized.

    Note:
        The preferred way to pass response-specific data, when using the
        default context type, is to set attributes directly on the
        `context` object. For example::

            resp.context.cache_strategy = 'lru'
    """
    options: ResponseOptions
    """Set of global options passed in from the App handler."""

    def __init__(self, options: Optional[ResponseOptions] = None) -> None:
        self.status = '200 OK'
        self._headers = {}

        # NOTE(kgriffs): Collection of additional headers as a list of raw
        #   tuples, to use in cases where we need more control over setting
        #   headers and duplicates are allowable or even necessary.
        #
        # PERF(kgriffs): Save some CPU cycles and a few bytes of RAM by
        #   only instantiating the list object later on IFF it is needed.
        self._extra_headers = None

        self.options = options if options is not None else ResponseOptions()

        # NOTE(tbug): will be set to a SimpleCookie object
        # when cookie is set via set_cookie
        self._cookies = None

        self.text = None
        self.stream = None
        self._data = None
        self._media = None
        self._media_rendered = _UNSET

        self.context = self.context_type()

    @property
    def status_code(self) -> int:
        """HTTP status code normalized from :attr:`status`.

        When a code is assigned to this property, :attr:`status` is updated,
        and vice-versa. The status code can be useful when needing to check
        in middleware for codes that fall into a certain class, e.g.::

            if resp.status_code >= 400:
                log.warning(f'returning error response: {resp.status_code}')
        """
        return http_status_to_code(self.status)

    @status_code.setter
    def status_code(self, value: int) -> None:
        self.status = value

    @property
    def body(self) -> NoReturn:
        raise AttributeRemovedError(
            'The body attribute is no longer supported. '
            'Please use the text attribute instead.'
        )

    @body.setter
    def body(self, value: str) -> NoReturn:
        raise AttributeRemovedError(
            'The body attribute is no longer supported. '
            'Please use the text attribute instead.'
        )

    @property
    def data(self) -> Optional[bytes]:
        """Byte string representing response content.

        Use this attribute in lieu of `text` when your content is
        already a byte string (of type ``bytes``). See also the note below.

        Warning:
            Always use the `text` attribute for text, or encode it
            first to ``bytes`` when using the `data` attribute, to
            ensure Unicode characters are properly encoded in the
            HTTP response.
        """
        return self._data

    @data.setter
    def data(self, value: Optional[bytes]) -> None:
        self._data = value

    @property
    def headers(self) -> Headers:
        """Copy of all headers set for the response, without cookies.

        Note that a new copy is created and returned each time this property is
        referenced.
        """
        return self._headers.copy()

    @property
    def media(self) -> Any:
        """A serializable object supported by the media handlers configured via
        :class:`falcon.RequestOptions`.

        Note:
            See also :ref:`media` for more information regarding media
            handling.
        """  # noqa D205
        return self._media

    @media.setter
    def media(self, value: Any) -> None:
        self._media = value
        self._media_rendered = _UNSET

    def render_body(self) -> Optional[bytes]:
        """Get the raw bytestring content for the response body.

        This method returns the raw data for the HTTP response body, taking
        into account the :attr:`~.text`, :attr:`~.data`, and :attr:`~.media`
        attributes.

        Note:
            This method ignores :attr:`~.stream`; the caller must check
            and handle that attribute directly.

        Returns:
            bytes: The UTF-8 encoded value of the `text` attribute, if
            set. Otherwise, the value of the `data` attribute if set, or
            finally the serialized value of the `media` attribute. If
            none of these attributes are set, ``None`` is returned.
        """
        data: Optional[bytes]
        text = self.text
        if text is None:
            data = self._data

            if data is None and self._media is not None:
                # NOTE(kgriffs): We use a special _UNSET singleton since
                #   None is ambiguous (the media handler might return None).
                if self._media_rendered is _UNSET:
                    if not self.content_type:
                        self.content_type = self.options.default_media_type

                    handler, _, _ = self.options.media_handlers._resolve(
                        self.content_type, self.options.default_media_type
                    )

                    self._media_rendered = handler.serialize(
                        self._media, self.content_type
                    )

                data = self._media_rendered
        else:
            try:
                # NOTE(kgriffs): Normally we expect text to be a string
                data = text.encode()
            except AttributeError:
                # NOTE(kgriffs): Assume it was a bytes object already
                data = text  # type: ignore[assignment]

        return data

    def __repr__(self) -> str:
        return f'<{self.__class__.__name__}: {self.status}>'

    def set_stream(
        self, stream: Union[ReadableIO, Iterable[bytes]], content_length: int
    ) -> None:
        """Set both `stream` and `content_length`.

        Although the :attr:`~falcon.Response.stream` and
        :attr:`~falcon.Response.content_length` properties may be set
        directly, using this method ensures
        :attr:`~falcon.Response.content_length` is not accidentally
        neglected when the length of the stream is known in advance. Using this
        method is also slightly more performant as compared to setting the
        properties individually.

        Note:
            If the stream length is unknown, you can set
            :attr:`~falcon.Response.stream` directly, and ignore
            :attr:`~falcon.Response.content_length`. In this case, the ASGI
            server may choose to use chunked encoding or one
            of the other strategies suggested by PEP-3333.

        Args:
            stream: A readable file-like object.
            content_length (int): Length of the stream, used for the
                Content-Length header in the response.
        """

        self.stream = stream

        # PERF(kgriffs): Set directly rather than incur the overhead of
        #   the self.content_length property.
        self._headers['content-length'] = str(content_length)

    def set_cookie(  # noqa: C901
        self,
        name: str,
        value: str,
        expires: Optional[datetime] = None,
        max_age: Optional[int] = None,
        domain: Optional[str] = None,
        path: Optional[str] = None,
        secure: Optional[bool] = None,
        http_only: bool = True,
        same_site: Optional[str] = None,
        partitioned: bool = False,
    ) -> None:
        """Set a response cookie.

        Note:
            This method can be called multiple times to add one or
            more cookies to the response.

        See Also:
            To learn more about setting cookies, see
            :ref:`Setting Cookies <setting-cookies>`. The parameters
            listed below correspond to those defined in `RFC 6265`_.

        Args:
            name (str): Cookie name
            value (str): Cookie value

        Keyword Args:
            expires (datetime): Specifies when the cookie should expire.
                By default, cookies expire when the user agent exits.

                (See also: RFC 6265, Section 4.1.2.1)
            max_age (int): Defines the lifetime of the cookie in
                seconds. By default, cookies expire when the user agent
                exits. If both `max_age` and `expires` are set, the
                latter is ignored by the user agent.

                Note:
                    Coercion to ``int`` is attempted if provided with
                    ``float`` or ``str``.

                (See also: RFC 6265, Section 4.1.2.2)

            domain (str): Restricts the cookie to a specific domain and
                any subdomains of that domain. By default, the user
                agent will return the cookie only to the origin server.
                When overriding this default behavior, the specified
                domain must include the origin server. Otherwise, the
                user agent will reject the cookie.

                Note:
                    Cookies do not provide isolation by port, so the domain
                    should not provide one. (See also: RFC 6265, Section 8.5)

                (See also: RFC 6265, Section 4.1.2.3)

            path (str): Scopes the cookie to the given path plus any
                subdirectories under that path (the "/" character is
                interpreted as a directory separator). If the cookie
                does not specify a path, the user agent defaults to the
                path component of the requested URI.

                Warning:
                    User agent interfaces do not always isolate
                    cookies by path, and so this should not be
                    considered an effective security measure.

                (See also: RFC 6265, Section 4.1.2.4)

            secure (bool): Direct the client to only return the cookie
                in subsequent requests if they are made over HTTPS
                (default: ``True``). This prevents attackers from
                reading sensitive cookie data.

                Note:
                    The default value for this argument is normally
                    ``True``, but can be modified by setting
                    :attr:`~.ResponseOptions.secure_cookies_by_default`
                    via :any:`App.resp_options`.

                Warning:
                    For the `secure` cookie attribute to be effective,
                    your application will need to enforce HTTPS.

                (See also: RFC 6265, Section 4.1.2.5)

            http_only (bool): The HttpOnly attribute limits the scope of the
                cookie to HTTP requests.  In particular, the attribute
                instructs the user agent to omit the cookie when providing
                access to cookies via "non-HTTP" APIs. This is intended to
                mitigate some forms of cross-site scripting. (default: ``True``)

                Note:
                    HttpOnly cookies are not visible to javascript scripts
                    in the browser. They are automatically sent to the server
                    on javascript ``XMLHttpRequest`` or ``Fetch`` requests.

                (See also: RFC 6265, Section 4.1.2.6)

            same_site (str): Helps protect against CSRF attacks by restricting
                when a cookie will be attached to the request by the user agent.
                When set to ``'Strict'``, the cookie will only be sent along
                with "same-site" requests.  If the value is ``'Lax'``, the
                cookie will be sent with same-site requests, and with
                "cross-site" top-level navigations.  If the value is ``'None'``,
                the cookie will be sent with same-site and cross-site requests.
                Finally, when this attribute is not set on the cookie, the
                attribute will be treated as if it had been set to ``'None'``.

                (See also: `Same-Site RFC Draft`_)

            partitioned (bool): Prevents cookies from being accessed from other
                subdomains. With partitioned enabled, a cookie set by
                https://3rd-party.example which is embedded inside
                https://site-a.example can no longer be accessed by
                https://site-b.example. While this attribute is not yet
                standardized, it is already used by Chrome.

                (See also: `CHIPS`_)

                .. versionadded:: 4.0

        Raises:
            KeyError: `name` is not a valid cookie name.
            ValueError: `value` is not a valid cookie value.

        .. _RFC 6265:
            http://tools.ietf.org/html/rfc6265

        .. _Same-Site RFC Draft:
            https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7

        .. _CHIPS:
            https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies

        """

        if not _is_ascii_encodable(name):
            raise KeyError('name is not ascii encodable')
        if not _is_ascii_encodable(value):
            raise ValueError('value is not ascii encodable')

        value = str(value)

        if self._cookies is None:
            self._cookies = http_cookies.SimpleCookie()

        try:
            self._cookies[name] = value
        except http_cookies.CookieError as e:  # pragma: no cover
            # NOTE(tbug): we raise a KeyError here, to avoid leaking
            # the CookieError to the user. SimpleCookie (well, BaseCookie)
            # only throws CookieError on issues with the cookie key
            raise KeyError(str(e))

        if expires:
            # set Expires on cookie. Format is Wdy, DD Mon YYYY HH:MM:SS GMT

            # NOTE(tbug): we never actually need to
            # know that GMT is named GMT when formatting cookies.
            # It is a function call less to just write "GMT" in the fmt string:
            fmt = '%a, %d %b %Y %H:%M:%S GMT'
            if expires.tzinfo is None:
                # naive
                self._cookies[name]['expires'] = expires.strftime(fmt)
            else:
                # aware
                gmt_expires = expires.astimezone(timezone.utc)
                self._cookies[name]['expires'] = gmt_expires.strftime(fmt)

        if max_age:
            # RFC 6265 section 5.2.2 says about the max-age value:
            #   "If the remainder of attribute-value contains a non-DIGIT
            #    character, ignore the cookie-av."
            # That is, RFC-compliant response parsers will ignore the max-age
            # attribute if the value contains a dot, as in floating point
            # numbers. Therefore, attempt to convert the value to an integer.
            self._cookies[name]['max-age'] = int(max_age)

        if domain:
            self._cookies[name]['domain'] = domain

        if path:
            self._cookies[name]['path'] = path

        is_secure = self.options.secure_cookies_by_default if secure is None else secure

        if is_secure:
            self._cookies[name]['secure'] = True

        if http_only:
            self._cookies[name]['httponly'] = http_only

        # PERF(kgriffs): Morsel.__setitem__() will lowercase this anyway,
        #   so we can just pass this in and when __setitem__() calls
        #   lower() it will be very slightly faster.
        if same_site:
            same_site = same_site.lower()

            if same_site not in _RESERVED_SAMESITE_VALUES:
                raise ValueError(
                    "same_site must be set to either 'lax', 'strict', or 'none'"
                )

            self._cookies[name]['samesite'] = same_site.capitalize()

        if partitioned:
            self._cookies[name]['partitioned'] = True

    def unset_cookie(
        self,
        name: str,
        samesite: str = 'Lax',
        domain: Optional[str] = None,
        path: Optional[str] = None,
    ) -> None:
        """Unset a cookie in the response.

        Clears the contents of the cookie, and instructs the user
        agent to immediately expire its own copy of the cookie.

        Note:
            Modern browsers place restriction on cookies without the
            "same-site" cookie attribute set. To that end this attribute
            is set to ``'Lax'`` by this method.

            (See also: `Same-Site warnings`_)

        Warning:
            In order to successfully remove a cookie, both the
            path and the domain must match the values that were
            used when the cookie was created.

        Args:
            name (str): Cookie name

        Keyword Args:
            samesite (str): Allows to override the default 'Lax' same_site
                    setting for the unset cookie.

                    .. versionadded:: 4.0

            domain (str): Restricts the cookie to a specific domain and
                    any subdomains of that domain. By default, the user
                    agent will return the cookie only to the origin server.
                    When overriding this default behavior, the specified
                    domain must include the origin server. Otherwise, the
                    user agent will reject the cookie.

                    Note:
                        Cookies do not provide isolation by port, so the domain
                        should not provide one. (See also: RFC 6265, Section 8.5)

                    (See also: RFC 6265, Section 4.1.2.3)

            path (str): Scopes the cookie to the given path plus any
                subdirectories under that path (the "/" character is
                interpreted as a directory separator). If the cookie
                does not specify a path, the user agent defaults to the
                path component of the requested URI.

                Warning:
                    User agent interfaces do not always isolate
                    cookies by path, and so this should not be
                    considered an effective security measure.

                (See also: RFC 6265, Section 4.1.2.4)

        .. _Same-Site warnings:
            https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#Fixing_common_warnings
        """  # noqa: E501
        if self._cookies is None:
            self._cookies = http_cookies.SimpleCookie()

        self._cookies[name] = ''

        # NOTE(Freezerburn): SimpleCookie apparently special cases the
        # expires attribute to automatically use strftime and set the
        # time as a delta from the current time. We use -1 here to
        # basically tell the browser to immediately expire the cookie,
        # thus removing it from future request objects.
        self._cookies[name]['expires'] = -1

        # NOTE(CaselIT): Set SameSite to Lax to avoid setting invalid cookies.
        # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#Fixing_common_warnings  # noqa: E501
        self._cookies[name]['samesite'] = samesite

        if domain:
            self._cookies[name]['domain'] = domain

        if path:
            self._cookies[name]['path'] = path

    @overload
    def get_header(self, name: str, default: str) -> str: ...

    @overload
    def get_header(self, name: str, default: Optional[str] = ...) -> Optional[str]: ...

    def get_header(self, name: str, default: Optional[str] = None) -> Optional[str]:
        """Retrieve the raw string value for the given header.

        Normally, when a header has multiple values, they will be
        returned as a single, comma-delimited string. However, the
        Set-Cookie header does not support this format, and so
        attempting to retrieve it will raise an error.

        Args:
            name (str): Header name, case-insensitive. Must be of type ``str``
                or ``StringType``, and only character values 0x00 through 0xFF
                may be used on platforms that use wide characters.
        Keyword Args:
            default: Value to return if the header
                is not found (default ``None``).

        Raises:
            ValueError: The value of the 'Set-Cookie' header(s) was requested.

        Returns:
            str: The value of the specified header if set, or
            the default value if not set.
        """

        # NOTE(kgriffs): normalize name by lowercasing it
        name = name.lower()

        if name == 'set-cookie':
            raise HeaderNotSupported('Getting Set-Cookie is not currently supported.')

        return self._headers.get(name, default)

    def set_header(self, name: str, value: str) -> None:
        """Set a header for this response to a given value.

        Warning:
            Calling this method overwrites any values already set for this
            header. To append an additional value for this header, use
            :meth:`~.append_header` instead.

        Warning:
            This method cannot be used to set cookies; instead, use
            :meth:`~.append_header` or :meth:`~.set_cookie`.

        Args:
            name (str): Header name (case-insensitive). The name may contain
                only US-ASCII characters.
            value (str): Value for the header. As with the header's name, the
                value may contain only US-ASCII characters.

        Raises:
            ValueError: `name` cannot be ``'Set-Cookie'``.
        """

        # NOTE(kgriffs): uwsgi fails with a TypeError if any header
        # is not a str, so do the conversion here. It's actually
        # faster to not do an isinstance check. str() will encode
        # to US-ASCII.
        value = str(value)

        # NOTE(kgriffs): normalize name by lowercasing it
        name = name.lower()

        if name == 'set-cookie':
            raise HeaderNotSupported('This method cannot be used to set cookies')

        self._headers[name] = value

    def delete_header(self, name: str) -> None:
        """Delete a header that was previously set for this response.

        If the header was not previously set, nothing is done (no error is
        raised). Otherwise, all values set for the header will be removed
        from the response.

        Note that calling this method is equivalent to setting the
        corresponding header property (when said property is available) to
        ``None``. For example::

            resp.etag = None

        Warning:
            This method cannot be used with the Set-Cookie header. Instead,
            use :meth:`~.unset_cookie` to remove a cookie and ensure that the
            user agent expires its own copy of the data as well.

        Args:
            name (str): Header name (case-insensitive). The name may
                contain only US-ASCII characters.

        Raises:
            ValueError: `name` cannot be ``'Set-Cookie'``.
        """

        # NOTE(kgriffs): normalize name by lowercasing it
        name = name.lower()

        if name == 'set-cookie':
            raise HeaderNotSupported('This method cannot be used to remove cookies')

        self._headers.pop(name, None)

    def append_header(self, name: str, value: str) -> None:
        """Set or append a header for this response.

        If the header already exists, the new value will normally be appended
        to it, delimited by a comma. The notable exception to this rule is
        Set-Cookie, in which case a separate header line for each value will be
        included in the response.

        Note:
            While this method can be used to efficiently append raw
            Set-Cookie headers to the response, you may find
            :meth:`~.set_cookie` to be more convenient.

        Args:
            name (str): Header name (case-insensitive). The name may contain
                only US-ASCII characters.
            value (str): Value for the header. As with the header's name, the
                value may contain only US-ASCII characters.
        """

        # NOTE(kgriffs): uwsgi fails with a TypeError if any header
        # is not a str, so do the conversion here. It's actually
        # faster to not do an isinstance check. str() will encode
        # to US-ASCII.
        value = str(value)

        # NOTE(kgriffs): normalize name by lowercasing it
        name = name.lower()

        if name == 'set-cookie':
            if not self._extra_headers:
                self._extra_headers = [(name, value)]
            else:
                self._extra_headers.append((name, value))
        else:
            if name in self._headers:
                value = self._headers[name] + ', ' + value

            self._headers[name] = value

    def set_headers(
        self, headers: Union[Mapping[str, str], Iterable[Tuple[str, str]]]
    ) -> None:
        """Set several headers at once.

        This method can be used to set a collection of raw header names and
        values all at once.

        Warning:
            Calling this method overwrites any existing values for the given
            header. If a list containing multiple instances of the same header
            is provided, only the last value will be used. To add multiple
            values to the response for a given header, see
            :meth:`~.append_header`.

        Warning:
            This method cannot be used to set cookies; instead, use
            :meth:`~.append_header` or :meth:`~.set_cookie`.

        Args:
            headers (Iterable[[str, str]]): An iterable of ``[name, value]`` two-member
                iterables, or a dict-like object that implements an ``items()`` method.
                Both *name* and *value* must be of type ``str`` and
                contain only US-ASCII characters.

                Note:
                    Falcon can process an iterable of tuples slightly faster
                    than a dict.

        Raises:
            ValueError: `headers` was not a ``dict`` or ``list`` of ``tuple``
                         or ``Iterable[[str, str]]``.
        """

        header_items = getattr(headers, 'items', None)

        if callable(header_items):
            headers = header_items()

        # NOTE(kgriffs): We can't use dict.update because we have to
        # normalize the header names.
        _headers = self._headers

        for name, value in headers:  # type: ignore[misc]
            # NOTE(kgriffs): uwsgi fails with a TypeError if any header
            # is not a str, so do the conversion here. It's actually
            # faster to not do an isinstance check. str() will encode
            # to US-ASCII.
            value = str(value)

            name = name.lower()
            if name == 'set-cookie':
                raise HeaderNotSupported('This method cannot be used to set cookies')

            _headers[name] = value

    def append_link(
        self,
        target: str,
        rel: str,
        title: Optional[str] = None,
        title_star: Optional[Tuple[str, str]] = None,
        anchor: Optional[str] = None,
        hreflang: Optional[Union[str, Iterable[str]]] = None,
        type_hint: Optional[str] = None,
        crossorigin: Optional[str] = None,
        link_extension: Optional[Iterable[Tuple[str, str]]] = None,
    ) -> None:
        """Append a link header to the response.

        (See also: RFC 5988, Section 1)

        Note:
            Calling this method repeatedly will cause each link to be
            appended to the Link header value, separated by commas.

        Args:
            target (str): Target IRI for the resource identified by the
                link. Will be converted to a URI, if necessary, per
                RFC 3987, Section 3.1.
            rel (str): Relation type of the link, such as "next" or
                "bookmark".

                (See also:
                http://www.iana.org/assignments/link-relations/link-relations.xhtml)

        Keyword Args:
            title (str): Human-readable label for the destination of
                the link (default ``None``). If the title includes non-ASCII
                characters, you will need to use `title_star` instead, or
                provide both a US-ASCII version using `title` and a
                Unicode version using `title_star`.
            title_star (tuple[str, str]): Localized title describing the
                destination of the link (default ``None``). The value must be a
                two-member tuple in the form of (*language-tag*, *text*),
                where *language-tag* is a standard language identifier as
                defined in RFC 5646, Section 2.1, and *text* is a Unicode
                string.

                Note:
                    *language-tag* may be an empty string, in which case the
                    client will assume the language from the general context
                    of the current request.

                Note:
                    *text* will always be encoded as UTF-8.

            anchor (str): Override the context IRI with a different URI
                (default None). By default, the context IRI for the link is
                simply the IRI of the requested resource. The value
                provided may be a relative URI.
            hreflang (str or iterable): Either a single *language-tag*, or
                a ``list`` or ``tuple`` of such tags to provide a hint to the
                client as to the language of the result of following the link.
                A list of tags may be given in order to indicate to the
                client that the target resource is available in multiple
                languages.
            type_hint(str): Provides a hint as to the media type of the
                result of dereferencing the link (default ``None``). As noted
                in RFC 5988, this is only a hint and does not override the
                Content-Type header returned when the link is followed.
            crossorigin(str):  Determines how cross origin requests are handled.
                Can take values 'anonymous' or 'use-credentials' or None.
                (See:
                https://www.w3.org/TR/html50/infrastructure.html#cors-settings-attribute)
            link_extension: Provides additional custom attributes, as
                described in RFC 8288, Section 3.4.2; each member of the iterable
                must be a two-tuple in the form of (*param*, *value*).

        """

        # PERF(kgriffs): Heuristic to detect possibility of an extension
        # relation type, in which case it will be a URL that may contain
        # reserved characters. Otherwise, don't waste time running the
        # string through uri.encode
        #
        # Example values for rel:
        #
        #     "next"
        #     "http://example.com/ext-type"
        #     "https://example.com/ext-type"
        #     "alternate http://example.com/ext-type"
        #     "http://example.com/ext-type alternate"
        #
        if '//' in rel:
            if ' ' in rel:
                rel = '"' + ' '.join([uri_encode(r) for r in rel.split()]) + '"'
            else:
                rel = f'"{uri_encode(rel)}"'

        value = '<' + uri_encode(target) + '>; rel=' + rel

        if title is not None:
            value += f'; title="{title}"'

        if title_star is not None:
            value += f"; title*=UTF-8'{title_star[0]}'{uri_encode_value(title_star[1])}"

        if type_hint is not None:
            value += f'; type="{type_hint}"'

        if hreflang is not None:
            if isinstance(hreflang, str):
                value += f'; hreflang={hreflang}'
            else:
                value += '; '
                value += '; '.join(['hreflang=' + lang for lang in hreflang])

        if anchor is not None:
            value += f'; anchor="{uri_encode(anchor)}"'

        if crossorigin is not None:
            crossorigin = crossorigin.lower()
            if crossorigin not in _RESERVED_CROSSORIGIN_VALUES:
                raise ValueError(
                    "crossorigin must be set to either 'anonymous' or 'use-credentials'"
                )
            if crossorigin == 'anonymous':
                value += '; crossorigin'
            else:  # crossorigin == 'use-credentials'
                # PERF(vytas): the only remaining value is inlined.
                # Un-inline in case more values are supported in the future.
                value += '; crossorigin="use-credentials"'

        if link_extension is not None:
            value += '; '
            value += '; '.join([f'{p}={v}' for p, v in link_extension])

        _headers = self._headers
        if 'link' in _headers:
            _headers['link'] += f', {value}'
        else:
            _headers['link'] = value

    @property
    def add_link(self) -> NoReturn:
        raise AttributeRemovedError(
            'The add_link() method is no longer supported. '
            'Please use append_link() instead.'
        )

    cache_control: Union[str, Iterable[str], None] = _header_property(
        'Cache-Control',
        """Set the Cache-Control header.

        Used to set a list of cache directives to use as the value of the
        Cache-Control header. The list will be joined with ", " to produce
        the value for the header.
        """,
        _format_header_value_list,
    )
    """Set the Cache-Control header.

    Used to set a list of cache directives to use as the value of the
    Cache-Control header. The list will be joined with ", " to produce
    the value for the header.
    """

    content_location: Optional[str] = _header_property(
        'Content-Location',
        """Set the Content-Location header.

        This value will be URI encoded per RFC 3986. If the value that is
        being set is already URI encoded it should be decoded first or the
        header should be set manually using the set_header method.
        """,
        uri_encode,
    )
    """Set the Content-Location header.

    This value will be URI encoded per RFC 3986. If the value that is
    being set is already URI encoded it should be decoded first or the
    header should be set manually using the set_header method.
    """

    content_length: Union[str, int, None] = _header_property(
        'Content-Length',
        """Set the Content-Length header.

        This property can be used for responding to HEAD requests when you
        aren't actually providing the response body, or when streaming the
        response. If either the `text` property or the `data` property is set
        on the response, the framework will force Content-Length to be the
        length of the given text bytes. Therefore, it is only necessary to
        manually set the content length when those properties are not used.

        Note:
            In cases where the response content is a stream (readable
            file-like object), Falcon will not supply a Content-Length header
            to the server unless `content_length` is explicitly set.
            Consequently, the server may choose to use chunked encoding in this
            case.

        """,
    )
    """Set the Content-Length header.

    This property can be used for responding to HEAD requests when you
    aren't actually providing the response body, or when streaming the
    response. If either the `text` property or the `data` property is set
    on the response, the framework will force Content-Length to be the
    length of the given text bytes. Therefore, it is only necessary to
    manually set the content length when those properties are not used.

    Note:
        In cases where the response content is a stream (readable
        file-like object), Falcon will not supply a Content-Length header
        to the server unless `content_length` is explicitly set.
        Consequently, the server may choose to use chunked encoding in this
        case.

    """

    content_range: Union[str, RangeSetHeader, None] = _header_property(
        'Content-Range',
        """A tuple to use in constructing a value for the Content-Range header.

        The tuple has the form (*start*, *end*, *length*, [*unit*]), where *start* and
        *end* designate the range (inclusive), and *length* is the
        total length, or '\\*' if unknown. You may pass ``int``'s for
        these numbers (no need to convert to ``str`` beforehand). The optional value
        *unit* describes the range unit and defaults to 'bytes'

        Note:
            You only need to use the alternate form, 'bytes \\*/1234', for
            responses that use the status '416 Range Not Satisfiable'. In this
            case, raising ``falcon.HTTPRangeNotSatisfiable`` will do the right
            thing.

        (See also: RFC 7233, Section 4.2)
        """,
        _format_range,
    )
    """A tuple to use in constructing a value for the Content-Range header.

    The tuple has the form (*start*, *end*, *length*, [*unit*]), where *start* and
    *end* designate the range (inclusive), and *length* is the
    total length, or '\\*' if unknown. You may pass ``int``'s for
    these numbers (no need to convert to ``str`` beforehand). The optional value
    *unit* describes the range unit and defaults to 'bytes'

    Note:
        You only need to use the alternate form, 'bytes \\*/1234', for
        responses that use the status '416 Range Not Satisfiable'. In this
        case, raising ``falcon.HTTPRangeNotSatisfiable`` will do the right
        thing.

    (See also: RFC 7233, Section 4.2)
    """

    content_type: Optional[str] = _header_property(
        'Content-Type',
        """Sets the Content-Type header.

        The ``falcon`` module provides a number of constants for
        common media types, including ``falcon.MEDIA_JSON``,
        ``falcon.MEDIA_MSGPACK``, ``falcon.MEDIA_YAML``,
        ``falcon.MEDIA_XML``, ``falcon.MEDIA_HTML``,
        ``falcon.MEDIA_JS``, ``falcon.MEDIA_TEXT``,
        ``falcon.MEDIA_JPEG``, ``falcon.MEDIA_PNG``,
        and ``falcon.MEDIA_GIF``.
        """,
    )
    """Sets the Content-Type header.

    The ``falcon`` module provides a number of constants for
    common media types, including ``falcon.MEDIA_JSON``,
    ``falcon.MEDIA_MSGPACK``, ``falcon.MEDIA_YAML``,
    ``falcon.MEDIA_XML``, ``falcon.MEDIA_HTML``,
    ``falcon.MEDIA_JS``, ``falcon.MEDIA_TEXT``,
    ``falcon.MEDIA_JPEG``, ``falcon.MEDIA_PNG``,
    and ``falcon.MEDIA_GIF``.
    """

    downloadable_as: Optional[str] = _header_property(
        'Content-Disposition',
        """Set the Content-Disposition header using the given filename.

        The value will be used for the ``filename`` directive. For example,
        given ``'report.pdf'``, the Content-Disposition header would be set
        to: ``'attachment; filename="report.pdf"'``.

        As per `RFC 6266 <https://tools.ietf.org/html/rfc6266#appendix-D>`_
        recommendations, non-ASCII filenames will be encoded using the
        ``filename*`` directive, whereas ``filename`` will contain the US
        ASCII fallback.
        """,
        functools.partial(_format_content_disposition, disposition_type='attachment'),
    )
    """Set the Content-Disposition header using the given filename.

    The value will be used for the ``filename`` directive. For example,
    given ``'report.pdf'``, the Content-Disposition header would be set
    to: ``'attachment; filename="report.pdf"'``.

    As per `RFC 6266 <https://tools.ietf.org/html/rfc6266#appendix-D>`_
    recommendations, non-ASCII filenames will be encoded using the
    ``filename*`` directive, whereas ``filename`` will contain the US
    ASCII fallback.
    """

    viewable_as: Optional[str] = _header_property(
        'Content-Disposition',
        """Set an inline Content-Disposition header using the given filename.

        The value will be used for the ``filename`` directive. For example,
        given ``'report.pdf'``, the Content-Disposition header would be set
        to: ``'inline; filename="report.pdf"'``.

        As per `RFC 6266 <https://tools.ietf.org/html/rfc6266#appendix-D>`_
        recommendations, non-ASCII filenames will be encoded using the
        ``filename*`` directive, whereas ``filename`` will contain the US
        ASCII fallback.

        .. versionadded:: 3.1
        """,
        functools.partial(_format_content_disposition, disposition_type='inline'),
    )
    """Set an inline Content-Disposition header using the given filename.

    The value will be used for the ``filename`` directive. For example,
    given ``'report.pdf'``, the Content-Disposition header would be set
    to: ``'inline; filename="report.pdf"'``.

    As per `RFC 6266 <https://tools.ietf.org/html/rfc6266#appendix-D>`_
    recommendations, non-ASCII filenames will be encoded using the
    ``filename*`` directive, whereas ``filename`` will contain the US
    ASCII fallback.

    .. versionadded:: 3.1
    """

    etag: Optional[str] = _header_property(
        'ETag',
        """Set the ETag header.

        The ETag header will be wrapped with double quotes ``"value"`` in case
        the user didn't pass it.
        """,
        _format_etag_header,
    )
    """Set the ETag header.

    The ETag header will be wrapped with double quotes ``"value"`` in case
    the user didn't pass it.
    """

    expires: Union[str, datetime, None] = _header_property(
        'Expires',
        """Set the Expires header. Set to a ``datetime`` (UTC) instance.

        Note:
            Falcon will format the ``datetime`` as an HTTP date string.
        """,
        dt_to_http,
    )
    """Set the Expires header. Set to a ``datetime`` (UTC) instance.

    Note:
        Falcon will format the ``datetime`` as an HTTP date string.
    """

    last_modified: Union[str, datetime, None] = _header_property(
        'Last-Modified',
        """Set the Last-Modified header. Set to a ``datetime`` (UTC) instance.

        Note:
            Falcon will format the ``datetime`` as an HTTP date string.
        """,
        dt_to_http,
    )
    """Set the Last-Modified header. Set to a ``datetime`` (UTC) instance.

    Note:
        Falcon will format the ``datetime`` as an HTTP date string.
    """

    location: Optional[str] = _header_property(
        'Location',
        """Set the Location header.

        This value will be URI encoded per RFC 3986. If the value that is
        being set is already URI encoded it should be decoded first or the
        header should be set manually using the set_header method.
        """,
        uri_encode,
    )
    """Set the Location header.

    This value will be URI encoded per RFC 3986. If the value that is
    being set is already URI encoded it should be decoded first or the
    header should be set manually using the set_header method.
    """

    retry_after: Union[int, str, None] = _header_property(
        'Retry-After',
        """Set the Retry-After header.

        The expected value is an integral number of seconds to use as the
        value for the header. The HTTP-date syntax is not supported.
        """,
    )
    """Set the Retry-After header.

    The expected value is an integral number of seconds to use as the
    value for the header. The HTTP-date syntax is not supported.
    """

    vary: Union[str, Iterable[str], None] = _header_property(
        'Vary',
        """Value to use for the Vary header.

        Set this property to an iterable of header names. For a single
        asterisk or field value, simply pass a single-element ``list``
        or ``tuple``.

        The "Vary" header field in a response describes what parts of
        a request message, aside from the method, Host header field,
        and request target, might influence the origin server's
        process for selecting and representing this response.  The
        value consists of either a single asterisk ("*") or a list of
        header field names (case-insensitive).

        (See also: RFC 7231, Section 7.1.4)
        """,
        _format_header_value_list,
    )
    """Value to use for the Vary header.

    Set this property to an iterable of header names. For a single
    asterisk or field value, simply pass a single-element ``list``
    or ``tuple``.

    The "Vary" header field in a response describes what parts of
    a request message, aside from the method, Host header field,
    and request target, might influence the origin server's
    process for selecting and representing this response.  The
    value consists of either a single asterisk ("*") or a list of
    header field names (case-insensitive).

    (See also: RFC 7231, Section 7.1.4)
    """

    accept_ranges: Optional[str] = _header_property(
        'Accept-Ranges',
        """Set the Accept-Ranges header.

        The Accept-Ranges header field indicates to the client which
        range units are supported (e.g. "bytes") for the target
        resource.

        If range requests are not supported for the target resource,
        the header may be set to "none" to advise the client not to
        attempt any such requests.

        Note:
            "none" is the literal string, not Python's built-in ``None``
            type.

        """,
    )
    """Set the Accept-Ranges header.

    The Accept-Ranges header field indicates to the client which
    range units are supported (e.g. "bytes") for the target
    resource.

    If range requests are not supported for the target resource,
    the header may be set to "none" to advise the client not to
    attempt any such requests.

    Note:
        "none" is the literal string, not Python's built-in ``None``
        type.

    """

    def _set_media_type(self, media_type: Optional[str] = None) -> None:
        """Set a content-type; wrapper around set_header.

        Args:
            media_type: Media type to use for the Content-Type
                header.

        """

        # PERF(kgriffs): Using "in" like this is faster than dict.setdefault()
        #   in most cases, except on PyPy where it is only a fraction of a
        #   nanosecond slower. Last tested on Python versions 3.5-3.7.
        if media_type is not None and 'content-type' not in self._headers:
            self._headers['content-type'] = media_type

    def _wsgi_headers(self, media_type: Optional[str] = None) -> list[tuple[str, str]]:
        """Convert headers into the format expected by WSGI servers.

        Args:
            media_type: Default media type to use for the Content-Type
                header if the header was not set explicitly (default ``None``).

        """

        headers = self._headers
        # PERF(vytas): uglier inline version of Response._set_media_type
        if media_type is not None and 'content-type' not in headers:
            headers['content-type'] = media_type

        items = list(headers.items())

        if self._extra_headers:
            items += self._extra_headers

        # NOTE(kgriffs): It is important to append these after self._extra_headers
        #   in case the latter contains Set-Cookie headers that should be
        #   overridden by a call to unset_cookie().
        if self._cookies is not None:
            # PERF(tbug):
            # The below implementation is ~23% faster than
            # the alternative:
            #
            #     self._cookies.output().split("\\r\\n")
            #
            # Even without the .split("\\r\\n"), the below
            # is still ~17% faster, so don't use .output()
            items += [('set-cookie', c.OutputString()) for c in self._cookies.values()]
        return items


class ResponseOptions:
    """Defines a set of configurable response options.

    An instance of this class is exposed via :attr:`falcon.App.resp_options`
    and :attr:`falcon.asgi.App.resp_options` for configuring certain
    :class:`~.Response` behaviors.
    """

    secure_cookies_by_default: bool
    """Set to ``False`` in development environments to make the ``secure`` attribute
    for all cookies. (default ``True``).

    This can make testing easier by not requiring HTTPS. Note, however, that this
    setting can be overridden via :meth:`~.Response.set_cookie()`'s ``secure`` kwarg.
    """
    default_media_type: str
    """The default Internet media type (RFC 2046) to use when rendering a response,
    when the Content-Type header is not set explicitly.

    This value is normally set to the media type provided when a :class:`falcon.App`
    is initialized; however, if created independently, this will default to
    :attr:`falcon.DEFAULT_MEDIA_TYPE`.
    """
    media_handlers: Handlers
    """A dict-like object for configuring the media-types to handle.

    Default handlers are provided for the ``application/json``,
    ``application/x-www-form-urlencoded`` and ``multipart/form-data`` media types.
    """
    static_media_types: Dict[str, str]
    """A mapping of dot-prefixed file extensions to Internet media types (RFC 2046).

    Defaults to ``mimetypes.types_map`` after calling ``mimetypes.init()``.
    """
    xml_error_serialization: bool
    """Set to ``False`` to disable automatic inclusion of the XML handler
    in the :ref:`default error serializer <errors>` (default ``True``).

    Enabling this option does not make Falcon automatically render all error
    responses in XML, but it is used only in the case the client prefers
    (via the ``Accept`` request header) XML to JSON and other configured media
    handlers.

    Note:
        Falcon 5.0 will either change the default to ``False``, or remove the
        automatic XML error serialization altogether.

    Note:
        This option has no effect when a custom error serializer, set using
        :meth:`~falcon.App.set_error_serializer`, is in use.

    .. versionadded:: 4.0
    """

    __slots__ = (
        'secure_cookies_by_default',
        'default_media_type',
        'media_handlers',
        'static_media_types',
        'xml_error_serialization',
    )

    def __init__(self) -> None:
        self.secure_cookies_by_default = True
        self.default_media_type = DEFAULT_MEDIA_TYPE
        self.media_handlers = Handlers()
        self.xml_error_serialization = True

        if not mimetypes.inited:
            mimetypes.init()
        self.static_media_types = mimetypes.types_map.copy()
        self.static_media_types.update(_DEFAULT_STATIC_MEDIA_TYPES)