Source code for simvx.core.assets.sources.http
"""HTTP/HTTPS source: fetches bytes over the network.
Uses :mod:`urllib.request` from the standard library so the engine has no
required network dependency. The web backend overrides this with a
``pyodide.http.pyfetch`` shim (see ``simvx.web.assets``).
Listing is not supported: HTTP has no portable directory protocol. To
load a group of remote URIs, ship a manifest JSON listing the URIs and
call ``AssetServer.load_manifest(...)``.
"""
from __future__ import annotations
import urllib.request
from collections.abc import Iterable
[docs]
class HttpSource:
"""Reads bytes via :func:`urllib.request.urlopen`.
Captures the ``ETag`` response header (when present) and exposes it
through :meth:`version` so the cache can revalidate without a full
refetch on subsequent loads.
"""
scheme = "https" # Same impl serves both schemes; AssetServer registers each.
def __init__(self, *, timeout: float = 10.0) -> None:
self._etag_cache: dict[str, str] = {}
self.timeout = timeout
[docs]
def read_bytes(self, uri: str) -> bytes:
req = urllib.request.Request(uri)
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
etag = resp.headers.get("ETag")
if etag:
self._etag_cache[uri] = etag
return resp.read()
[docs]
def version(self, uri: str) -> str | None:
cached = self._etag_cache.get(uri)
if cached is not None:
return f"etag:{cached}"
try:
req = urllib.request.Request(uri, method="HEAD")
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
etag = resp.headers.get("ETag")
if etag:
self._etag_cache[uri] = etag
return f"etag:{etag}"
last_mod = resp.headers.get("Last-Modified")
if last_mod:
return f"last-modified:{last_mod}"
except OSError:
return None
return None
[docs]
def list(self, uri: str) -> Iterable[str]:
raise NotImplementedError(
"HttpSource cannot list remote directories; ship a manifest JSON instead."
)