aio-libs / aiohttp
1 10
import asyncio
2 10
import collections.abc
3 10
import datetime
4 10
import enum
5 10
import json
6 10
import math
7 10
import time
8 10
import warnings
9 10
import zlib
10 10
from concurrent.futures import Executor
11 10
from email.utils import parsedate
12 10
from http.cookies import Morsel
13 10
from typing import (
14
    TYPE_CHECKING,
15
    Any,
16
    Dict,
17
    Iterator,
18
    Mapping,
19
    MutableMapping,
20
    Optional,
21
    Tuple,
22
    Union,
23
    cast,
24
)
25

26 10
from multidict import CIMultiDict, istr
27

28 10
from . import hdrs, payload
29 10
from .abc import AbstractStreamWriter
30 10
from .helpers import (
31
    ETAG_ANY,
32
    PY_38,
33
    QUOTED_ETAG_RE,
34
    CookieMixin,
35
    ETag,
36
    HeadersMixin,
37
    populate_with_cookies,
38
    rfc822_formatted_time,
39
    sentinel,
40
    validate_etag_value,
41
)
42 10
from .http import RESPONSES, SERVER_SOFTWARE, HttpVersion10, HttpVersion11
43 10
from .payload import Payload
44 10
from .typedefs import JSONEncoder, LooseHeaders
45

46 10
__all__ = ("ContentCoding", "StreamResponse", "Response", "json_response")
47

48

49
if TYPE_CHECKING:  # pragma: no cover
50
    from .web_request import BaseRequest
51

52
    BaseClass = MutableMapping[str, Any]
53
else:
54 10
    BaseClass = collections.abc.MutableMapping
55

56

57 10
if not PY_38:
58
    # allow samesite to be used in python < 3.8
59
    # already permitted in python 3.8, see https://bugs.python.org/issue29613
60 3
    Morsel._reserved["samesite"] = "SameSite"  # type: ignore[attr-defined]
61

62

63 10
class ContentCoding(enum.Enum):
64
    # The content codings that we have support for.
65
    #
66
    # Additional registered codings are listed at:
67
    # https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding
68 10
    deflate = "deflate"
69 10
    gzip = "gzip"
70 10
    identity = "identity"
71

72

73
############################################################
74
# HTTP Response classes
75
############################################################
76

77

78 10
class StreamResponse(BaseClass, HeadersMixin, CookieMixin):
79

80 10
    __slots__ = (
81
        "_length_check",
82
        "_body",
83
        "_keep_alive",
84
        "_chunked",
85
        "_compression",
86
        "_compression_force",
87
        "_req",
88
        "_payload_writer",
89
        "_eof_sent",
90
        "_body_length",
91
        "_state",
92
        "_headers",
93
        "_status",
94
        "_reason",
95
        "__weakref__",
96
    )
97

98 10
    def __init__(
99
        self,
100
        *,
101
        status: int = 200,
102
        reason: Optional[str] = None,
103
        headers: Optional[LooseHeaders] = None,
104
    ) -> None:
105 10
        super().__init__()
106 10
        self._length_check = True
107 10
        self._body = None
108 10
        self._keep_alive = None  # type: Optional[bool]
109 10
        self._chunked = False
110 10
        self._compression = False
111 10
        self._compression_force = None  # type: Optional[ContentCoding]
112

113 10
        self._req = None  # type: Optional[BaseRequest]
114 10
        self._payload_writer = None  # type: Optional[AbstractStreamWriter]
115 10
        self._eof_sent = False
116 10
        self._body_length = 0
117 10
        self._state = {}  # type: Dict[str, Any]
118

119 10
        if headers is not None:
120 10
            self._headers = CIMultiDict(headers)  # type: CIMultiDict[str]
121
        else:
122 10
            self._headers = CIMultiDict()
123

124 10
        self.set_status(status, reason)
125

126 10
    @property
127 10
    def prepared(self) -> bool:
128 10
        return self._payload_writer is not None
129

130 10
    @property
131 10
    def task(self) -> "Optional[asyncio.Task[None]]":
