1from __future__ import annotations
2
3import json
4import typing as t
5from http import HTTPStatus
6from urllib.parse import urljoin
7
8from .._internal import _get_environ
9from ..datastructures import Headers
10from ..http import generate_etag
11from ..http import http_date
12from ..http import is_resource_modified
13from ..http import parse_etags
14from ..http import parse_range_header
15from ..http import remove_entity_headers
16from ..sansio.response import Response as _SansIOResponse
17from ..urls import iri_to_uri
18from ..utils import cached_property
19from ..wsgi import _RangeWrapper
20from ..wsgi import ClosingIterator
21from ..wsgi import get_current_url
22
23if t.TYPE_CHECKING:
24 from _typeshed.wsgi import StartResponse
25 from _typeshed.wsgi import WSGIApplication
26 from _typeshed.wsgi import WSGIEnvironment
27
28 from .request import Request
29
30
31def _iter_encoded(iterable: t.Iterable[str | bytes]) -> t.Iterator[bytes]:
32 for item in iterable:
33 if isinstance(item, str):
34 yield item.encode()
35 else:
36 yield item
37
38
39class Response(_SansIOResponse):
40 """Represents an outgoing WSGI HTTP response with body, status, and
41 headers. Has properties and methods for using the functionality
42 defined by various HTTP specs.
43
44 The response body is flexible to support different use cases. The
45 simple form is passing bytes, or a string which will be encoded as
46 UTF-8. Passing an iterable of bytes or strings makes this a
47 streaming response. A generator is particularly useful for building
48 a CSV file in memory or using SSE (Server Sent Events). A file-like
49 object is also iterable, although the
50 :func:`~werkzeug.utils.send_file` helper should be used in that
51 case.
52
53 The response object is itself a WSGI application callable. When
54 called (:meth:`__call__`) with ``environ`` and ``start_response``,
55 it will pass its status and headers to ``start_response`` then
56 return its body as an iterable.
57
58 .. code-block:: python
59
60 from werkzeug.wrappers.response import Response
61
62 def index():
63 return Response("Hello, World!")
64
65 def application(environ, start_response):
66 path = environ.get("PATH_INFO") or "/"
67
68 if path == "/":
69 response = index()
70 else:
71 response = Response("Not Found", status=404)
72
73 return response(environ, start_response)
74
75 :param response: The data for the body of the response. A string or
76 bytes, or tuple or list of strings or bytes, for a fixed-length
77 response, or any other iterable of strings or bytes for a
78 streaming response. Defaults to an empty body.
79 :param status: The status code for the response. Either an int, in
80 which case the default status message is added, or a string in
81 the form ``{code} {message}``, like ``404 Not Found``. Defaults
82 to 200.
83 :param headers: A :class:`~werkzeug.datastructures.Headers` object,
84 or a list of ``(key, value)`` tuples that will be converted to a
85 ``Headers`` object.
86 :param mimetype: The mime type (content type without charset or
87 other parameters) of the response. If the value starts with
88 ``text/`` (or matches some other special cases), the charset
89 will be added to create the ``content_type``.
90 :param content_type: The full content type of the response.
91 Overrides building the value from ``mimetype``.
92 :param direct_passthrough: Pass the response body directly through
93 as the WSGI iterable. This can be used when the body is a binary
94 file or other iterator of bytes, to skip some unnecessary
95 checks. Use :func:`~werkzeug.utils.send_file` instead of setting
96 this manually.
97
98 .. versionchanged:: 2.1
99 Old ``BaseResponse`` and mixin classes were removed.
100
101 .. versionchanged:: 2.0
102 Combine ``BaseResponse`` and mixins into a single ``Response``
103 class.
104
105 .. versionchanged:: 0.5
106 The ``direct_passthrough`` parameter was added.
107 """
108
109 #: if set to `False` accessing properties on the response object will
110 #: not try to consume the response iterator and convert it into a list.
111 #:
112 #: .. versionadded:: 0.6.2
113 #:
114 #: That attribute was previously called `implicit_seqence_conversion`.
115 #: (Notice the typo). If you did use this feature, you have to adapt
116 #: your code to the name change.
117 implicit_sequence_conversion = True
118
119 #: If a redirect ``Location`` header is a relative URL, make it an
120 #: absolute URL, including scheme and domain.
121 #:
122 #: .. versionchanged:: 2.1
123 #: This is disabled by default, so responses will send relative
124 #: redirects.
125 #:
126 #: .. versionadded:: 0.8
127 autocorrect_location_header = False
128
129 #: Should this response object automatically set the content-length
130 #: header if possible? This is true by default.
131 #:
132 #: .. versionadded:: 0.8
133 automatically_set_content_length = True
134
135 #: The response body to send as the WSGI iterable. A list of strings
136 #: or bytes represents a fixed-length response, any other iterable
137 #: is a streaming response. Strings are encoded to bytes as UTF-8.
138 #:
139 #: Do not set to a plain string or bytes, that will cause sending
140 #: the response to be very inefficient as it will iterate one byte
141 #: at a time.
142 response: t.Iterable[str] | t.Iterable[bytes]
143
144 def __init__(
145 self,
146 response: t.Iterable[bytes] | bytes | t.Iterable[str] | str | None = None,
147 status: int | str | HTTPStatus | None = None,
148 headers: t.Mapping[str, str | t.Iterable[str]]
149 | t.Iterable[tuple[str, str]]
150 | None = None,
151 mimetype: str | None = None,
152 content_type: str | None = None,
153 direct_passthrough: bool = False,
154 ) -> None:
155 super().__init__(
156 status=status,
157 headers=headers,
158 mimetype=mimetype,
159 content_type=content_type,
160 )
161
162 #: Pass the response body directly through as the WSGI iterable.
163 #: This can be used when the body is a binary file or other
164 #: iterator of bytes, to skip some unnecessary checks. Use
165 #: :func:`~werkzeug.utils.send_file` instead of setting this
166 #: manually.
167 self.direct_passthrough = direct_passthrough
168 self._on_close: list[t.Callable[[], t.Any]] = []
169
170 # we set the response after the headers so that if a class changes
171 # the charset attribute, the data is set in the correct charset.
172 if response is None:
173 self.response = []
174 elif isinstance(response, (str, bytes, bytearray)):
175 self.set_data(response)
176 else:
177 self.response = response
178
179 def call_on_close(self, func: t.Callable[[], t.Any]) -> t.Callable[[], t.Any]:
180 """Adds a function to the internal list of functions that should
181 be called as part of closing down the response. Since 0.7 this
182 function also returns the function that was passed so that this
183 can be used as a decorator.
184
185 .. versionadded:: 0.6
186 """
187 self._on_close.append(func)
188 return func
189
190 def __repr__(self) -> str:
191 if self.is_sequence:
192 body_info = f"{sum(map(len, self.iter_encoded()))} bytes"
193 else:
194 body_info = "streamed" if self.is_streamed else "likely-streamed"
195 return f"<{type(self).__name__} {body_info} [{self.status}]>"
196
197 @classmethod
198 def force_type(
199 cls, response: Response, environ: WSGIEnvironment | None = None
200 ) -> Response:
201 """Enforce that the WSGI response is a response object of the current
202 type. Werkzeug will use the :class:`Response` internally in many
203 situations like the exceptions. If you call :meth:`get_response` on an
204 exception you will get back a regular :class:`Response` object, even
205 if you are using a custom subclass.
206
207 This method can enforce a given response type, and it will also
208 convert arbitrary WSGI callables into response objects if an environ
209 is provided::
210
211 # convert a Werkzeug response object into an instance of the
212 # MyResponseClass subclass.
213 response = MyResponseClass.force_type(response)
214
215 # convert any WSGI application into a response object
216 response = MyResponseClass.force_type(response, environ)
217
218 This is especially useful if you want to post-process responses in
219 the main dispatcher and use functionality provided by your subclass.
220
221 Keep in mind that this will modify response objects in place if
222 possible!
223
224 :param response: a response object or wsgi application.
225 :param environ: a WSGI environment object.
226 :return: a response object.
227 """
228 if not isinstance(response, Response):
229 if environ is None:
230 raise TypeError(
231 "cannot convert WSGI application into response"
232 " objects without an environ"
233 )
234
235 from ..test import run_wsgi_app
236
237 response = Response(*run_wsgi_app(response, environ))
238
239 response.__class__ = cls
240 return response
241
242 @classmethod
243 def from_app(
244 cls, app: WSGIApplication, environ: WSGIEnvironment, buffered: bool = False
245 ) -> Response:
246 """Create a new response object from an application output. This
247 works best if you pass it an application that returns a generator all
248 the time. Sometimes applications may use the `write()` callable
249 returned by the `start_response` function. This tries to resolve such
250 edge cases automatically. But if you don't get the expected output
251 you should set `buffered` to `True` which enforces buffering.
252
253 :param app: the WSGI application to execute.
254 :param environ: the WSGI environment to execute against.
255 :param buffered: set to `True` to enforce buffering.
256 :return: a response object.
257 """
258 from ..test import run_wsgi_app
259
260 return cls(*run_wsgi_app(app, environ, buffered))
261
262 @t.overload
263 def get_data(self, as_text: t.Literal[False] = False) -> bytes: ...
264
265 @t.overload
266 def get_data(self, as_text: t.Literal[True]) -> str: ...
267
268 def get_data(self, as_text: bool = False) -> bytes | str:
269 """The string representation of the response body. Whenever you call
270 this property the response iterable is encoded and flattened. This
271 can lead to unwanted behavior if you stream big data.
272
273 This behavior can be disabled by setting
274 :attr:`implicit_sequence_conversion` to `False`.
275
276 If `as_text` is set to `True` the return value will be a decoded
277 string.
278
279 .. versionadded:: 0.9
280 """
281 self._ensure_sequence()
282 rv = b"".join(self.iter_encoded())
283
284 if as_text:
285 return rv.decode()
286
287 return rv
288
289 def set_data(self, value: bytes | str) -> None:
290 """Sets a new string as response. The value must be a string or
291 bytes. If a string is set it's encoded to the charset of the
292 response (utf-8 by default).
293
294 .. versionadded:: 0.9
295 """
296 if isinstance(value, str):
297 value = value.encode()
298 self.response = [value]
299 if self.automatically_set_content_length:
300 self.headers["Content-Length"] = str(len(value))
301
302 data = property(
303 get_data,
304 set_data,
305 doc="A descriptor that calls :meth:`get_data` and :meth:`set_data`.",
306 )
307
308 def calculate_content_length(self) -> int | None:
309 """Returns the content length if available or `None` otherwise."""
310 try:
311 self._ensure_sequence()
312 except RuntimeError:
313 return None
314 return sum(len(x) for x in self.iter_encoded())
315
316 def _ensure_sequence(self, mutable: bool = False) -> None:
317 """This method can be called by methods that need a sequence. If
318 `mutable` is true, it will also ensure that the response sequence
319 is a standard Python list.
320
321 .. versionadded:: 0.6
322 """
323 if self.is_sequence:
324 # if we need a mutable object, we ensure it's a list.
325 if mutable and not isinstance(self.response, list):
326 self.response = list(self.response) # type: ignore
327 return
328 if self.direct_passthrough:
329 raise RuntimeError(
330 "Attempted implicit sequence conversion but the"
331 " response object is in direct passthrough mode."
332 )
333 if not self.implicit_sequence_conversion:
334 raise RuntimeError(
335 "The response object required the iterable to be a"
336 " sequence, but the implicit conversion was disabled."
337 " Call make_sequence() yourself."
338 )
339 self.make_sequence()
340
341 def make_sequence(self) -> None:
342 """Converts the response iterator in a list. By default this happens
343 automatically if required. If `implicit_sequence_conversion` is
344 disabled, this method is not automatically called and some properties
345 might raise exceptions. This also encodes all the items.
346
347 .. versionadded:: 0.6
348 """
349 if not self.is_sequence:
350 # if we consume an iterable we have to ensure that the close
351 # method of the iterable is called if available when we tear
352 # down the response
353 close = getattr(self.response, "close", None)
354 self.response = list(self.iter_encoded())
355 if close is not None:
356 self.call_on_close(close)
357
358 def iter_encoded(self) -> t.Iterator[bytes]:
359 """Iter the response encoded with the encoding of the response.
360 If the response object is invoked as WSGI application the return
361 value of this method is used as application iterator unless
362 :attr:`direct_passthrough` was activated.
363 """
364 # Encode in a separate function so that self.response is fetched
365 # early. This allows us to wrap the response with the return
366 # value from get_app_iter or iter_encoded.
367 return _iter_encoded(self.response)
368
369 @property
370 def is_streamed(self) -> bool:
371 """If the response is streamed (the response is not an iterable with
372 a length information) this property is `True`. In this case streamed
373 means that there is no information about the number of iterations.
374 This is usually `True` if a generator is passed to the response object.
375
376 This is useful for checking before applying some sort of post
377 filtering that should not take place for streamed responses.
378 """
379 try:
380 len(self.response) # type: ignore
381 except (TypeError, AttributeError):
382 return True
383 return False
384
385 @property
386 def is_sequence(self) -> bool:
387 """If the iterator is buffered, this property will be `True`. A
388 response object will consider an iterator to be buffered if the
389 response attribute is a list or tuple.
390
391 .. versionadded:: 0.6
392 """
393 return isinstance(self.response, (tuple, list))
394
395 def close(self) -> None:
396 """Close the wrapped response if possible. You can also use the object
397 in a with statement which will automatically close it.
398
399 .. versionadded:: 0.9
400 Can now be used in a with statement.
401 """
402 if hasattr(self.response, "close"):
403 self.response.close()
404 for func in self._on_close:
405 func()
406
407 def __enter__(self) -> Response:
408 return self
409
410 def __exit__(self, exc_type, exc_value, tb): # type: ignore
411 self.close()
412
413 def freeze(self) -> None:
414 """Make the response object ready to be pickled. Does the
415 following:
416
417 * Buffer the response into a list, ignoring
418 :attr:`implicity_sequence_conversion` and
419 :attr:`direct_passthrough`.
420 * Set the ``Content-Length`` header.
421 * Generate an ``ETag`` header if one is not already set.
422
423 .. versionchanged:: 2.1
424 Removed the ``no_etag`` parameter.
425
426 .. versionchanged:: 2.0
427 An ``ETag`` header is always added.
428
429 .. versionchanged:: 0.6
430 The ``Content-Length`` header is set.
431 """
432 # Always freeze the encoded response body, ignore
433 # implicit_sequence_conversion and direct_passthrough.
434 self.response = list(self.iter_encoded())
435 self.headers["Content-Length"] = str(sum(map(len, self.response)))
436 self.add_etag()
437
438 def get_wsgi_headers(self, environ: WSGIEnvironment) -> Headers:
439 """This is automatically called right before the response is started
440 and returns headers modified for the given environment. It returns a
441 copy of the headers from the response with some modifications applied
442 if necessary.
443
444 For example the location header (if present) is joined with the root
445 URL of the environment. Also the content length is automatically set
446 to zero here for certain status codes.
447
448 .. versionchanged:: 0.6
449 Previously that function was called `fix_headers` and modified
450 the response object in place. Also since 0.6, IRIs in location
451 and content-location headers are handled properly.
452
453 Also starting with 0.6, Werkzeug will attempt to set the content
454 length if it is able to figure it out on its own. This is the
455 case if all the strings in the response iterable are already
456 encoded and the iterable is buffered.
457
458 :param environ: the WSGI environment of the request.
459 :return: returns a new :class:`~werkzeug.datastructures.Headers`
460 object.
461 """
462 headers = Headers(self.headers)
463 location: str | None = None
464 content_location: str | None = None
465 content_length: str | int | None = None
466 status = self.status_code
467
468 # iterate over the headers to find all values in one go. Because
469 # get_wsgi_headers is used each response that gives us a tiny
470 # speedup.
471 for key, value in headers:
472 ikey = key.lower()
473 if ikey == "location":
474 location = value
475 elif ikey == "content-location":
476 content_location = value
477 elif ikey == "content-length":
478 content_length = value
479
480 if location is not None:
481 location = iri_to_uri(location)
482
483 if self.autocorrect_location_header:
484 # Make the location header an absolute URL.
485 current_url = get_current_url(environ, strip_querystring=True)
486 current_url = iri_to_uri(current_url)
487 location = urljoin(current_url, location)
488
489 headers["Location"] = location
490
491 # make sure the content location is a URL
492 if content_location is not None:
493 headers["Content-Location"] = iri_to_uri(content_location)
494
495 if 100 <= status < 200 or status == 204:
496 # Per section 3.3.2 of RFC 7230, "a server MUST NOT send a
497 # Content-Length header field in any response with a status
498 # code of 1xx (Informational) or 204 (No Content)."
499 headers.remove("Content-Length")
500 elif status == 304:
501 remove_entity_headers(headers)
502
503 # if we can determine the content length automatically, we
504 # should try to do that. But only if this does not involve
505 # flattening the iterator or encoding of strings in the
506 # response. We however should not do that if we have a 304
507 # response.
508 if (
509 self.automatically_set_content_length
510 and self.is_sequence
511 and content_length is None
512 and status not in (204, 304)
513 and not (100 <= status < 200)
514 ):
515 content_length = sum(len(x) for x in self.iter_encoded())
516 headers["Content-Length"] = str(content_length)
517
518 return headers
519
520 def get_app_iter(self, environ: WSGIEnvironment) -> t.Iterable[bytes]:
521 """Returns the application iterator for the given environ. Depending
522 on the request method and the current status code the return value
523 might be an empty response rather than the one from the response.
524
525 If the request method is `HEAD` or the status code is in a range
526 where the HTTP specification requires an empty response, an empty
527 iterable is returned.
528
529 .. versionadded:: 0.6
530
531 :param environ: the WSGI environment of the request.
532 :return: a response iterable.
533 """
534 status = self.status_code
535 if (
536 environ["REQUEST_METHOD"] == "HEAD"
537 or 100 <= status < 200
538 or status in (204, 304)
539 ):
540 iterable: t.Iterable[bytes] = ()
541 elif self.direct_passthrough:
542 return self.response # type: ignore
543 else:
544 iterable = self.iter_encoded()
545 return ClosingIterator(iterable, self.close)
546
547 def get_wsgi_response(
548 self, environ: WSGIEnvironment
549 ) -> tuple[t.Iterable[bytes], str, list[tuple[str, str]]]:
550 """Returns the final WSGI response as tuple. The first item in
551 the tuple is the application iterator, the second the status and
552 the third the list of headers. The response returned is created
553 specially for the given environment. For example if the request
554 method in the WSGI environment is ``'HEAD'`` the response will
555 be empty and only the headers and status code will be present.
556
557 .. versionadded:: 0.6
558
559 :param environ: the WSGI environment of the request.
560 :return: an ``(app_iter, status, headers)`` tuple.
561 """
562 headers = self.get_wsgi_headers(environ)
563 app_iter = self.get_app_iter(environ)
564 return app_iter, self.status, headers.to_wsgi_list()
565
566 def __call__(
567 self, environ: WSGIEnvironment, start_response: StartResponse
568 ) -> t.Iterable[bytes]:
569 """Process this response as WSGI application.
570
571 :param environ: the WSGI environment.
572 :param start_response: the response callable provided by the WSGI
573 server.
574 :return: an application iterator
575 """
576 app_iter, status, headers = self.get_wsgi_response(environ)
577 start_response(status, headers)
578 return app_iter
579
580 # JSON
581
582 #: A module or other object that has ``dumps`` and ``loads``
583 #: functions that match the API of the built-in :mod:`json` module.
584 json_module = json
585
586 @property
587 def json(self) -> t.Any | None:
588 """The parsed JSON data if :attr:`mimetype` indicates JSON
589 (:mimetype:`application/json`, see :attr:`is_json`).
590
591 Calls :meth:`get_json` with default arguments.
592 """
593 return self.get_json()
594
595 @t.overload
596 def get_json(self, force: bool = ..., silent: t.Literal[False] = ...) -> t.Any: ...
597
598 @t.overload
599 def get_json(self, force: bool = ..., silent: bool = ...) -> t.Any | None: ...
600
601 def get_json(self, force: bool = False, silent: bool = False) -> t.Any | None:
602 """Parse :attr:`data` as JSON. Useful during testing.
603
604 If the mimetype does not indicate JSON
605 (:mimetype:`application/json`, see :attr:`is_json`), this
606 returns ``None``.
607
608 Unlike :meth:`Request.get_json`, the result is not cached.
609
610 :param force: Ignore the mimetype and always try to parse JSON.
611 :param silent: Silence parsing errors and return ``None``
612 instead.
613 """
614 if not (force or self.is_json):
615 return None
616
617 data = self.get_data()
618
619 try:
620 return self.json_module.loads(data)
621 except ValueError:
622 if not silent:
623 raise
624
625 return None
626
627 # Stream
628
629 @cached_property
630 def stream(self) -> ResponseStream:
631 """The response iterable as write-only stream."""
632 return ResponseStream(self)
633
634 def _wrap_range_response(self, start: int, length: int) -> None:
635 """Wrap existing Response in case of Range Request context."""
636 if self.status_code == 206:
637 self.response = _RangeWrapper(self.response, start, length) # type: ignore
638
639 def _is_range_request_processable(self, environ: WSGIEnvironment) -> bool:
640 """Return ``True`` if `Range` header is present and if underlying
641 resource is considered unchanged when compared with `If-Range` header.
642 """
643 return (
644 "HTTP_IF_RANGE" not in environ
645 or not is_resource_modified(
646 environ,
647 self.headers.get("etag"),
648 None,
649 self.headers.get("last-modified"),
650 ignore_if_range=False,
651 )
652 ) and "HTTP_RANGE" in environ
653
654 def _process_range_request(
655 self,
656 environ: WSGIEnvironment,
657 complete_length: int | None,
658 accept_ranges: bool | str,
659 ) -> bool:
660 """Handle Range Request related headers (RFC7233). If `Accept-Ranges`
661 header is valid, and Range Request is processable, we set the headers
662 as described by the RFC, and wrap the underlying response in a
663 RangeWrapper.
664
665 Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise.
666
667 :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`
668 if `Range` header could not be parsed or satisfied.
669
670 .. versionchanged:: 2.0
671 Returns ``False`` if the length is 0.
672 """
673 from ..exceptions import RequestedRangeNotSatisfiable
674
675 if (
676 not accept_ranges
677 or complete_length is None
678 or complete_length == 0
679 or not self._is_range_request_processable(environ)
680 ):
681 return False
682
683 if accept_ranges is True:
684 accept_ranges = "bytes"
685
686 parsed_range = parse_range_header(environ.get("HTTP_RANGE"))
687
688 if parsed_range is None:
689 raise RequestedRangeNotSatisfiable(complete_length)
690
691 range_tuple = parsed_range.range_for_length(complete_length)
692 content_range_header = parsed_range.to_content_range_header(complete_length)
693
694 if range_tuple is None or content_range_header is None:
695 raise RequestedRangeNotSatisfiable(complete_length)
696
697 content_length = range_tuple[1] - range_tuple[0]
698 self.headers["Content-Length"] = str(content_length)
699 self.headers["Accept-Ranges"] = accept_ranges
700 self.content_range = content_range_header # type: ignore
701 self.status_code = 206
702 self._wrap_range_response(range_tuple[0], content_length)
703 return True
704
705 def make_conditional(
706 self,
707 request_or_environ: WSGIEnvironment | Request,
708 accept_ranges: bool | str = False,
709 complete_length: int | None = None,
710 ) -> Response:
711 """Make the response conditional to the request. This method works
712 best if an etag was defined for the response already. The `add_etag`
713 method can be used to do that. If called without etag just the date
714 header is set.
715
716 This does nothing if the request method in the request or environ is
717 anything but GET or HEAD.
718
719 For optimal performance when handling range requests, it's recommended
720 that your response data object implements `seekable`, `seek` and `tell`
721 methods as described by :py:class:`io.IOBase`. Objects returned by
722 :meth:`~werkzeug.wsgi.wrap_file` automatically implement those methods.
723
724 It does not remove the body of the response because that's something
725 the :meth:`__call__` function does for us automatically.
726
727 Returns self so that you can do ``return resp.make_conditional(req)``
728 but modifies the object in-place.
729
730 :param request_or_environ: a request object or WSGI environment to be
731 used to make the response conditional
732 against.
733 :param accept_ranges: This parameter dictates the value of
734 `Accept-Ranges` header. If ``False`` (default),
735 the header is not set. If ``True``, it will be set
736 to ``"bytes"``. If it's a string, it will use this
737 value.
738 :param complete_length: Will be used only in valid Range Requests.
739 It will set `Content-Range` complete length
740 value and compute `Content-Length` real value.
741 This parameter is mandatory for successful
742 Range Requests completion.
743 :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`
744 if `Range` header could not be parsed or satisfied.
745
746 .. versionchanged:: 2.0
747 Range processing is skipped if length is 0 instead of
748 raising a 416 Range Not Satisfiable error.
749 """
750 environ = _get_environ(request_or_environ)
751 if environ["REQUEST_METHOD"] in ("GET", "HEAD"):
752 # if the date is not in the headers, add it now. We however
753 # will not override an already existing header. Unfortunately
754 # this header will be overridden by many WSGI servers including
755 # wsgiref.
756 if "date" not in self.headers:
757 self.headers["Date"] = http_date()
758 is206 = self._process_range_request(environ, complete_length, accept_ranges)
759 if not is206 and not is_resource_modified(
760 environ,
761 self.headers.get("etag"),
762 None,
763 self.headers.get("last-modified"),
764 ):
765 if parse_etags(environ.get("HTTP_IF_MATCH")):
766 self.status_code = 412
767 else:
768 self.status_code = 304
769 if (
770 self.automatically_set_content_length
771 and "content-length" not in self.headers
772 ):
773 length = self.calculate_content_length()
774 if length is not None:
775 self.headers["Content-Length"] = str(length)
776 return self
777
778 def add_etag(self, overwrite: bool = False, weak: bool = False) -> None:
779 """Add an etag for the current response if there is none yet.
780
781 .. versionchanged:: 2.0
782 SHA-1 is used to generate the value. MD5 may not be
783 available in some environments.
784 """
785 if overwrite or "etag" not in self.headers:
786 self.set_etag(generate_etag(self.get_data()), weak)
787
788
789class ResponseStream:
790 """A file descriptor like object used by :meth:`Response.stream` to
791 represent the body of the stream. It directly pushes into the
792 response iterable of the response object.
793 """
794
795 mode = "wb+"
796
797 def __init__(self, response: Response):
798 self.response = response
799 self.closed = False
800
801 def write(self, value: bytes) -> int:
802 if self.closed:
803 raise ValueError("I/O operation on closed file")
804 self.response._ensure_sequence(mutable=True)
805 self.response.response.append(value) # type: ignore
806 self.response.headers.pop("Content-Length", None)
807 return len(value)
808
809 def writelines(self, seq: t.Iterable[bytes]) -> None:
810 for item in seq:
811 self.write(item)
812
813 def close(self) -> None:
814 self.closed = True
815
816 def flush(self) -> None:
817 if self.closed:
818 raise ValueError("I/O operation on closed file")
819
820 def isatty(self) -> bool:
821 if self.closed:
822 raise ValueError("I/O operation on closed file")
823 return False
824
825 def tell(self) -> int:
826 self.response._ensure_sequence()
827 return sum(map(len, self.response.response))
828
829 @property
830 def encoding(self) -> str:
831 return "utf-8"