(self, message: ASGISendEvent)
| 460 | |
| 461 | class="cm"># ASGI interface |
| 462 | async def send(self, message: ASGISendEvent) -> None: |
| 463 | if self.flow.write_paused and not self.disconnected: |
| 464 | await self.flow.drain() class="cm"># pragma: full coverage |
| 465 | |
| 466 | if self.disconnected: |
| 467 | return class="cm"># pragma: full coverage |
| 468 | |
| 469 | if not self.response_started: |
| 470 | class="cm"># Sending response status line and headers |
| 471 | if message[class="st">"type"] != class="st">"http.response.start": |
| 472 | raise RuntimeError(fclass="st">"Expected ASGI message &class="cm">#x27;http.response.start', but got '{message['type']}'.") |
| 473 | |
| 474 | self.response_started = True |
| 475 | self.waiting_for_100_continue = False |
| 476 | |
| 477 | status_code = message[class="st">"status"] |
| 478 | headers = self.default_headers + list(message.get(class="st">"headers", [])) |
| 479 | |
| 480 | if CLOSE_HEADER in self.scope[class="st">"headers"] and CLOSE_HEADER not in headers: |
| 481 | headers = headers + [CLOSE_HEADER] |
| 482 | |
| 483 | if self.access_log: |
| 484 | self.access_logger.info( |
| 485 | &class="cm">#x27;%s - class="st">"%s %s HTTP/%s" %d', |
| 486 | get_client_addr(self.scope), |
| 487 | self.scope[class="st">"method"], |
| 488 | get_path_with_query_string(self.scope), |
| 489 | self.scope[class="st">"http_version"], |
| 490 | status_code, |
| 491 | ) |
| 492 | |
| 493 | class="cm"># Write response status line and headers |
| 494 | content = [STATUS_LINE[status_code]] |
| 495 | |
| 496 | for name, value in headers: |
| 497 | if HEADER_RE.search(name): |
| 498 | raise RuntimeError(class="st">"Invalid HTTP header name.") class="cm"># pragma: full coverage |
| 499 | if HEADER_VALUE_RE.search(value): |
| 500 | raise RuntimeError(class="st">"Invalid HTTP header value.") |
| 501 | |
| 502 | name = name.lower() |
| 503 | if name == bclass="st">"content-length" and self.chunked_encoding is None: |
| 504 | self.expected_content_length = int(value.decode()) |
| 505 | self.chunked_encoding = False |
| 506 | elif name == bclass="st">"transfer-encoding" and value.lower() == bclass="st">"chunked": |
| 507 | self.expected_content_length = 0 |
| 508 | self.chunked_encoding = True |
| 509 | elif name == bclass="st">"connection" and value.lower() == bclass="st">"close": |
| 510 | self.keep_alive = False |
| 511 | content.extend([name, bclass="st">": ", value, bclass="st">"\r\n"]) |
| 512 | |
| 513 | if self.chunked_encoding is None and self.scope[class="st">"method"] != class="st">"HEAD" and status_code not in (204, 304): |
| 514 | class="cm"># Neither content-length nor transfer-encoding specified |
| 515 | self.chunked_encoding = True |
| 516 | content.append(bclass="st">"transfer-encoding: chunked\r\n") |
| 517 | |
| 518 | content.append(bclass="st">"\r\n") |
| 519 | self.transport.write(bclass="st">"".join(content)) |
no test coverage detected