132 10
        if self._req:
133 10
            return self._req.task
134
        else:
135 10
            return None
136

137 10
    @property
138 10
    def status(self) -> int:
139 10
        return self._status
140

141 10
    @property
142 10
    def chunked(self) -> bool:
143 10
        return self._chunked
144

145 10
    @property
146 10
    def compression(self) -> bool:
147 10
        return self._compression
148

149 10
    @property
150 10
    def reason(self) -> str:
151 10
        return self._reason
152

153 10
    def set_status(
154
        self,
155
        status: int,
156
        reason: Optional[str] = None,
157
        _RESPONSES: Mapping[int, Tuple[str, str]] = RESPONSES,
158
    ) -> None:
159 10
        assert not self.prepared, (
160
            "Cannot change the response status code after " "the headers have been sent"
161
        )
162 10
        self._status = int(status)
163 10
        if reason is None:
164 10
            try:
165 10
                reason = _RESPONSES[self._status][0]
166 0
            except Exception:
167 0
                reason = ""
168 10
        self._reason = reason
169

170 10
    @property
171 10
    def keep_alive(self) -> Optional[bool]:
172 10
        return self._keep_alive
173

174 10
    def force_close(self) -> None:
175 10
        self._keep_alive = False
176

177 10
    @property
178 10
    def body_length(self) -> int:
179 10
        return self._body_length
180

181 10
    def enable_chunked_encoding(self) -> None:
182
        """Enables automatic chunked transfer encoding."""
183 10
        self._chunked = True
184

185 10
        if hdrs.CONTENT_LENGTH in self._headers:
186 10
            raise RuntimeError(
187
                "You can't enable chunked encoding when " "a content length is set"
188
            )
189

190 10
    def enable_compression(self, force: Optional[ContentCoding] = None) -> None:
191
        """Enables response compression encoding."""
192
        # Backwards compatibility for when force was a bool <0.17.
193 10
        self._compression = True
194 10
        self._compression_force = force
195

196 10
    @property
197 10
    def headers(self) -> "CIMultiDict[str]":
198 10
        return self._headers
199

200 10
    @property
201 10
    def content_length(self) -> Optional[int]:
202
        # Just a placeholder for adding setter
203 10
        return super().content_length
204

205 10
    @content_length.setter
206 10
    def content_length(self, value: Optional[int]) -> None:
207 10
        if value is not None:
208 10
            value = int(value)
209 10
            if self._chunked:
210 10
                raise RuntimeError(
211
                    "You can't set content length when " "chunked encoding is enable"
212
                )
213 10
            self._headers[hdrs.CONTENT_LENGTH] = str(value)
214
        else:
215 10
            self._headers.pop(hdrs.CONTENT_LENGTH, None)
216

217 10
    @property
218 10
    def content_type(self) -> str:
219
        # Just a placeholder for adding setter
220 10
        return super().content_type
221

222 10
    @content_type.setter
223 10
    def content_type(self, value: str) -> None:
224 10
        self.content_type  # read header values if needed
225 10
        self._content_type = str(value)
226 10
        self._generate_content_type_header()
227

228 10
    @property
229 10
    def charset(self) -> Optional[str]:
230
        # Just a placeholder for adding setter
231 10
        return super().charset
232

233 10
    @charset.setter
234 10
    def charset(self, value: Optional[str]) -> None:
235 10
        ctype = self.content_type  # read header values if needed
236 10
        if ctype == "application/octet-stream":
237 10
            raise RuntimeError(
238
                "Setting charset for application/octet-stream "
239
                "doesn't make sense, setup content_type first"
240
            )
241 10
        assert self._content_dict is not None
242 10
        if value is None:
243 10
            self._content_dict.pop("charset", None)
244
        else:
245 10
            self._content_dict["charset"] = str(value).lower()
246 10
        self._generate_content_type_header()
247

248 10
    @property
249 10
    def last_modified(self) -> Optional[datetime.datetime]:
250
        """The value of Last-Modified HTTP header, or None.
251

252
        This header is represented as a `datetime` object.
253
        """
