(self, header_block: bytes, reader, writer)
| 1333 | # ── Plain HTTP forwarding ───────────────────────────────────── |
| 1334 | |
| 1335 | async def _do_http(self, header_block: bytes, reader, writer): |
| 1336 | body = b"" |
| 1337 | if has_unsupported_transfer_encoding(header_block): |
| 1338 | log.warning("Unsupported Transfer-Encoding on plain HTTP request") |
| 1339 | writer.write( |
| 1340 | b"HTTP/1.1 501 Not Implemented\r\n" |
| 1341 | b"Connection: close\r\n" |
| 1342 | b"Content-Length: 0\r\n\r\n" |
| 1343 | ) |
| 1344 | await writer.drain() |
| 1345 | return |
| 1346 | length = parse_content_length(header_block) |
| 1347 | if length > MAX_REQUEST_BODY_BYTES: |
| 1348 | writer.write(b"HTTP/1.1 413 Content Too Large\r\n\r\n") |
| 1349 | await writer.drain() |
| 1350 | return |
| 1351 | if length > 0: |
| 1352 | body = await reader.readexactly(length) |
| 1353 | |
| 1354 | first_line = header_block.split(b"\r\n")[0].decode(errors="replace") |
| 1355 | log.info("HTTP → %s", first_line) |
| 1356 | |
| 1357 | # Parse request and relay through Apps Script |
| 1358 | parts = first_line.strip().split(" ", 2) |
| 1359 | method = parts[0] if parts else "GET" |
| 1360 | url = parts[1] if len(parts) > 1 else "/" |
| 1361 | |
| 1362 | headers = {} |
| 1363 | for raw_line in header_block.split(b"\r\n")[1:]: |
| 1364 | if b":" in raw_line: |
| 1365 | k, v = raw_line.decode(errors="replace").split(":", 1) |
| 1366 | headers[k.strip()] = v.strip() |
| 1367 | |
| 1368 | # ── CORS preflight over plain HTTP ───────────────────────────── |
| 1369 | origin = header_value(headers, "origin") |
| 1370 | acr_method = header_value(headers, "access-control-request-method") |
| 1371 | acr_headers = header_value(headers, "access-control-request-headers") |
| 1372 | if method.upper() == "OPTIONS" and acr_method: |
| 1373 | log.debug("CORS preflight (HTTP) → %s (responding locally)", url[:60]) |
| 1374 | writer.write(cors_preflight_response( |
| 1375 | origin, acr_method, acr_headers, |
| 1376 | )) |
| 1377 | await writer.drain() |
| 1378 | return |
| 1379 | |
| 1380 | if await self._maybe_stream_download(method, url, headers, body, writer): |
| 1381 | return |
| 1382 | |
| 1383 | # Cache check for GET |
| 1384 | response = None |
| 1385 | if self._cache_allowed(method, url, headers, body): |
| 1386 | response = self._cache.get(url) |
| 1387 | if response: |
| 1388 | log.debug("Cache HIT (HTTP): %s", url[:60]) |
| 1389 | |
| 1390 | if response is None: |
| 1391 | response = await self._relay_smart(method, url, headers, body) |
| 1392 | # Cache successful GET |
no test coverage detected