A simple HTTP/1 client with no pipelining support.
| 337 | |
| 338 | |
| 339 | class Http1Client(Http1Connection): |
| 340 | """A simple HTTP/1 client with no pipelining support.""" |
| 341 | |
| 342 | ReceiveProtocolError = ResponseProtocolError |
| 343 | ReceiveData = ResponseData |
| 344 | ReceiveEndOfMessage = ResponseEndOfMessage |
| 345 | |
| 346 | def __init__(self, context: Context): |
| 347 | super().__init__(context, context.server) |
| 348 | |
| 349 | def send(self, event: HttpEvent) -> layer.CommandGenerator[None]: |
| 350 | if isinstance(event, RequestProtocolError): |
| 351 | yield commands.CloseConnection(self.conn) |
| 352 | return |
| 353 | |
| 354 | if self.stream_id is None: |
| 355 | assert isinstance(event, RequestHeaders) |
| 356 | self.stream_id = event.stream_id |
| 357 | self.request = event.request |
| 358 | assert self.stream_id == event.stream_id |
| 359 | |
| 360 | if isinstance(event, RequestHeaders): |
| 361 | request = event.request |
| 362 | if request.is_http2 or request.is_http3: |
| 363 | # Convert to an HTTP/1 request. |
| 364 | request = ( |
| 365 | request.copy() |
| 366 | ) # (we could probably be a bit more efficient here.) |
| 367 | request.http_version = "HTTP/1.1" |
| 368 | if "Host" not in request.headers and request.authority: |
| 369 | request.headers.insert(0, "Host", request.authority) |
| 370 | request.authority = "" |
| 371 | cookie_headers = request.headers.get_all("Cookie") |
| 372 | if len(cookie_headers) > 1: |
| 373 | # Only HTTP/2 supports multiple cookie headers, HTTP/1.x does not. |
| 374 | # see: https://www.rfc-editor.org/rfc/rfc6265#section-5.4 |
| 375 | # https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.5 |
| 376 | request.headers["Cookie"] = "; ".join(cookie_headers) |
| 377 | raw = http1.assemble_request_head(request) |
| 378 | yield commands.SendData(self.conn, raw) |
| 379 | elif isinstance(event, RequestData): |
| 380 | assert self.request |
| 381 | if "chunked" in self.request.headers.get("transfer-encoding", "").lower(): |
| 382 | raw = b"%x\r\n%s\r\n" % (len(event.data), event.data) |
| 383 | else: |
| 384 | raw = event.data |
| 385 | if raw: |
| 386 | yield commands.SendData(self.conn, raw) |
| 387 | elif isinstance(event, RequestEndOfMessage): |
| 388 | assert self.request |
| 389 | if "chunked" in self.request.headers.get("transfer-encoding", "").lower(): |
| 390 | yield commands.SendData(self.conn, b"0\r\n\r\n") |
| 391 | elif http1.expected_http_body_size(self.request, self.response) == -1: |
| 392 | yield commands.CloseTcpConnection(self.conn, half_close=True) |
| 393 | yield from self.mark_done(request=True) |
| 394 | else: |
| 395 | raise AssertionError(f"Unexpected event: {event}") |
| 396 |
no outgoing calls
searching dependent graphs…