254 10
        httpdate = self._headers.get(hdrs.LAST_MODIFIED)
255 10
        if httpdate is not None:
256 10
            timetuple = parsedate(httpdate)
257 10
            if timetuple is not None:
258 10
                return datetime.datetime(*timetuple[:6], tzinfo=datetime.timezone.utc)
259 10
        return None
260

261 10
    @last_modified.setter
262 10
    def last_modified(
263
        self, value: Optional[Union[int, float, datetime.datetime, str]]
264
    ) -> None:
265 10
        if value is None:
266 10
            self._headers.pop(hdrs.LAST_MODIFIED, None)
267 10
        elif isinstance(value, (int, float)):
268 10
            self._headers[hdrs.LAST_MODIFIED] = time.strftime(
269
                "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(math.ceil(value))
270
            )
271 10
        elif isinstance(value, datetime.datetime):
272 10
            self._headers[hdrs.LAST_MODIFIED] = time.strftime(
273
                "%a, %d %b %Y %H:%M:%S GMT", value.utctimetuple()
274
            )
275 10
        elif isinstance(value, str):
276 10
            self._headers[hdrs.LAST_MODIFIED] = value
277

278 10
    @property
279 10
    def etag(self) -> Optional[ETag]:
280 10
        quoted_value = self._headers.get(hdrs.ETAG)
281 10
        if not quoted_value:
282 10
            return None
283 10
        elif quoted_value == ETAG_ANY:
284 10
            return ETag(value=ETAG_ANY)
285 10
        match = QUOTED_ETAG_RE.fullmatch(quoted_value)
286 10
        if not match:
287 10
            return None
288 10
        is_weak, value = match.group(1, 2)
289 10
        return ETag(
290
            is_weak=bool(is_weak),
291
            value=value,
292
        )
293

294 10
    @etag.setter
295 10
    def etag(self, value: Optional[Union[ETag, str]]) -> None:
296 10
        if value is None:
297 10
            self._headers.pop(hdrs.ETAG, None)
298 10
        elif (isinstance(value, str) and value == ETAG_ANY) or (
299
            isinstance(value, ETag) and value.value == ETAG_ANY
300
        ):
301 10
            self._headers[hdrs.ETAG] = ETAG_ANY
302 10
        elif isinstance(value, str):
303 10
            validate_etag_value(value)
304 10
            self._headers[hdrs.ETAG] = f'"{value}"'
305 10
        elif isinstance(value, ETag) and isinstance(value.value, str):
306 10
            validate_etag_value(value.value)
307 10
            hdr_value = f'W/"{value.value}"' if value.is_weak else f'"{value.value}"'
308 10
            self._headers[hdrs.ETAG] = hdr_value
309
        else:
310 10
            raise ValueError(
311
                f"Unsupported etag type: {type(value)}. "
312
                f"etag must be str, ETag or None"
313
            )
314

315 10
    def _generate_content_type_header(
316
        self, CONTENT_TYPE: istr = hdrs.CONTENT_TYPE
317
    ) -> None:
318 10
        assert self._content_dict is not None
319 10
        assert self._content_type is not None
320 10
        params = "; ".join(f"{k}={v}" for k, v in self._content_dict.items())
321 10
        if params:
322 10
            ctype = self._content_type + "; " + params
323
        else:
324 10
            ctype = self._content_type
325 10
        self._headers[CONTENT_TYPE] = ctype
326

327 10
    async def _do_start_compression(self, coding: ContentCoding) -> None:
328 10
        if coding != ContentCoding.identity:
329 10
            assert self._payload_writer is not None
330 10
            self._headers[hdrs.CONTENT_ENCODING] = coding.value
331 10
            self._payload_writer.enable_compression(coding.value)
332
            # Compressed payload may have different content length,
333
            # remove the header
334 10
            self._headers.popall(hdrs.CONTENT_LENGTH, None)
335

336 10
    async def _start_compression(self, request: "BaseRequest") -> None:
337 10
        if self._compression_force:
338 10
            await self._do_start_compression(self._compression_force)
339
        else:
340 10
            accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, "").lower()
341 10
            for coding in ContentCoding:
342 10
                if coding.value in accept_encoding:
343 10
                    await self._do_start_compression(coding)
344 10
                    return
345

346 10
    async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]:
347 10
        if self._eof_sent:
348 10
            return None
349 10
        if self._payload_writer is not None:
350 10
            return self._payload_writer
351

352 10
        return await self._start(request)
353

354 10
    async def _start(self, request: "BaseRequest") -> AbstractStreamWriter:
355 10
        self._req = request
356 10
        writer = self._payload_writer = request._payload_writer
357

358 10
        await self._prepare_headers()
359 10
        await request._prepare_hook(self)
360 10
        await self._write_headers()
361

362 10
        return writer
363

364 10
    async def _prepare_headers(self) -> None:
365 10
        request = self._req
366 10
        assert request is not None
367 10
        writer = self._payload_writer
368 10
        assert writer is not None
369 10
        keep_alive = self._keep_alive
370 10
        if keep_alive is None:
371 10
            keep_alive = request.keep_alive
372 10
        self._keep_alive = keep_alive
373

374 10
        version = request.version
375

376 10
        headers = self._headers
377 10
        populate_with_cookies(headers, self.cookies)
378

379 10
        if self._compression:
380 10
            await self._start_compression(request)
381

382 10
        if self._chunked:
383 10
            if version != HttpVersion11:
384 10
                raise RuntimeError(
385
                    "Using chunked encoding is forbidden "
386
                    "for HTTP/{0.major}.{0.minor}".format(request.version)
387
                )
388 10
            writer.enable_chunking()
389 10
            headers[hdrs.TRANSFER_ENCODING] = "chunked"
390 10
            if hdrs.CONTENT_LENGTH in headers:
391 0
                del headers[hdrs.CONTENT_LENGTH]
392 10
        elif self._length_check:
393 10
            writer.length = self.content_length
394 10
            if writer.length is None:
395 10
                if version >= HttpVersion11 and self.status != 204:
396 10
                    writer.enable_chunking()
397 10
                    headers[hdrs.TRANSFER_ENCODING] = "chunked"
398 10
                    if hdrs.CONTENT_LENGTH in headers:
399 0
                        del headers[hdrs.CONTENT_LENGTH]
400
                else:
401 10
                    keep_alive = False
402
            # HTTP 1.1: https://tools.ietf.org/html/rfc7230#section-3.3.2
403
            # HTTP 1.0: https://tools.ietf.org/html/rfc1945#section-10.4
404 10
            elif version >= HttpVersion11 and self.status in (100, 101, 102, 103, 204):
405 10
                del headers[hdrs.CONTENT_LENGTH]
406

407 10
        if self.status not in (204, 304):
408 10
            headers.setdefault(hdrs.CONTENT_TYPE, "application/octet-stream")
409 10
        headers.setdefault(hdrs.DATE, rfc822_formatted_time())
410 10
        headers.setdefault(hdrs.SERVER, SERVER_SOFTWARE)
411

412
        # connection header
413 10
        if hdrs.CONNECTION not in headers:
414 10
            if keep_alive:
415 10
                if version == HttpVersion10:
416 10
                    headers[hdrs.CONNECTION] = "keep-alive"
417
            else:
418 10
                if version == HttpVersion11:
419 10
                    headers[hdrs.CONNECTION] = "close"
420

421 10
    async def _write_headers(self) -> None:
422 10
        request = self._req
423 10
        assert request is not None
424 10
        writer = self._payload_writer
425 10
        assert writer is not None
426
        # status line
427 10
        version = request.version
428 10
        status_line = "HTTP/{}.{} {} {}".format(
429
            version[0], version[1], self._status, self._reason
430
        )
431 10
        await writer.write_headers(status_line, self._headers)
432

433 10
    async def write(self, data: bytes) -> None:
434 10
        assert isinstance(
435
            data, (bytes, bytearray, memoryview)
436
        ), "data argument must be byte-ish (%r)" % type(data)
437

438 10
        if self._eof_sent:
439 10
            raise RuntimeError("Cannot call write() after write_eof()")
440 10
        if self._payload_writer is None:
441 10
            raise RuntimeError("Cannot call write() before prepare()")
442

443 10
        await self._payload_writer.write(data)
444

445 10
    async def drain(self) -> None:
446 10
        assert not self._eof_sent, "EOF has already been sent"
447 10
        assert self._payload_writer is not None, "Response has not been started"
448 10
        warnings.warn(
449
            "drain method is deprecated, use await resp.write()",
450
            DeprecationWarning,
451
            stacklevel=2,
452
        )
453 10
        await self._payload_writer.drain()
454

455 10
    async def write_eof(self, data: bytes = b"") -> None:
456 10
        assert isinstance(
457
            data, (bytes, bytearray, memoryview)
458
        ), "data argument must be byte-ish (%r)" % type(data)
459

460 10
        if self._eof_sent:
461 10
            return
462

463 10
        assert self._payload_writer is not None, "Response has not been started"
464

465 10
        await self._payload_writer.write_eof(data)
466 10
        self._eof_sent = True
467 10
        self._req = None
468 10
        self._body_length = self._payload_writer.output_size
469 10
        self._payload_writer = None
470

471 10
    def __repr__(self) -> str:
472 10
        if self._eof_sent:
473 10
            info = "eof"
474 10
        elif self.prepared:
475 10
            assert self._req is not None
476 10
            info = f"{self._req.method} {self._req.path} "
477
        else:
478 10
            info = "not prepared"
479 10
        return f"<{self.__class__.__name__} {self.reason} {info}>"
480

481 10
    def __getitem__(self, key: str) -> Any:
482 10
        return self._state[key]
483

484 10
    def __setitem__(self, key: str, value: Any) -> None:
485 10
        self._state[key] = value
486

487 10
    def __delitem__(self, key: str) -> None:
488 10
        del self._state[key]
489

490 10
    def __len__(self) -> int:
491 10
        return len(self._state)
492

493 10
    def __iter__(self) -> Iterator[str]:
494 10
        return iter(self._state)
495

496 10
    def __hash__(self) -> int:
497 10
        return hash(id(self))
498

499 10
    def __eq__(self, other: object) -> bool:
500 10
        return self is other
501

502

503 10
class Response(StreamResponse):
504

505 10
    __slots__ = (
506
        "_body_payload",
507
        "_compressed_body",
508
        "_zlib_executor_size",
509
        "_zlib_executor",
510
    )
511

512 10
    def __init__(
513
        self,
514
        *,
515
        body: Any = None,
516
        status: int = 200,
517
        reason: Optional[str] = None,
518
        text: Optional[str] = None,
519
        headers: Optional[LooseHeaders] = None,
520
        content_type: Optional[str] = None,
521
        charset: Optional[str] = None,
522
        zlib_executor_size: Optional[int] = None,
523
        zlib_executor: Optional[Executor] = None,
524
    ) -> None:
525 10
        if body is not None and text is not None:
526 10
            raise ValueError("body and text are not allowed together")
527

528 10
        if headers is None:
529 10
            real_headers = CIMultiDict()  # type: CIMultiDict[str]
530 10
        elif not isinstance(headers, CIMultiDict):
531 10
            real_headers = CIMultiDict(headers)
532
        else:
533 10
            real_headers = headers  # = cast('CIMultiDict[str]', headers)
534

535 10
        if content_type is not None and "charset" in content_type:
536 10
            raise ValueError("charset must not be in content_type " "argument")
537

538 10
        if text is not None:
539 10
            if hdrs.CONTENT_TYPE in real_headers:
540 10
                if content_type or charset:
541 10
                    raise ValueError(
542
                        "passing both Content-Type header and "
543
                        "content_type or charset params "
544
                        "is forbidden"
545
                    )
546
            else:
547
                # fast path for filling headers
548 10
                if not isinstance(text, str):
549 10
                    raise TypeError("text argument must be str (%r)" % type(text))
550 10
                if content_type is None:
551 10
                    content_type = "text/plain"
552 10
                if charset is None:
553 10
                    charset = "utf-8"
554 10
                real_headers[hdrs.CONTENT_TYPE] = content_type + "; charset=" + charset
555 10
                body = text.encode(charset)
556 10
                text = None
557
        else:
558 10
            if hdrs.CONTENT_TYPE in real_headers:
559 10
                if content_type is not None or charset is not None:
560 10
                    raise ValueError(
561
                        "passing both Content-Type header and "
562
                        "content_type or charset params "
563
                        "is forbidden"
564
                    )
565
            else:
566 10
                if content_type is not None:
567 10
                    if charset is not None:
568 10
                        content_type += "; charset=" + charset
569 10
                    real_headers[hdrs.CONTENT_TYPE] = content_type
570

571 10
        super().__init__(status=status, reason=reason, headers=real_headers)
572

573 10
        if text is not None:
574 10
            self.text = text
575
        else:
576 10
            self.body = body
577

578 10
        self._compressed_body = None  # type: Optional[bytes]
579 10
        self._zlib_executor_size = zlib_executor_size
580 10
        self._zlib_executor = zlib_executor
581

582 10
    @property
583 10
    def body(self) -> Optional[Union[bytes, Payload]]:
584 10
        return self._body
585

586 10
    @body.setter
587 10
    def body(
588
        self,
589
        body: bytes,
590
        CONTENT_TYPE: istr = hdrs.CONTENT_TYPE,
591
        CONTENT_LENGTH: istr = hdrs.CONTENT_LENGTH,
592
    ) -> None:
593 10
        if body is None:
594 10
            self._body = None  # type: Optional[bytes]
595 10
            self._body_payload = False  # type: bool
596 10
        elif isinstance(body, (bytes, bytearray)):
597 10
            self._body = body
598 10
            self._body_payload = False
599
        else:
600 10
            try:
601 10
                self._body = body = payload.PAYLOAD_REGISTRY.get(body)
602 10
            except payload.LookupError:
603 10
                raise ValueError("Unsupported body type %r" % type(body))
604

605 10
            self._body_payload = True
606

607 10
            headers = self._headers
608

609
            # set content-length header if needed
610 10
            if not self._chunked and CONTENT_LENGTH not in headers:
611 10
                size = body.size
612 10
                if size is not None:
613 10
                    headers[CONTENT_LENGTH] = str(size)
614

615
            # set content-type
616 10
            if CONTENT_TYPE not in headers:
617 10
                headers[CONTENT_TYPE] = body.content_type
618

619
            # copy payload headers
620 10
            if body.headers:
621 10
                for (key, value) in body.headers.items():
622 10
                    if key not in headers:
623 10
                        headers[key] = value
624

625 10
        self._compressed_body = None
626

627 10
    @property
628 10
    def text(self) -> Optional[str]:
629 10
        if self._body is None:
630 10
            return None
631 10
        return self._body.decode(self.charset or "utf-8")
632

633 10
    @text.setter
634 10
    def text(self, text: str) -> None:
635 10
        assert text is None or isinstance(
636
            text, str
637
        ), "text argument must be str (%r)" % type(text)
638

639 10
        if self.content_type == "application/octet-stream":
640 10
            self.content_type = "text/plain"
641 10
        if self.charset is None:
642 10
            self.charset = "utf-8"
643

644 10
        self._body = text.encode(self.charset)
645 10
        self._body_payload = False
646 10
        self._compressed_body = None
647

648 10
    @property
649 10
    def content_length(self) -> Optional[int]:
650 10
        if self._chunked:
651 10
            return None
652

653 10
        if hdrs.CONTENT_LENGTH in self._headers:
654 10
            return super().content_length
655

656 10
        if self._compressed_body is not None:
657
            # Return length of the compressed body
658 10
            return len(self._compressed_body)
659 10
        elif self._body_payload:
660
            # A payload without content length, or a compressed payload
661 10
            return None
662 10
        elif self._body is not None:
663 10
            return len(self._body)
664
        else:
665 10
            return 0
666

667 10
    @content_length.setter
668 10
    def content_length(self, value: Optional[int]) -> None:
669 10
        raise RuntimeError("Content length is set automatically")
670

671 10
    async def write_eof(self, data: bytes = b"") -> None:
672 10
        if self._eof_sent:
673 10
            return
674 10
        if self._compressed_body is None:
675 10
            body = self._body  # type: Optional[Union[bytes, Payload]]
676
        else:
677 10
            body = self._compressed_body
678 10
        assert not data, f"data arg is not supported, got {data!r}"
679 10
        assert self._req is not None
680 10
        assert self._payload_writer is not None
681 10
        if body is not None:
682 10
            if self._req._method == hdrs.METH_HEAD or self._status in [204, 304]:
683 10
                await super().write_eof()
684 10
            elif self._body_payload:
685 10
                payload = cast(Payload, body)
686 10
                await payload.write(self._payload_writer)
687 10
                await super().write_eof()
688
            else:
689 10
                await super().write_eof(cast(bytes, body))
690
        else:
691 10
            await super().write_eof()
692

693 10
    async def _start(self, request: "BaseRequest") -> AbstractStreamWriter:
694 10
        if not self._chunked and hdrs.CONTENT_LENGTH not in self._headers:
695 10
            if not self._body_payload:
696 10
                if self._body is not None:
697 10
                    self._headers[hdrs.CONTENT_LENGTH] = str(len(self._body))
698
                else:
699 10
                    self._headers[hdrs.CONTENT_LENGTH] = "0"
700

701 10
        return await super()._start(request)
702

703 10
    def _compress_body(self, zlib_mode: int) -> None:
704 10
        assert zlib_mode > 0
705 10
        compressobj = zlib.compressobj(wbits=zlib_mode)
706 10
        body_in = self._body
707 10
        assert body_in is not None
708 10
        self._compressed_body = compressobj.compress(body_in) + compressobj.flush()
709

710 10
    async def _do_start_compression(self, coding: ContentCoding) -> None:
711 10
        if self._body_payload or self._chunked:
712 10
            return await super()._do_start_compression(coding)
713

714 10
        if coding != ContentCoding.identity:
715
            # Instead of using _payload_writer.enable_compression,
716
            # compress the whole body
717 10
            zlib_mode = (
718
                16 + zlib.MAX_WBITS if coding == ContentCoding.gzip else zlib.MAX_WBITS
719
            )
720 10
            body_in = self._body
721 10
            assert body_in is not None
722 10
            if (
723
                self._zlib_executor_size is not None
724
                and len(body_in) > self._zlib_executor_size
725
            ):
726 10
                await asyncio.get_event_loop().run_in_executor(
727
                    self._zlib_executor, self._compress_body, zlib_mode
728
                )
729
            else:
730 10
                self._compress_body(zlib_mode)
731

732 10
            body_out = self._compressed_body
733 10
            assert body_out is not None
734

735 10
            self._headers[hdrs.CONTENT_ENCODING] = coding.value
736 10
            self._headers[hdrs.CONTENT_LENGTH] = str(len(body_out))
737

738

739 10
def json_response(
740
    data: Any = sentinel,
741
    *,
742
    text: Optional[str] = None,
743
    body: Optional[bytes] = None,
744
    status: int = 200,
745
    reason: Optional[str] = None,
746
    headers: Optional[LooseHeaders] = None,
747
    content_type: str = "application/json",
748
    dumps: JSONEncoder = json.dumps,
749
) -> Response:
750 10
    if data is not sentinel:
751 10
        if text or body:
752 10
            raise ValueError("only one of data, text, or body should be specified")
753
        else:
754 10
            text = dumps(data)
755 10
    return Response(
756
        text=text,
757
        body=body,
758
        status=status,
759
        reason=reason,
760
        headers=headers,
761
        content_type=content_type,
762
    )

Read our documentation on viewing source code .

Loading